【Mybatis】Mybatis源码之懒加载原理 时序图 关

这是我参与8月更文挑战的第30天,活动详情查看:8月更文挑战

嵌套查询与懒加载的代码实现这里不再赘述,见文章Mybatis之缓存、懒加载。下面我们来直接分析源码,看看懒加载的执行过程。

时序图

BaseExecutorSimpleExecutorPreparedStatementHandlerDefaultResultSetHandlerqueryqueryFromDatabasedoQueryqueryhandleResultSetshandleResultSethandleRowValueshandleRowValuesForSimpleResultMapgetRowValueapplyPropertyMappingsgetPropertyMappingValuegetPropertyMappingValueListBaseExecutorSimpleExecutorPreparedStatementHandlerDefaultResultSetHandler
关键代码
====

BaseExecutor#query

  • query方法,所有查询都需要经过这里,如果是普通查询,则进入查询时queryStack=1,查询结束后queryStack=0
  • 如果是嵌套查询,进入嵌套外部查询时,queryStack=1,进入嵌套内部查询时,queryStack=2,嵌套内部查询结束后queryStack=1,嵌套外部查询结束后queryStack=0.
1
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
java复制代码/**
* 查询数据库中的数据
* @param ms 映射语句
* @param parameter 参数对象
* @param rowBounds 翻页限制条件
* @param resultHandler 结果处理器
* @param key 缓存的键
* @param boundSql 查询语句
* @param <E> 结果类型
* @return 结果列表
* @throws SQLException
*/
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
// 执行器已经关闭
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) { // 新的查询栈且要求清除缓存
// 新的查询栈,故清除本地缓存,即清除一级缓存
clearLocalCache();
}
List<E> list;
try {
/**
* 查询栈,第一次进入时,压栈
* 普通查询时,压栈后执行查询,完成后出栈,查询栈为0
* 嵌套查询时,第一次进入后压栈,在第一次查询还未完成(查询栈未出栈)时执行嵌套查询,会再次压栈,因此就会出现查询栈大于1的情况
*/
queryStack++;
// 尝试从本地缓存获取结果
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 本地缓存中有结果,则对于CALLABLE语句还需要绑定到IN/INOUT参数上
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 本地缓存没有结果,故需要查询数据库
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
// 查询结束后,出栈
queryStack--;
}
if (queryStack == 0) {
// 懒加载操作的处理
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
// 如果本地缓存的作用域为STATEMENT,则立刻清除本地缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}

DefaultResultSetHandler#getRowValue

  • 在getRowValue方法中,会先创建接收结果的空对象
  • 判断是否开启自动映射,开启自动映射的配置及枚举如下
1
xml复制代码<setting name="autoMappingBehavior" value="PARTIAL"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码// 自动映射选项
public enum AutoMappingBehavior {

/**
* Disables auto-mapping.
*/
// 关闭自动映射
NONE,

/**
* Will only auto-map results with no nested result mappings defined inside.
*/
// 仅仅自动映射单层属性
PARTIAL,

/**
* Will auto-map result mappings of any complexity (containing nested or otherwise).
*/
// 映射所有属性,含嵌套属性
FULL
}
  • 根据resultMap标签配置的映射关系进行映射
1
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
java复制代码/**
* 将一条记录转化为一个对象
*
* @param rsw 结果集包装
* @param resultMap 结果映射
* @param columnPrefix 列前缀
* @return 转化得到的对象
* @throws SQLException
*/
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
// 懒加载
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 创建这一行记录对应的空对象
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
// 根据对象得到其MetaObject
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
// 是否允许自动映射未明示的字段
if (shouldApplyAutomaticMappings(resultMap, false)) {
// 自动映射未明示的字段(resultType),映射时的TypeHandler通过属性名与set方法参数类型的映射来获取属性的类型,并据此获取对应的TypeHandler
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// 按照明示的字段进行重新映射(resultMap),解析XML时获取TypeHandler
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}

DefaultResultSetHandler#createResultObject

  • 在createResultObject方法中,会根据resultMap标签中的type属性来创建结果集的接收对象
  • 如果当前属性是嵌套查询,并且是懒加载,则会创建接收对象的代理对象,代理类是EnhancedResultObjectProxyImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
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;
}

DefaultResultSetHandler#applyPropertyMappings

  1. 获取ResultSet中的列名
  2. 获取resultMap标签中的映射配置
  3. 调用方法getPropertyMappingValue获取对应的结果值
  4. 拿到映射关系对应的属性名称
  5. 调用metaObject.setValue方法对属性进行赋值
1
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
java复制代码private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
// 获取ResultSet中的列名
final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
// 获取resultMap映射配置
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// 获取映射配置中的完整列名
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
if (propertyMapping.getNestedResultMapId() != null) {
// the user added a column attribute to a nested result map, ignore it
column = null;
}
if (propertyMapping.isCompositeResult()
|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
|| propertyMapping.getResultSet() != null) {
// 根据列名获取对应的值
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
// issue #541 make property optional
// 获取映射配置中那个属性名
final String property = propertyMapping.getProperty();
if (property == null) {
continue;
} else if (value == DEFERRED) {
// 如果是懒加载,则设置当前查询成功,并且直接进入下一次循环,不再为当前属性赋值
foundValues = true;
continue;
}
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
// gcode issue #377, call setter on nulls (value is not 'found')
// 为属性设置值
metaObject.setValue(property, value);
}
}
}
return foundValues;
}

