0%

根据树结构去理解整个文件系统的挂载和挂载点还是有点困难,所以自己做了些总结,有不对的地方还请指正

df -h命令后

Filesystem 这里的文件系统表示的是目录的层次结构,而不是所谓的像ext4,fat那种真正的文件系统类型
一个具体的设备文件(或分区) ,或许是一块硬盘上的一个分区,或许是u盘上的一个分区

目录就相当于门
整个linux中的文件系统就像一个世界接着一个世界,不同的世界代表不同的被挂载的文件系统,挂载点就相当于任意门(特殊的门),各个被挂载的文件系统之间是独立的,但是要进入其中一个需要一个一个进入任意门去穿梭,整个世界的第一个门是 / ,它里面的世界是第一个文件系统,这个世界里又有很多个门,有的门背后的世界和这个世界是同一个,则他们是在同一个文件系统里,而当有其他的分区被挂载到一个目录的时候,例如
对一个目录 /home/lll,要到达这个门后的世界,要穿过 / 和 home/ (这个门在 / 后面的世界里) 和 lll/ (这个门在 home/ 后面的世界里),形象的描述为:

/ —》 世界A (包含 home/ —-》 世界B (包含 lll/ —》 世界C )如果不做任何其他挂载,世界A、B、C是同一个世界)当要把/dev/sda1这个id所指向的分区挂载到 /home/lll 则相当于把 /home/lll/ 这个门变成任意门,它通往的世界变成了sda1的世界空间,自然的,只要进了这个世界,如果再不挂载其他的,则所有对 /home/lll/… 的操作都在sda1的世界里,而对这个门前面的世界的操作,如对/home/aaa的操作,仍然在一开始的那个世界里
把挂载点想象成一个任意门,进入第一个任意门是世界A,里面还有一个任意门通往世界B,虽然只有进了第一个任意门才能进第二个任意门,但世界A和世界B是毫无关系彼此独立的(挂载的文件系统之间彼此独立)
挂载绑定:把一个目录挂载到另一个目录,比如 mount –bind dir1 dir2 则是相当于对dir2这个门做了特殊处理,让dir2和dir1通往同一个世界的同一个位置,而dir2原本通往的那个位置里面的东西会被遮盖掉,当解除挂载后,dir2会恢复它原本通往的世界和位置,且里面的东西还在

当把目录A挂载到目录B,= mount –bind A B ; 其中B是新的挂载点 ,就是让门B通往的地方和门A通往的地方一毛一样
把设备A挂载到目录B = mount A B ; 其中B是新的挂载点,就是让门B通往的地方是A的世界空间。
挂载点必须是目录(门)

可以对同一个目录多次挂载,最后生效的结果是最后一次挂载,此时卸载,则倒数第二次挂载生效,再卸载,则倒数第三次挂载生效

在面向对象的编程中,使用对象的继承是一个非常普遍的做法,但是在关系数据库管理系统RDBMS中,使用的是外键表示实体(表)之间的关系,那么对于继承关系,该怎么在RDBMS中表示呢?一般来说有3种实现方式:

Concrete Table Inheritance(具体表继承)

Single Table Inheritance(单表继承)

Class Table Inheritance(类表继承)

比如在一个教务系统中,有老师学生2个对象,这两个对象都是“人”对象的子类,所以我们可以建立一个Person表,该表有人的公共属性:姓名、性别等,还有就是数据的唯一标识,一个ID。而教师对象有教师的特有属性,比如职称,学生有学生的特有属性,比如学号。所以我们可以建立Person、Teacher、Student3个表,其关系在PowerDesigner中如图所示:

1

1.具体表继承。

不建立父对象,将父对象的所有属性转移到子对象中,为每个子对象建立对于的表。如果使用这种方法,那么就只需要建立Teacher表和Student表,不需要Person表,在PowerDesigner中,双击继承节点,打开属性窗口,取消“Generate Parent”选项,选中“Generate children”并选择“Inherit all attributes”,如图所示:

2

生成的数据库表将如图所示:

3

2.单表继承。

