参考博客:
Mybatis延迟加载的实现以及使用场景_ashui811的博客-CSDN博客_mybatis延迟加载使用场景
Mybatis 之 延迟加载_悠然予夏的博客-CSDN博客_mybatis延迟加载
1、什么是延迟加载
问题
在开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的订单信息。此时就是我们所说的延迟加载。
举个栗子
* 在一对多中,当我们有一个用户,它有个100个订单
在查询用户的时候,要不要把关联的订单查出来?
在查询订单的时候,要不要把关联的用户查出来?
* 回答
在查询用户时,用户下的订单应该是,什么时候用,什么时候查询。
在查询订单时,订单所属的用户信息应该是随着订单一起查询出来。
延迟加载
就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载。
优点:
先从单表查询,需要时再从关联表去关联查询,⼤⼤提⾼数据库性能,因为查询单表要比关联查询多张表速度要快。
缺点:
因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成⽤户等待时间变长,造成用户体验下降。
在多表中:
一对多,多对多:通常情况下采用延迟加载
一对一(多对一):通常情况下采用立即加载
注意:
延迟加载是基于嵌套查询来实现的
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窗体中已经触发了延迟加载对象的方法