MyBatis源码剖析-延迟加载
# 1. 延迟加载相关概念
# 1.1 什么是延迟加载?
举个例子:如果查询员工信息并且关联查询员工所属部门信息。如果先查询员工单信息即可满足要求,当我们需要查询部门信息时再查询用部门信息。把对部门信息的按需去查询就是延迟加载。
所以延迟加载即先从单表查询、需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
我们来对比一下:
-- 关联查询
SELECT e.*,d.* FROM dept d inner join emp e on d.DEPTNO = e.DEPTNO;
-- 单表分次查询
select * from emp;
select * from dept where deptno = '查询出员工的部门编号';
2
3
4
5
6
这就比较直观了,也就是说,我把关联查询分两次来做,而不是一次性查出所有的。第一步只查询单表emp,必然会查出emp中的一个deptno字段,然后我再根据这个deptno查dept表,也是单表查询。下面来总结一下如何使用这个延迟加载。
# 1.2 实现延迟加载
前面博文中总结了resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),其实association和collection还具备延迟加载的功能,这里我就拿association来说明,collection和association使用的方法都是一样的。
在association和collection标签中都有一个fetchType属性,通过修改它的值,可以修改局部的加载策略:
<resultMap id="empDeptMap" type="emp">
<result column="empno" property="empno"></result>
<result column="ename" property="ename"></result>
<result column="job" property="job"></result>
<result column="mgr" property="mgr"></result>
<result column="hiredate" property="hiredate"></result>
<result column="sal" property="sal"></result>
<result column="comm" property="comm"></result>
<result column="deptno" property="deptno"></result>
<!--
fetchType="lazy" 懒加载策略
fetchType="eager" 立即加载策略
-->
<association property="dept" column="deptno" javaType="dept"
select="com.hukai.demo.dao.IDeptDao.findById" fetchType="lazy"></association>
</resultMap>
<select id="findEmpAndDept" resultMap="empDeptMap">
select * from emp
</select>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
在Mybatis的核心配置文件中可以使用setting标签修改全局的加载策略:
<settings>
<!-- 打开延迟加载的开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 将积极加载改为消极加载,即延迟加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
2
3
4
5
6
配置完成后我们先测试没有调用延迟加载属性时的sql发送情况:
@Test
public void testFindEmpAndDeptById() throws Exception{
List<Emp> empAndDept = empDao.findEmpAndDept();
empAndDept.forEach(emp -> System.out.println(emp.getEname()));
// empAndDept.forEach(emp -> System.out.println(emp.getDept()));
}
2
3
4
5
6

效果很明显,只执行了一条sql,查询了所有员工信息,并没有查询部门信息。
接下来我们测试调用了延迟加载属性时的sql发送情况:
@Test
public void testFindEmpAndDeptById() throws Exception{
List<Emp> empAndDept = empDao.findEmpAndDept();
// empAndDept.forEach(emp -> System.out.println(emp.getEname()));
empAndDept.forEach(emp -> System.out.println(emp.getDept()));
}
2
3
4
5
6