在一个宽表中列出所有父对象和子对象的属性,同时用一个标识列表示该行数据存储的是哪个子类的数据。在PowerDesigner中,修改继承节点的属性,取消“Generate children”,选中“Generate parent”,然后在下面添加一个标识列,叫PersonType,如图所示:

4
生成的数据库表,在一个宽表中表示如图所示:

5

可以看到Person中的列集成了Person、Teacher、Student这3个表的所有列,同时还多了一个列PersonType,这个列就是用来区分这行数据到底表示的是一个学生还是一个老师。

3.类表继承。

对父对象和每个子对象建立一个对应的表,然后在子表中设置该子表的主键为与父表关联的外键。在PowerDesigner中,对于继承节点的属性,允许生成父和子表,另外,将“Inherit only primary attributes”选中。如图所示:

6
生成的数据库表如图所示:

7

这里可以看到,Teacher的主键和Student的主键同时又是该表的外键,连接到Person表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Medium

Given an array of integers, find out whether there are two distinct indices i and j in the array such that the absolute difference between nums[i] and nums[j] is at most t and the absolute difference between i and j is at most k.

Example 1:

Input: nums = [1,2,3,1], k = 3, t = 0
Output: true
Example 2:

Input: nums = [1,0,1,1], k = 1, t = 2
Output: true
Example 3:

Input: nums = [1,5,9,1,5,9], k = 2, t = 3
Output: false

给定一个整数数组,找出数组中是否有两个不同的索引i和j,使得nums[i]和nums[j]之间的绝对值差最大为t, i和j之间的绝对值差最大为k。

笨办法就是双重循环,如果下标绝对值符合后再看数值是否符合,会超时

网上找的中文解释都没看懂。

使用TreeSet数据结构,可以借助TreeSet中有用的函数

TreeSet.floor(x) 表示TreeSet中小于或等于x的最大元素
TreeSet.ceiling(x) 表示TreeSet中大于或等于x的最小元素

对于数组中任意一个数nums[i], 与其绝对值差小于等于为t的区间为 [nums[i]-t,nums[i]+t], 数轴表示如下

    I__________I__________I
nums[i]-t   nums[i]      nums[i]+t

使用TreeSet的floor和ceiling函数,可以得到是否有数字在上述区间内
即:f = floor(nums[i] + t) 且 f>=nums[i],说明f在[ nums[i], nums[i]+t]内
c = ceiling(nums[i] - t) 且 c<=nums[i],说明c在[ nums[i]-t, nums[i]]内

如果存在f或者c,然后要保证的是 它们的索引和i差距小于k,则只需维持TreeSet中始终只保留窗口大小为k的元素即可(在访问nums[i]时,TreeSet中始终只保留nums[i-k]到nums[i-1] )。

这样,只要存在f或者c,就存在满足条件的值,因为TreeSet中元素的坐标都满足和i的约束关系,直接返回。
若不存在,则把nums[i]加入TreeSet,删去nums[i-k](因为下一个要访问的是nums[i+1],nums[i-k]与nums[i+1]的坐标约束不满足),继续遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Solution {
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
if (nums == null || nums.length == 0 || k <= 0) {
return false;
}

final TreeSet<Integer> values = new TreeSet<>();
for (int i = 0; i < nums.length; i++) {

final Integer floor = values.floor(nums[i] + t); //set中是否有满足 小于或等于 nums[i]+t 的数字
final Integer ceil = values.ceiling(nums[i] - t); //set中是否有满足 大于或等于 nums[i]-t 的数字

//如果有这样的数字,且TreeSet中元素坐标都与i满足约束条件,那么就有满足题意的元素

if ((floor != null && floor >= nums[ind])
|| (ceil != null && ceil <= nums[ind])) {
return true;
}

values.add(nums[i]);
//保证TreeSet中的坐标和i的差都小于等于k
if (i >= k) {
values.remove(nums[i - k]);
}
}

return false;
}
}

只用位运算不用算术运算实现整数的加减乘除运算

给定两个32位整数a和b,可正,可负,可0,不能使用算术运算符,分别实现a和b的加减乘除运算
不考虑溢出

用位运算实现加法

如果不考虑进位,a^b就是正确结果。
在只算进位的情况下,也就是只考虑a加b的过程中进位产生的值是什么,结果就是(a&b)<<1,因为在第i位只有1与1相加才会产生进位。

