在上一篇Mybatis查询逻辑时候,有一个点就是懒加载,这个点其实有点复杂,所以博主单独拿出来分析。
本文从以下角度展开:
- 什么是懒加载?
- Mybatis对懒加载的配置如何?
- 懒加载通过什么方式实现懒加载的?
懒加载使用
- 配置中加入以下两行:
<!-- 打开延迟加载的开关 -->
<setting name="lazyLoadingEnabled" value="true" />
<!-- 将积极加载改为消极加载(及按需加载) -->
<setting name="aggressiveLazyLoading" value="false" />
- 在所需要的
resultMap
的 列上加上fetchType="lazy"
表明是懒加载
例如:
<resultMap id="LazyResultMap" type="anla.learn.mybatis.interceptor.model.UserLazyDepartment" >
<id column="uid" property="uid" jdbcType="INTEGER" />
<result column="email" property="email" jdbcType="VARCHAR" />
<result column="password" property="password" jdbcType="VARCHAR" />
<result column="department_id" property="departmentId" jdbcType="BIGINT"/>
<!-- 懒加载 -->
<association property="department" fetchType="lazy" select="anla.learn.mybatis.interceptor.dao.DepartmentMapper.getByIndex" javaType="anla.learn.mybatis.interceptor.model.Department" column="department_id"/>
</resultMap>
分析
从结果入手,使用懒加载和不使用懒加载的返回的对象有什么区别呢?
- 正常查询:
- 定义懒加载查询:
两幅图片比较明显,懒加载返回对象不是原本的对象类型,而是带有后缀的字节码动态生成的类。
那么现在的目的就是找出懒加载为返回对象为何是动态生成字节码类?
ResultSet处理
配置懒加载是,在<resultMap>
增加<association>
节点配置,那么是否为处理结果发现了 fetchType=lazy
的配置,从而动态生成了类,从而当返回对象调用某些方法时,执行 懒加载查询语句呢?
从 efaultResultSetHandler
的 handlerResultSet
开始,而后往下一步步 调试
handleResultSet(rsw, resultMap, multipleResults, null);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
最终要返回的object 对象是由createResultObject
方法生成:
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;
}
上面逻辑可以看出,默认会生成一个 正常的对应 Type 的 resultObject
,而当判断有嵌套查询或者有懒加载变量时,则会对已有的 resultObject
重新赋值:
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
看看这个懒加载的代理生成过程:
@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);
}
public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
// 获取返回类型
final Class<?> type = target.getClass();
// 构造callback,这个是精髓
EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
// 使用Javassist创建代理
Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
// 拷贝属性
PropertyCopier.copyBeanProperties(type, target, enhanced);
return enhanced;
}
上面代码可以使用以下逻辑:
- 构造 代理 callback 即
EnhancedResultObjectProxyImpl
crateProxy(type, callback, constructorArgTypes, constructorArgs);
为使用 javassist 创建。PropertyCopier.copyBeanProperties(type, target, enhanced);
为将 target 属性拷贝到enhanced中。
看看 EnhancedResultObjectProxyImpl
:
它实现了 javassist.util.proxy.MethodHandler
, 所以实际上返回对象每次调用方法,都会调用 EnhancedResultObjectProxyImpl
的invoke方法,也就是说每个方法都被拦截了:
invoke:
@Override
public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
final String methodName = method.getName();
try {
synchronized (lazyLoader) {
// 对 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) {
// 如果仍然有为执行懒加载,则需要适配
return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
} else {
// 否则直接返回创建成的对象
return original;
}
} else {
if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
// 如果有懒加载并且执行的方法不为 finalize
if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
// 如果 aggressiveLazyLoading 为true 或者 包含 "equals", "clone", "hashCode", "toString" 之一,则全部加载
lazyLoader.loadAll();
} else if (PropertyNamer.isSetter(methodName)) {
// 如果是setter 方法,那么清楚懒加载map
final String property = PropertyNamer.methodToProperty(methodName);
lazyLoader.remove(property);
} else if (PropertyNamer.isGetter(methodName)) {
// 如果是get,那么判断是否为懒加载 所需loader ,是就执行懒加载
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);
}
}
上面 代码有以下逻辑:
- 对懒加载 map 加锁
- 判断是否为
writeReplace
序列化用方法,是的话在序列化成文件时候,需要判断是否加入 懒加载map集合对象。 - 判断当前对象
lazyLoader
是否有值,即是否有未完成的懒加载查询。lazyLoader
是在 getRowValue中赋值的,也就是说在创建代理对象时lazyLoader
为空,在后面给代理对象每个字段进行属性赋值时,会判断是否为懒加载(lazy loading),从而设置lazyLoader的值。
lazyLoader
中加载过属性不会加载第二次,会从lazyLoader
中删除,所以不用担心每次都会重新查询。 - 判断是否配置了
aggressiveLazyLoading
为true
, aggressiveLazyLoading 为true则默认触发一个懒加载时会将所有都加载出来,或者 包含 “equals”, “clone”, “hashCode”, “toString” 之一,则全部加载。 - 如果是setXxx方法,则会不进行懒加载操作,并且将
lazyLoader
对应字段删除。 - 如果是getXxx方法,则会从
lozyLoader
执行sql加载出来。
ResultLoaderMap
中维护了一个 Map<String, LoadPair>
, key 为属性,而LoadPair则作为一个句柄去调用加载的语句 等语句。
最终到 LoadPair
的load
方法时,实际只会进行 select
操作。
LoadPair
属于 ResultLoaderMap
的内部类,最终会调用 ResultLoader
的 loadResult
方法:
public Object loadResult() throws SQLException {
List<Object> list = selectList();
resultObject = resultExtractor.extractObjectFromList(list, targetType);
return resultObject;
}
private <E> List<E> selectList() throws SQLException {
Executor localExecutor = executor;
if (Thread.currentThread().getId() != this.creatorThreadId || localExecutor.isClosed()) {
// 如果不是当前线程,则新建执行器
localExecutor = newExecutor();
}
try {
// 否则使用该执行其执行query方法
return localExecutor.query(mappedStatement, parameterObject, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, cacheKey, boundSql);
} finally {
if (localExecutor != executor) {
localExecutor.close(false);
}
}
}
loadResult
则是获取 Executor
而后执行 query
逻辑。
但是有意思的是,懒加载 使用 <association/>
节点,虽然默认行为是 select
动作,但事实上你可以方任意一个 查询,可以为 select
、insert
、delete
、update
等语句,仍然会以懒加载机制,到该执行时候会被执行。
觉得博主写的有用,不妨关注博主公众号: 六点A君。
哈哈哈,一起研究Mybatis: