水煮MyBatis(十七)- 延迟加载(下)
前言
前面介绍了一些延迟加载功能里的基本组件和相应的使用方式,这一章主要从源码角度来分析其是如何实现此功能的。
懒加载执行序列图
注意:在执行invoke之前,有一些前置动作:主查询完成之后,给嵌入式查询生成对应的ResultLoader,这个逻辑与主查询是绑定在一起的,所以没有在序列图中展现。
关键源码
这里分三个关键源码来说明
- 判定是否需要懒加载,也是前提条件;
- 主查询执行完成以后,Mybatis对懒加载属性做了什么?
- 查询懒加载属性的时候,如何执行的;
判定是否延迟加载
先来看看源码,具体是在Mapper接口进行初始化的时候,MapperAnnotationBuilder类中根据注解解析,生成MappedStatement时。 方法中有两个分支:
- 读取配置文件中的
lazy-loading-enabled
设置; - 一对一的懒加载条件:有对应的查询方法,并且获取类型是LAZY;
- 一对多的懒加载条件:有对应的查询方法,并且获取类型是LAZY;
private boolean isLazy(Result result) {
// 读取配置文件中的lazy.load.enabled设置
boolean isLazy = configuration.isLazyLoadingEnabled();
// 一对一的懒加载条件:有对应的查询方法,并且获取类型是LAZY
if (result.one().select().length() > 0 && FetchType.DEFAULT != result.one().fetchType()) {
isLazy = result.one().fetchType() == FetchType.LAZY;
} else if (result.many().select().length() > 0 && FetchType.DEFAULT != result.many().fetchType()) {
// 一对多的懒加载条件:有对应的查询方法,并且获取类型是LAZY
isLazy = result.many().fetchType() == FetchType.LAZY;
}
return isLazy;
}
值得注意的是,就算下面的if/else语句里的条件都不匹配,也会根据配置文件中的lazy-loading-enabled
设置来指定是否进行懒加载。也就是说,就算我们设定了FetchType=DEFAULT
,只要配置了lazy-loading-enabled=true
,就会默认进行懒加载。
主查询处理懒查询属性
主查询完成之后,会给嵌入式查询生成对应的ResultLoader,方便执行后续的查询。这段代码在【DefaultResultSetHandler】类中。
这里主要介绍一下图中标注绿色的方法:【getNestedQueryMappingValue】
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) {
// 读取执行语句
final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
// 获取缓存key
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.addLoader(property, metaResultObject, resultLoader);
value = DEFERRED;
} else {
// 否则直接加载
value = resultLoader.loadResult();
}
}
}
return value;
}
方法很直白,分了三大块:
- 读取懒加载的配置,也就是上章提到过的@Result注解;
- 如果有本地缓存【一级缓存】,则无论是否懒加载,都直接返回。为什么不判断二级缓存呢?据我所知,二级缓存只有在开启缓存以后,在CachingExecutor里查询的时候使用到,也就是说,就算在这个方法里用不到二级缓存,后续查询的时候,也有用到的机会。
- 生成ResultLoader,判断嵌入式属性对象是否需要懒加载,如果配置的是
[FetchType = EAGER]
,则直接加载返回,否则将加载参数缓存,等待需要加载的时候再执行resultLoader.loadResult()
方法。
执行懒加载
下面是【JavassistProxyFactory】类里的简化以后的invoke()方法,只展示了核心逻辑
@Override
public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
String methodName = method.getName();
// 如果存在懒加载属性,且不为finally方法
if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
// 如果aggressive.enable为true,或者当前方法为懒加载触发方法,则加载全部属性,无论是否配置了懒加载
lazyLoader.loadAll();
} else if (PropertyNamer.isSetter(methodName)) {
// 如果是set方法,则懒加载列表中移除此属性
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);
}
}
}
return methodProxy.invoke(enhanced, args);
}
真正执行懒加载的三个前提条件:
- 如果存在懒加载属性,且不为finally方法;
- 如果
aggressive-lazy-loading
为true,或者当前方法为懒加载触发方法,lazy-load-trigger-methods
配置,则加载全部属性,无论是否配置了懒加载; - 如果是set方法,则懒加载列表中移除此属性;
执行加载
最终执行懒加载的类【ResultLoaderMap.LoadPair】,也是简化了逻辑,就不展开介绍了。
public void load(final Object userObject) throws SQLException {
// 用resultLoader对象执行加载
this.metaResultObject.setValue(property, this.resultLoader.loadResult());
}
// 加载
public Object loadResult() throws SQLException {
// 查询列表,无论是二级缓存或者数据库查询
List<Object> list = selectList();
// 组合数据,列表或者单个元素
resultObject = resultExtractor.extractObjectFromList(list, targetType);
return resultObject;
}
转载自:https://juejin.cn/post/7246219598176370748