把完全不考虑进位的相加值与只考虑进位的产生值再相加,就是最终的结果,也就是说,一直重复这样的过程,直到进位产生的值完全消失,说明所有的过程都加完了。

1
2
3
4
5
6
7
8
public int add(int a, int b){
int sum=a;
while(b!=0){
sum = a^b;
b = (a&b)<<1;
a = sum;
}
}

用位运算实现减法运算

实现a-b只要实现a+(-b)即可,根据二进制数在机器中表达的规则,得到一个数的相反数,就是这个数的二进制数表达取反加1的结果,,得到b的相反数-b后,再与a进行加法

1
2
3
4
5
6
7
int negNum(int n){
return add(~n, 1);
}

public int minus(int a, int b){
return add(a, negNum(b));
}

用位运算实现乘法运算

a×b的结果可以写成:a×2^0×b0+a×2^1×b1+…+a×2^31×b31
其中,bi为0或1代表整数b的二进制数表达中第i位的值,

举例:a=22=000010110,b=13=000001101,res=0
b的最右侧为1,所以res=res+a,同时b右移一位,a左移一位(左移一位即乘2)
a=000101100, b=000000110
b最右侧为0,所以res不变,b右移一位,a左移一位
a=001011000,b=000000011
b最右侧为1,res=res+a,同时b右移一位,a左移一位。
a=010110000,b=000000001
b最右侧为1,res=res+a,同时b右移一位,a左移一位。
a=101100000,b=000000000
此时b为0,过程停止,返回res=100011110,即286
不管a和b是正,负,还是0,上述过程都是对的,因为都满足a×2^0×b0+a×2^1×b1+…+a×2^31×b31

1
2
3
4
5
6
7
8
9
10
11
public int multi(int a, int b){
int res = 0;
while(b!=0){
if((b&1)!=0){
res = add(res,a);
}
a << =1;
b >>> = 1;
}
return res;
}

用位运算实现除法运算,其实就是乘法的逆运算。

其实就是找到a能包含的最大部分(指b* 2^x )然后让a减去这个最大部分,再让剩下的a找到次大部分,并依次找下去。以上过程只适用于a和b都不是负数的时候,所以,如果a和b中有一个为负数或都为负数时,可以先把a和b转成正数,计算完后再看res的真实符号是什么就可以。

1
2
3
4
5
6
7
8
9
10
11
12
public int div(int a, int b){
int x = isNeg(a)?negNum(a):a;
int y = isNeg(b)?negNum(b):b;
int res = 0;
for(int i=31; i > -1; i= minus(i, 1)){
if((x>>i)>=y){
res |= (1<<i);
x = minus(x, y<<i);
}
}
return isNeg(a) ^ isNeg(b)?negNum(res):res;
}

上述方法可以计算绝大多数情况,但32位整数最小值为-2147483648,最小值的绝对值比最大值的绝对值大1,所以,如果a或b等于最小值,是转不成相应正数的,总结如下:
1.如果a和b都不为最小值,直接使用上述过程,返回div(a,b)
2.如果a和b都为最小值,a/b的结果为1,直接返回1
3.如果a不为最小值,b为最小值,a/b结果为0,直接 返回0
4.如果a为最小值,b不为最小值,只能把最小值增加一点,计算出一个结果,然后根据这个结果修正一下,得到最终结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public int divide(int a, int b){
if (b==0) {
throw new RuntimeException("divisor is 0");
}
if(a==Integer.MIN_VALUE && b==Integer.MIN_VALUE){
return 1;
}else if(b==Integer.MIN_VALUE){
return 0;
}else if(a==Integer.MIN_VALUE){
int res = div(add(a, 1), b);
return add(res, div(minus(a, multi(res, b)), b));
}else{
return div(a, b);
}

}

简介
在orm框架中,比如hibernate和mybatis都可以设置关联对象,比如user对象关联dept
假如查询出n个user,那么需要做n次查询dept,查询user是一次select,查询user关联的
dept,是n次,所以是n+1问题,其实叫1+n更为合理一些。

mybatis配置
UserMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14