DefaultResultSetHandler#getPropertyMappingValue

  • 如果当前映射是嵌套查询,则调用getNestedQueryMappingValue方法获取对应列的值
  • 如果是普通查询,则直接调用TypeHandler中的getResult方法获取对应列的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
if (propertyMapping.getNestedQueryId() != null) {
// 执行嵌套查询
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
} else if (propertyMapping.getResultSet() != null) {
// 处理resultSet属性(该属性与多结果集查询属性resultSets搭配)
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERRED;
} else {
// 普通查询
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
return typeHandler.getResult(rs, column);
}
}

DefaultResultSetHandler#getNestedQueryMappingValue

  • 如果是非懒加载,则会根据加载好的参数,立即执行查询BaseExecutor#query,并将结果返回
  • 如果是懒加载,则会以当前属性名的全大写作为key,将执行查询所需的各项条件封装值作为value,存入lazyLoader中
1
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
java复制代码private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
// 获取嵌套查询的需要的参数
final String nestedQueryId = propertyMapping.getNestedQueryId();
final String property = propertyMapping.getProperty();
final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix);
Object value = null;
// 查询参数不为空
if (nestedQueryParameterObject != null) {
// 获取SQL
final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
// 生成CacheKey
final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
final Class<?> targetType = propertyMapping.getJavaType();
if (executor.isCached(nestedQuery, key)) {
executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
value = DEFERRED;
} else {
final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
// 是否懒加载
if (propertyMapping.isLazy()) {
// 懒加载,将懒加载的属性放入lazyLoader中
lazyLoader.addLoader(property, metaResultObject, resultLoader);
value = DEFERRED;
} else {
// 非懒加载直接执行查询
value = resultLoader.loadResult();
}
}
}
return value;
}

到这里,如果是懒加载,则懒加载的属性值为空,查询结束;如果不是懒加载,嵌套内部查询在查询出结果后,对该属性赋值,整个嵌套查询就结束了。而如果是懒加载,返回的对象是一个代理对象,当调用对象中的方法时就会走到代理类的intercept方法中。

由于在XML中配置了CGLIB代理,因此这里走到了CglibProxyFactory类中。这里可以配置使用CGLIB代理或者JDK代理。

1
2
xml复制代码<!--指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。CGLIB | JAVASSIST-->
<setting name="proxyFactory" value="CGLIB"/>

CglibProxyFactory.EnhancedResultObjectProxyImpl#intercept

  • 普通方法调用,进入到else代码块
  • 对于激进加载属性aggressive,可以在XML中进行配置
1
2
xml复制代码<!--当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载,默认值false-->
<setting name="aggressiveLazyLoading" value="false"/>
  • 当调用属性的get方法,并且lazyLoader中包含有该属性,则加载该属性的值
1
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
java复制代码/**
* 代理类的拦截方法
* @param enhanced 代理对象本身
* @param method 被调用的方法
* @param args 每调用的方法的参数
* @param methodProxy 用来调用父类的代理
* @return 方法返回值
* @throws Throwable
*/
@Override
public Object intercept(Object enhanced, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 取出被代理类中此次被调用的方法的名称
final String methodName = method.getName();
try {
synchronized (lazyLoader) { // 防止属性的并发加载
if (WRITE_REPLACE_METHOD.equals(methodName)) { // 被调用的是writeReplace方法
// 创建一个原始对象
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) { // 存在懒加载属性
// 则此时返回的信息要更多,不仅仅是原对象,还有相关的懒加载的设置等信息。因此使用CglibSerialStateHolder进行一次封装
return new CglibSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
} else {
// 没有未懒加载的属性了,那直接返回原对象进行序列化
return original;
}
} else {
if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) { // 存在懒加载属性且被调用的不是finalize方法
if (aggressive || lazyLoadTriggerMethods.contains(methodName)) { // 设置了激进懒加载或者被调用的方法是能够触发全局懒加载的方法
// 完成所有属性的懒加载
lazyLoader.loadAll();
} else if (PropertyNamer.isSetter(methodName)) { // 调用了属性写方法
// 则先清除该属性的懒加载设置。该属性不需要被懒加载了
final String property = PropertyNamer.methodToProperty(methodName);
lazyLoader.remove(property);
} else if (PropertyNamer.isGetter(methodName)) { // 调用了属性读方法
final String property = PropertyNamer.methodToProperty(methodName);
// 如果该属性是尚未加载的懒加载属性,则进行懒加载
if (lazyLoader.hasLoader(property)) {
lazyLoader.load(property);
}
}
}
}
}
// 触发被代理类的相应方法。能够进行到这里的是除去writeReplace方法外的方法,例如读写方法、toString方法等
return methodProxy.invokeSuper(enhanced, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}

以上就是Mybatis懒加载的实现原理。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%