0%

mybatis的懒加载

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时和普遍意义上的序列化还是有所差别的,还是对调用对应属性的方法,这样就还是会触发懒加载