<resultMap id="BaseResultMap" type="testmaven.entity.User">

<id column="id" jdbcType="INTEGER" property="id" />

<result column="name" jdbcType="VARCHAR" property="name" />

<result column="age" jdbcType="INTEGER" property="age" />

<result column="dept_id" jdbcType="INTEGER" property="deptId" />

<association property="dept" column="dept_id" fetchType="eager" select="testmaven.mapper.DeptMapper.selectByPrimaryKey" ></association>

</resultMap>

DeptMapper.xml

1
2
3
4
5
6
7
8
9
10
11
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >

select

<include refid="Base_Column_List" />

from dept

where id = #{id,jdbcType=INTEGER}

</select>

我们可以看到user通过association中的dept_id关联了dept,查询user后,比如查询到4个user,那么会执行4次查询dept;

测试

1
List<User> list = userMapper.selectByExample(null);

打印jdbc log我们能看到,查询到4个user,然后执行了4次查询dept
select1

1+n带来的问题
查询主数据,是1次查询,查询出n条记录;根据这n条主记录,查询从记录,共需要n次,所以叫数据库1+n问题;这样会带来性能问题,比如,查询到的n条记录,我可能只用到其中1条,但是也执行了n次从记录查询,这是不合理的。为了解决这个问题,出现了懒加载,懒加载就是用到的时候再查询;我们设置association元素中的fetchType fetchType=lazy

1
2
<association property="dept" column="dept_id" fetchType="lazy" select="testmaven.mapper.DeptMapper.selectByPrimaryKey" ></association>
我们再做测试
1
2
3
4
5
6
7
List<User> list = userMapper.selectByExample(null);

User u = list.get(0);

System.out.println(u.getClass());

System.out.println(u.getName());

jdbc log
jdbc log

懒加载 减少了性能消耗,一定程度上缓解了1+n带来的性能问题

总结
1+n问题是什么?应该怎样解决?

1+n是执行一次查询获取n条主数据后,由于关联引起的执行n次查询从数据;它带来了性能问题;一般来说,通过懒加载 可以部分缓解1+n带来的性能问题

mybatis是先处理sql,把sql执行完后把结果表映射到resultMap或者resultType上

如下面的:

一个project对应一个user

1
2
3
4
5
6
7
public class Project {
int id;
String name;
User user;
ItemState state;
Set<User> signer;
}
1
2
3
4
5
public class User{
public int id;
public String username;
public String password;
}

表为:

1
2
3
4
5
6
create table project(
id int primary key auto_increment,
name varchar(50),
user_id int,
state enum('created', 'planned', 'started', 'finished'),
)engine=innoDB, charset=utf8;

user表:
+———-+————-+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+———-+————-+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(20) | NO | MUL | NULL | |
| password | varchar(18) | NO | | NULL | |
+———-+————-+——+—–+———+—————-+

当给定一个userid,要查询其对应的project及其对应的user。
首先如果结果集不是简单的POJO(POJO其属性不含其它bean的类型),就不能简单使用resultType,因为复杂映射映射不出来。要使用resultMap,且使用association和collection

下面称association和collection为“复杂映射”

所有的复杂映射都是根据结果表进行映射

如要使用userid去查询project及其对应的user,有以下办法:

用法一:

ProjectMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<mapper namespace="com.ljy.inspector.dao.ProjectDao">

<resultMap id="projectMap" type="com.ljy.inspector.entity.Project">
<id column="id" property="id"/>
<result column="state" property="state" typeHandler="com.ljy.inspector.type_handler.ItemStateHandler"/>
<association column="user_id" property="user" select="com.ljy.inspector.dao.UserDao.findById">
</association>
</resultMap>

<select id="findByUserId" resultMap="projectMap">
select
from project as p
<where>
p.user_id = #{userId}
</where>
</select>
</mapper>

UserMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<mapper namespace="com.ljy.inspector.dao.UserDao">

<resultMap id="UserMap" type="com.ljy.inspector.entity.User">
<id column="id" property="id" jdbcType="INTEGER"/>
<result column="username" property="username" jdbcType="VARCHAR"/>
<result column="password" property="password" jdbcType="VARCHAR"/>
</resultMap>

