发布时间:2021-04-17作者:laosun阅读(1407)
Springboot mybatis 懒加载测试
使用idea测试mybatis懒加载果真有坑
为了映射对象中复杂的关联对象, 我们在 ResultMap 中可以配置 association 和 collection. 这两者的实现方式可以通过
再请求一次 select (Nested Select)
通过 join 将所有的属性读取出来 (Nested Results).
但有时候我们并不会使用到对象中所有的属性, 所以这些额外的从数据库拉来的数据就浪费了. 对于通过 select 实现的方式我们可以使用懒加载来提升效率。
配置
lazyLoadingEnabled
默认为 false
, 也就是不使用懒加载. 所以如果 association 和 collection 使用了 select
, 那么 MyBatis 会一次性执行所有的查询. 如果 accociation 和 collection 中的 fetchType
指定为 lazy
, 那么即使 lazyLoadingEnabled
为 false, MyBatis 也会使用懒加载.
Java 配置中 @One
和 @Many
的 fetchType
支持三个值: LAZY
, EAGER
, DEFAULT
, 其中 DEFAULT
的意思是跟随全局设置即 lazyLoadingEnabled
.
默认为 true
, 也就是说当你开启了懒加载之后, 只要调用返回的对象中的 任何一个方法, 那么 MyBatis 就会加载所有的懒加载的属性, 即执行你配置的 select
语句.
默认值为 equals,clone,hashCode,toString
, 当你调用这几个方法时, MyBatis 会加载所有懒加载的属性.
默认为 JAVASSIST
(MyBatis 3.3 or above). 懒加载是通过字节码增强实现的, 3.3 以前是通过 cglib 实现的, 3.3 之后包括 3.3 是使用 javassist 实现的.
前面提到了懒加载是通过字节码增强实现的, 所以 MyBatis 会动态代理你的类, 然后根据调用的方法名来判断是否需要加载属性.
相关类的实现有两个, 分别对应 javassist 和 cglib 的版本:
javassist: org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory
cglib: org.apache.ibatis.executor.loader.cglib.CglibProxyFactory
两个类在方法拦截时的处理逻辑是一样的, 我们挑其中一个来看 (javassist 的):
@Overridepublic Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable { final String methodName = method.getName(); try { synchronized (lazyLoader) { if (WRITE_REPLACE_METHOD.equals(methodName)) { // 这段不懂是处理什么情况的, 没细看 Object original = null; if (constructorArgTypes.isEmpty()) { original = objectFactory.create(type); } else { original = objectFactory.create(type, constructorArgTypes, constructorArgs); } PropertyCopier.copyBeanProperties(type, enhanced, original); if (lazyLoader.size() > 0) { return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs); } else { return original; } } else { // 这段是重点 if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) { if (aggressive || lazyLoadTriggerMethods.contains(methodName)) { // 如果开启了 aggressive 或者调用的是 lazyLoadTriggerMethods 中设置的方法, 则加载所有属性 lazyLoader.loadAll(); } else if (PropertyNamer.isProperty(methodName)) { // 判断方法是否是以 get, set, is 开头 final String property = PropertyNamer.methodToProperty(methodName); // 方法名转换成属性名 if (lazyLoader.hasLoader(property)) { lazyLoader.load(property); // 加载 } } } } } return methodProxy.invoke(enhanced, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); }}
项目中开启了懒加载后, 准备测试下是否真的启用了懒加载, 于是打印日志看了下对应的属性在调用相应的方法前是否是 null
. 结果发现属性每次都被加载了, 以为 MyBatis 能拦截属性的直接访问或者生成代理类的时候会分析相应字节码, 如果发现字节码中有属性的访问就在访问该方法时加载属性, 查了下 cglib 和 javassist 文档感觉并没有相应的功能啊.
最后发现原来是因为 aggressiveLazyLoading
默认是开启的, 因为我访问了对象的其他方法所以属性被加载了.
千万不要用下断点的方式查看对应的属性有没有被加载, 可能 是因为 IDEA 在 debug 的时候会调用 lazyLoadTriggerMethods
中的方法的, 所以导致属性被加载.
MyBatis 生成的代理类会多出一个 handler
的属性, 从而导致 Jackson 序列化失败, 可以通过在类上添加注解来忽略该属性:
@JsonIgnoreProperties("handler") public class MyDO {}
以上文章转载自 https://yoncise.com/2016/11/05/MyBatis-%E8%AE%B0%E5%BD%95%E4%BA%8C-lazy-loading/
具体的测试方式,请看以下mybatis xml:
第一种方式:直接在mybatis 中使用 fetchType="lazy"
第二种方式,配置全局懒加载:
#开启mybatis延迟加载 #全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。 mybatis.configuration.lazy-loading-enabled=true mybatis-plus.configuration.lazy-loading-enabled=true #当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。 mybatis.configuration.aggressive-lazy-loading=false mybatis-plus.configuration.aggressive-lazy-loading=false #懒加载情况下报“No serializer found for class org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory$Enha“ #和@JsonIgnoreProperties("handler")类似 spring.jackson.serialization.fail-on-empty-beans=false
测试代码(以下代码和配置的名称有修改,未经测试):
#MyBatis 生成的代理类会多出一个 handler 的属性, 从而导致 Jackson 序列化失败, 可以通过在类上添加注解来忽略该属性 @JsonIgnoreProperties("handler") public class Users implements Serializable { private Integer id; private String name; private Orders orders;//假如一个用户仅有一笔订单 set get... }
public class Orders implements Serializable { private Integer userId; private String orderNo; set get... }
<!-- 懒加载 --> <resultMap id="LAZY_TEST" type="com.xxx.entity.Users" extends="BaseResultMap"> <collection property="orders" ofType="com.xxx.entity.Orders" column="id" select="twoSelect" fetchType="lazy"> </collection> </resultMap> <!-- 根据主键id查找用户 --> <select id="findByIdOfLazy" parameterType="java.lang.Integer" resultMap="LAZY_TEST"> select * from t_users where id = #{id} </select> <!-- 根据用户id查找订单 --> <select id="twoSelect" parameterType="java.lang.Integer" resultType="com.xxx.entity.Orders"> select `user_id`, `order_no` from t_orders where user_id = #{id} </select>
@RequestMapping(value = "/testLazy") public Users testLazy(Integer id) { Users users = usersMapper.findByIdOfLazy(id); logger.info("------------");#在idea中打断点会导致order属性也加载,所以这里输出一些日志来区分 users.getOrders(); return users; }
输出日志:
Preparing: select * from t_users where id = ? Parameters: 1(Integer) Total: 1 ------------ Preparing: select `user_id`, `order_no` from t_orders where id = ? Parameters: 1(Integer) Total: 1
版权属于: 技术客
原文地址: https://www.sunjs.com/article/detail/baedac44c03b4f0298c019295974a6a1.html
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
关键字: Java 源码 springboot mybatis