mybatis延迟加载总结
1、延迟加载
就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载。
- 优点:先从单表查询,需要时再从关联表去关联查询,⼤⼤提⾼数据库性能,因为查询单表要比关联查询多张表速度要快。
- 缺点:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成⽤户等待时间变长,造成用户体验下降。
在多表中:
- 一对多,多对多:通常情况下采用延迟加载
- 一对一(多对一):通常情况下采用立即加载
注意:延迟加载是基于嵌套查询来实现的
2、实现
局部延迟加载,在association和collection标签中都有⼀个fetchType属性,通过修改它的值,可以修改局部的加载策略。
<!-- 开启⼀对多 延迟加载 --> <resultMap id="userMap" type="user"> <id column="id" property="id"></id> <result column="username" property="username"></result> <result column="password" property="password"></result> <result column="birthday" property="birthday"></result> <!-- fetchType="lazy" 懒加载策略 fetchType="eager" ⽴即加载策略 --> <collection property="orderList" ofType="order" column="id" select="com.lagou.dao.OrderMapper.findByUid" fetchType="lazy"> </collection> </resultMap> <select id="findAll" resultMap="userMap"> SELECT * FROM `user` </select>
全局延迟加载
在Mybatis的核⼼配置⽂件中可以使⽤setting标签修改全局的加载策略。
<settings> <!--开启全局延迟加载功能--> <setting name="lazyLoadingEnabled" value="true"/> </settings>
注意:局部的加载策略的优先级高于全局的加载策略
<!-- 关闭⼀对⼀ 延迟加载 --> <resultMap id="orderMap" type="order"> <id column="id" property="id"></id> <result column="ordertime" property="ordertime"></result> <result column="total" property="total"></result> <!-- fetchType="lazy" 懒加载策略 fetchType="eager" ⽴即加载策略 --> <association property="user" column="uid" javaType="user" select="com.lagou.dao.UserMapper.findById" fetchType="eager"> </association> </resultMap> <select id="findAll" resultMap="orderMap"> SELECT * from orders </select>
3、延迟加载原理实现
它的原理是,使用 CGLIB 或 Javassist( 默认 ) 创建目标对象的代理对象。当调用代理对象的延迟加载属性的 getting 方法时,进入拦截器方法。比如调⽤ a.getB().getName() 方法,进入拦截器的invoke(...) 方法,发现 a.getB() 需要延迟加载时,那么就会单独发送事先保存好的查询关联 B对象的 SQL ,把 B 查询上来,然后调用a.setB(b) 方法,于是 a 对象 b 属性就有值了,接着完成a.getB().getName() 方法的调用。这就是延迟加载的基本原理。
总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定方法,执行数据加载。
4、延迟加载原理(源码剖析)
MyBatis延迟加载主要使用:Javassist,Cglib实现,类图展示:
Setting 配置加载:
public class Configuration { /** * aggressiveLazyLoading: * 当开启时,任何⽅法的调⽤都会加载该对象的所有属性。否则,每个属性会按需加载(参考lazyLoadTriggerMethods). * 默认为true */ protected boolean aggressiveLazyLoading; /** * 延迟加载触发⽅法 */ protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[]{"equals", "clone", "hashCode", "toString" })); /** * 是否开启延迟加载 */ protected boolean lazyLoadingEnabled = false; /** * 默认使⽤Javassist代理⼯⼚ * * @param proxyFactory */ public void setProxyFactory(ProxyFactory proxyFactory) { if (proxyFactory == null) { proxyFactory = new JavassistProxyFactory(); } this.proxyFactory = proxyFactory; } //省略... }
延迟加载代理对象创建
Mybatis的查询结果是由ResultSetHandler接口的handleResultSets()方法处理的。 ResultSetHandler接口只有一个实现,DefaultResultSetHandler,接下来看下延迟加载相关的一个核心的方法。
// 创建映射后的结果对象 private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { // useConstructorMappings ,表示是否使用构造方法创建该结果对象。此处将其重置 this.useConstructorMappings = false; // reset previous mapping result final List<Class<?>> constructorArgTypes = new ArrayList<>(); // 记录使用的构造方法的参数类型的数组 final List<Object> constructorArgs = new ArrayList<>(); // 记录使用的构造方法的参数值的数组 // 创建映射后的结果对象 Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { // 如果有内嵌的查询,并且开启延迟加载,则创建结果对象的代理对象 final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { // issue gcode #109 && issue #149 if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { // 创建延迟加载代理对象 resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); break; } } } // 判断是否使用构造方法创建该结果对象 this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result return resultObject; }
默认采用javassistProxy进行代理对象的创建
注意事项:IDEA调试问题 当配置aggressiveLazyLoading=true,在使用IDEA进行调试的时候,如果断点打到代理执行逻辑当中,你会发现延迟加载的代码永远都不能进入,总是会被提前执行。 主要产生的原因在aggressiveLazyLoading,因为在调试的时候,IDEA的Debuger窗体中已经触发了延迟加载对象的方法。