<select id="findById" resultType="com.ljy.inspector.entity.User">
SELECT * FROM user AS u
<where>
u.id = #{id}
</where>
</select>

</mapper>

association中嵌套select语句,这样的执行顺序是:先执行上层select找出resultMap中除了复杂映射的其他部分,默认开启部分自动映射,因此其他部分会被自动映射,除了有的映射不到,需要使用类型处理器,如ItemState。接着执行association中的select语句,并且将user_id作为参数传入findById,user_id就会被当做findById中的 #{id} (即使名字不一样,但就是这么做的)
这样做的话,会得到正确的结果:

1
2
3
4
5
6
7
8
9
10
{
"id": 10,
"name": "czxczc",
"state": "CREATED",
"user": {
"id": 2,
"username": "123",
"password": "11111111"
},
}

这样做相当于把查询分了两步:
1.在project中先select查询user_id为给定值的所有元组(行),把能自动映射或者指定了类型处理器的属性先映射到Project对象上

2.再根据上述元组中的user_id属性去select查询user表中id为userid中的行,并把查询结果集映射到User对象上,并且把这个User对象赋给上面的Project对象的user属性。

整体就是两步select,根据第一步查询出的结果的某个字段去进行第二步查询。
先构建出第一步查询的结果对象obj1,然后把第二步查询的结果对象obj2赋给obj1对应的属性。

(先得到结果表1,对其进行映射,再使用其中的某字段得到结果表2,再对结果表2进行映射,将映射结果2嵌入映射结果1的属性中。整个过程产生了两张结果表,因为有两个select)

用法二:

ProjectMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<mapper namespace="com.ljy.inspector.dao.ProjectDao">

<resultMap id="projectMap" type="com.ljy.inspector.entity.Project">
<id column="id" property="id"/>
<result column="state" property="state" typeHandler="com.ljy.inspector.type_handler.ItemStateHandler"/>
<association property="user" javaType="com.ljy.inspector.entity.User">
<id column="u_id" property="id"/>
<result column="username" property="username"/>
</association>
</resultMap>

<sql id="base_user">
p.*, u.id as u_id, u.username, u.password
</sql>

<select id="findByUserId" resultMap="projectMap">
select
<include refid="base_user"/>
from project as p,user as u
<where>
p.user_id = #{userId}
and
u.id = #{userId}
</where>
</select>
</mapper>

结果为:

1
2
3
4
5
6
7
8
9
10
{
"id": 10,
"name": null,
"state": "CREATED",
"user": {
"id": 2,
"username": "123",
"password": null
},
}

这种方法是执行一次select查询,一次查询两个表,然后再对这个联合结果表进行映射,这种方式完全是按照sql的思想理念去做的,只有最后才是对联合结果表进行一次映射。
这种方式的resultMap会自行关闭自动映射,原因在于结果表只有一张,如果两张表中有名称相同的字段,会产生歧义(如project的叫id,user的也叫id)。在sql查询中,这种情况不会有问题,结果表可以有两个列都叫id,但是对于映射来说列名是唯一标签,所以会产生歧义。所以这种情况下,要对有歧义的列名起别名,如上面的sql标签,将user中的id别名为u_id,那么最终产生的结果表中,只有一列叫id,即project.id,而user.id的这一列叫u_id,所以在association中列名叫做u_id。
这样得出一张结果表后,先整体映射为指定的对象类型,再把其中要映射为属性或者需要处理的列去映射成相应的属性。而把需要映射为association的列摘出来,根据提供的对象类型把它们映射成相应的对象,赋给上层的对象的属性

方法二还有一种做法是指定association的resultMap属性,两者其实是一样的。本质上都是从一张结果表中取结果列取映射。

所以对于association需要注意的点:

1.如果使用了select属性,则本质上要进行两次查询,得到两张结果表,因为是从不同的表中查询(相同也无所谓)且每次只查询一个表,所以二者互不干涉,所以不存在字段歧义的问题,自动映射会开启。
2.如果使用resultMap属性,或者association本身当做一个resultMap,则本质是对多张表的一次联合查询,只产生一张结果表。然后对这张结果表一次进行映射。可能存在字段歧义问题,自动映射会关闭(如果没有指明映射关系的字段会得到空值)。此时要把产生歧义的字段起别名,并且把所有需要映射的字段都显式写出来。

所有的mybatis的resultMap,不管其结构有多复杂,本质都是执行完sql后对结果表再进行映射(归根结底要很清晰执行完sql语句后结果表是什么样子),其中的column的名称是结果表的column名称而不是原表的colum名称(虽然不起别名的话二者相同)。只要明确这一点,很多问题就能迎刃而解。

MyBatis 是否支持延迟加载(懒加载)?延迟加载的原理是什么?

延迟加载的原理的是调用的时候触发加载,而不是在初始化的时候就加载信息。比如调用 a. getB(). getName(),这个时候发现 a. getB() 的值为 null,此时会单独触发事先保存好的关联 B 对象的 SQL,先查询出来 B,然后再调用 a. setB(b),而这时候再调用 a. getB(). getName() 就有值了,这就是延迟加载的基本原理。其本质是将整个查询的结果包装成一个代理,当要进行属性加载时,调用其被代理的一些方法,再从数据库中查
MyBatis 支持延迟加载,设置 lazyLoadingEnabled=true 即可(全局懒加载)。或者单独属性的懒加载(fetchType=lazy)

如果使用了config文件
则设置:

1
2
3
4
<settings>
    <setting name="lazyLoadingEnabled" value="true”/>
    ...
</settings>

如果是在application.properties中加载配置,则使用:

1
mybatis.configuration.lazy-loading-enabled=true

注意,二者只能有一个!

1
2
3
4
注意!如果在application.properties中指定了配置文件,那就不能再在application.properties中设置任何mybatis的配置,
也就是不能再设置任何以 “mybatis.configuration.XXX” 开头的属性
否则会报错:
Property 'configuration' and 'configLocation' can not specified with together

可以在resultMap中具体的association或者collection中可以用fetchType=lazy/eager 来覆盖掉全局的懒加载属性

对于fetchType的使用,可以不开启lazyLoadingEnabled的情况下单独使用,也是生效的,也可以在开启lazyLoadingEnabled的情况下使用,这样设置了fetchType的属性的加载策略会忽略全局的加载策略。

对于级联查询,就可以单独使用fetchType=lazy的方式来解决N+1的问题

如果设置了懒加载(不论是fetchtype还是lazyLoadingEnabled),那么实体类会产生了一个代理,代理中有一个handler属性,这个属性类没有实现序列化接口,无法被加到json中,所以需要在类上注解@JsonIgnoreProperties(value = {“handler”})来把它忽略掉
如果不加这个注解,会报如下错:

Could not write JSON: No serializer found for class org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory$EnhancedResultObjectProxyImpl and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS);

有时候懒加载看起来没起作用,实际上是一些方法被暗地调用导致懒属性被加载
lazyLoadTriggerMethods属性可以设置当调用对象的某些方法可能会导致该对象属性的懒加载被触发,

默认值:equals,clone,hashCode,toString(也就是说,当调用对象的这些方法时,对象的懒属性都会被加载)
配置文件中加入了这个设置:

1
2
<setting name="lazyLoadTriggerMethods" value="”/>  
<!-- 表示调用任何方法都不会触发懒加载,除非直接调用该对象的被懒加载的属性及其方法(如A.b,或者A.b.getName() 这样) -->

附上文档上的说明:
lazyLoadTriggerMethods:指定哪个对象的方法触发一次延迟加载。
比如,当我打印对象时,由于触发了 toString 方法,所以触发了一次懒加载,该对象的所有级联属性就会被加载

陷阱:
有的时候这些方法可能会被框架在无意中自动触发而导致懒属性被加载(尤其是toString和hashCode,很多时候不知道在哪就被调用了,结果看起来好像是懒加载没起作用),所以如果不是很明确地需要触发加载的方法时,最好把lazyLoadTriggerMethods这个属性设为空,或者设置为自己能控制调用的方法。

关于对象转化成Json传输时懒加载不起作用的问题
在设置了懒加载后,在本地验证的时候,懒加载确实是起作用的,但是,当把对象转为json传输的时候,由于要调用相应属性来完成序列化,所以仍然是要对其属性进行加载的。比如