使用了延迟加载,将关联查询分成了两次单表查询。
# 1.3 延迟加载原理实现
它的原理是,使用 CGLIB 或 Javassist( 默认 ) 创建目标对象的代理对象。当调用代理对象的延迟加载属性的 getting 方法时,进入拦截器方法。比如调用 a.getB().getName() 方法,进入拦截器的invoke(...) 方法,发现 a.getB() 需要延迟加载时,那么就会单独发送事先保存好的查询关联 B 对象的 SQL ,把 B 查询上来,然后调用 a.setB(b) 方法,于是 a 对象 b 属性就有值了,接着完成a.getB().getName() 方法的调用。这就是延迟加载的基本原理。
总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定方法,执行数据加载。
# 2. 延迟加载源码剖析
# 2.1 延迟加载相关配置解析
在settings标签中,有4个和延迟加载相关属性:
| 设置名 | 描述 | 有效值 | 默认值 |
|---|---|---|---|
| lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | true | false | false |
| aggressiveLazyLoading | 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载。 | true | false | false |
| lazyLoadTriggerMethods | 指定对象的哪些方法触发一次延迟加载。 | 用逗号分隔的方法列表。 | equals,clone,hashCode,toString |
| proxyFactory | 指定 Mybatis 创建可延迟加载对象所用到的代理工具。 | CGLIB | JAVASSIST | JAVASSIST |
Setting 配置加载:
public class Configuration {
/**
* 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。
*/
protected boolean lazyLoadingEnabled = false;
protected ProxyFactory proxyFactory = new JavassistProxyFactory();
/**
* 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载(参考lazyLoadTriggerMethods)
*/
protected boolean aggressiveLazyLoading;
/**
* 指定哪个对象的方法触发一次延迟加载。
*/
protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
public void setProxyFactory(ProxyFactory proxyFactory) {
if (proxyFactory == null) {
proxyFactory = new JavassistProxyFactory();
}
this.proxyFactory = proxyFactory;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
有了这些属性值之后,我们来看看延迟加载代理对象是如何创建的。
# 2.2 延迟加载代理对象创建
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
默认采用javassistProxy进行代理对象的创建,我们转到JavasisstProxyFactory类中看看代理类是如何创建的:
public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory {
@Override
public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
}
private static class EnhancedResultObjectProxyImpl implements MethodHandler {
private final Class<?> type;
private final ResultLoaderMap lazyLoader;
private final boolean aggressive;
private final Set<String> lazyLoadTriggerMethods;
private final ObjectFactory objectFactory;
private final List<Class<?>> constructorArgTypes;
private final List<Object> constructorArgs;
private EnhancedResultObjectProxyImpl(Class<?> type, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
this.type = type;
this.lazyLoader = lazyLoader;
this.aggressive = configuration.isAggressiveLazyLoading();
this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods();
this.objectFactory = objectFactory;
this.constructorArgTypes = constructorArgTypes;
this.constructorArgs = constructorArgs;
}
public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
final Class<?> type = target.getClass();
// 创建 EnhancedResultObjectProxyImpl 对象
EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
// 创建代理对象
Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
// 将 target 的属性,复制到 enhanced 中
PropertyCopier.copyBeanProperties(type, target, enhanced);
return enhanced;
}
@Override
public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
final String methodName = method.getName();
try {
synchronized (lazyLoader) {
// 忽略 WRITE_REPLACE_METHOD ,和序列化相关
if (WRITE_REPLACE_METHOD.equals(methodName)) {
Object original;
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)) {
lazyLoader.loadAll();
// 如果调用了 setting 方法,则不在使用延迟加载
} else if (PropertyNamer.isSetter(methodName)) {
final String property = PropertyNamer.methodToProperty(methodName);
lazyLoader.remove(property); // 移除
// 如果调用了 getting 方法,则执行延迟加载
} else if (PropertyNamer.isGetter(methodName)) {
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);
}
}
}
// 创建代理对象
static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
// 创建 javassist ProxyFactory 对象
ProxyFactory enhancer = new ProxyFactory();
// 设置父类
enhancer.setSuperclass(type);
// 根据情况,设置接口为 WriteReplaceInterface 。和序列化相关,可以无视
try {
type.getDeclaredMethod(WRITE_REPLACE_METHOD); // 如果已经存在 writeReplace 方法,则不用设置接口为 WriteReplaceInterface
// ObjectOutputStream will call writeReplace of objects returned by writeReplace
if (log.isDebugEnabled()) {
log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this");
}
} catch (NoSuchMethodException e) {
enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class}); // 如果不存在 writeReplace 方法,则设置接口为 WriteReplaceInterface
} catch (SecurityException e) {
// nothing to do here
}
// 创建代理对象
Object enhanced;
Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]);
Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]);
try {
enhanced = enhancer.create(typesArray, valuesArray);
} catch (Exception e) {
throw new ExecutorException("Error creating lazy proxy. Cause: " + e, e);
}
// 设置代理对象的执行器
((Proxy) enhanced).setHandler(callback);
return enhanced;
}
}
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# 3. 总结
优点:
- 先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
缺点:
- 因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
因此,在多表中一对多,多对多通常情况下采用延迟加载,一对一(多对一)通常情况下采用立即加载。