1
2
3
4
Class A{
List<B> b;
...
}
1
2
3
<resultMap type = A>
...
    <collection …property=b ofType=B fetchType=lazy .../>

在本地跑单元测试时,可以发现a对象的b属性只要不调用,而且不去调用A对象可能会触发懒加载的方法,则b属性始终为空,除非像调用了类似a.b,即要用到相应的属性时,才会触发读数据库加载数据。但是呢,如果把对象a当做json去传输时,它的b对象也是会被加载的,因为在序列化时,会调用相应的属性及其方法,这时就会触发加载去数据库读b的数据。那么想让a对象传输时不加载且不传输b的话,解决办法如下:

首先是要设置懒加载,即要么开启全局的懒加载lazyLoadingEnabled”设置,要么单独对A的属性b设置fetchType=lazy
在A的类上标注@JsonIgnoreProperties(value = {“handler”, “b”}) 这个意思是json序列化时要忽略掉哪些属性,首先handler是必须要忽略的,因为懒加载后对象其实是一个代理,被自动加上了handler属性,它没有实现序列化接口,所以要排除;而且把b属性给排除掉,表明序列化时会忽略b属性,那么序列化时就不会调用任何关于b的方法了。这样的话,在客户端得到的A对象中是没有b属性的,而且查看sql的调用,发现也是没有去查找B表的。

 但要注意的是,如果不开启懒加载开关,只是设置了@JsonIgnoreProperties,那么其实相应的属性还是会被加载(即会去查相应属性的表),但查到属性后,不把它序列化到json中,所以客户端还是看不到这个属性,但事实上对于数据库来说是已经查了的。

而如果不标注忽略属性,仅仅把想要不序列化的对象设置为transient,是不起作用的,还是会被序列化,这里猜测应该是序列化为json时和普遍意义上的序列化还是有所差别的,还是对调用对应属性的方法,这样就还是会触发懒加载

Dispatcher接收到了客户的请求,遍历所有的HandlerMapping集合,找出被@Controller注解的bean和被@Request注解的类和方法,根据请求的路径,参数等一系列条件,找到符合条件的HandlerMapping,并得到Handler执行链,其中包括拦截器和controller方法,是一条拦截+处理链。

dispatcherServlet让把请求放进入链中,先执行所有拦截器的preHandle方法,执行完后准备去真正的handle方法(即controller的对应方法)。首先使用解析类解析handle方法的参数,包括@RequestParam、@RequestBody、@PathVariable注解的参数,从请求中提取出这些参数的值,并把它们连同请求一起交给handle方法,在handle中执行真正的业务逻辑。在handle方法处理结束之后,再使用一些处理类处理handle方法的返回值,处理完后合并进响应,再去执行所有拦截器的postHandle方法。之后把返回值交给dispatcherServlet,如果返回的是一个ModelAndView,则把它交给视图解析器去解析,由它解析出相应的视图(即html、jsp、xml等)并写到响应中,此时还要执行所有拦截器的afterCompletion再做处理。

然后,把最终处理完成的响应返回给客户。

另:过滤器在dispatcherServlet的前面,如果有过滤器,则请求进来时先进过滤器链,再进dispatcherServlet。响应走时先通过dispatcherServlet,然后再通过过滤器链
在这里插入图片描述

springboot 静态资源无法访问

在一个项目中WebMvcConfigurationSupport只能存在一个,多个的时候,只有一个会生效。(按名称排序后第一个的)

1.SpringBoot 的 @EnableAutoConfiguration 会启用自动配置类

2.WebMvcAutoConfiguration,该类配置了一些默认的静态资源映射

  • 自动映射 localhost:8080/** 为以下路径
    classpath:/resources/
    classpath:/static/
    classpath:/public/
    classpath:/META-INF/resources/
    1. 自动映射 localhost:8080/webjars/** 为以下路径
      classpath:/META-INF/resources/webjars/

此时,我们不需要多做什么,只要将静态资源放入 src/main/resources 目录下的 resources、static 或 public 文件夹下,即可通过 url 定位相关资源,例如 localhost:8080/index.html 可定位至 src/main/resources/static/index.html

但是!
一旦使用继承了WebMvcConfigurationSupport或者WebMvcConfigurerAdapter的自定义配置类,即使没有重写addResourceHandlers方法,默认配置都会被不被采用。如果要采用原本默认的配置,则需要webmvc配置类中重写如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class GoWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//配置静态资源处理
registry.addResourceHandler("/**")
.addResourceLocations("resources/", "static/", "public/",
"META-INF/resources/")
.addResourceLocations("classpath:resources/", "classpath:static/",
"classpath:public/", "classpath:META-INF/resources/")
.addResourceLocations("file:///tmp/webapps/");
}
}

经过试验得证:如果不使用WebMvcConfigurationSupport,则使用application.properties中的默认配置
如果重写了application.properties中的配置,则使用重写后的配置,如:

1
2
3
spring.resources.static-locations=resources/, static/, public/, \ META-INF/resources/, resources/, classpath:static/, public/, classpath:META-INF/resources/, \file:///tmp/webapps/

spring.mvc.static-path-pattern=/**

一旦使用了WebMvcConfigurationSupport(或WebMvcConfigurerAdapter)那么必须要重写addResourceHandlers来配置资源映射,此时application.properties中关于静态资源访问的配置将失效(除非对url写了响应的controller来处理,那是另一回事)

当添加了静态资源后,无法立即访问的问题

如果访问项目中的静态资源,访问的所有的东西其实都是target目录中的,这是经过编译的,如果对项目目录下的静态资源进行修改(增删改),由于target下的东西没有受影响,所以没法立即生效,比如往/resouces/static下增加一张图片,通过url无法立即访问到,必须重启项目。其实重启就是让资源文件经过编译(其实资源文件根本不编译)后,加到target中,那么这样就很麻烦,要是想对一个静态资源修改后能立即通过url访问到要如何做呢?
解决办法将静态资源放在非项目类路径下即可,例如 “file:///Users/mytest/inspector/independent_resources/“);,并且在资源处理器中加上路径即可。
file:// 后面跟的必须是文件系统的绝对路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
registry.addResourceHandler("/**”)
//**********/
.addResourceLocations("resources/", "static/", "public/",
"META-INF/resources/")
.addResourceLocations("classpath:resources/", "classpath:static/",
"classpath:public/", "classpath:META-INF/resources/")
//**********/
//如果要让添加的静态资源立即生效,则不能把它放到项目资源里面,应该放在外面,比如下面的目录
//这样一旦静态资源添加到这个目录,则直接从该位置获取,而不是从编译的target位置获取
.addResourceLocations("file:///Users/mytest/inspector/independent_resources/");

/**********之间的是项目类的路径,不用管,最下面的是非项目类的路径
这样的话,在项目运行时,往/Users/mytest/inspector/independent_resources/目录下添加一个111.png后,在浏览器中通过localhost:8080/111.png就可访问到这个图片了。
如果不添加这个,在项目运行时,往项目的src/main/resources 下添加一个111.png后,不能立即通过浏览器访问到这个图片,因为项目类的所有资源和文件必须编译为target版本后才对外服务,而添加资源后不重启项目无法重新编译,target中的东西没有更新。

https://blog.csdn.net/rui18300072030/article/details/74923580

因此,需要修改后立即生效的文件,尤其是非代码文件(除去html,css这种)最好是放在项目类的外部,放在文件系统的绝对路径下。如果文件必须放在项目中,但又必须得立即生效,
那么对项目中的同一个资源目录,即添加其相对路径,又添加其绝对路径:

如果文件必须放在项目中,但又必须得立即生效,那么用下面的方法:
对项目中的同一个资源目录,即添加其相对路径,又添加其绝对路径:

1
2
3
registry.addResourceHandler("/**")
.addResourceLocations("classpath:static/")
.addResourceLocations("file:///Users/mytest/inspector/src/main/resources/static/");

上面两个路径其实是同一个,但通过上面相对路径访问的是target中的东西。在运行时添加一个文件后,target没有更新,通过相对路径就访问不到,但实际上文件系统是更新了,所以用下面的绝对路径直接访问文件系统中的文件就可。