在上一篇Mybatis查询逻辑时候,有一个点就是懒加载,这个点其实有点复杂,所以博主单独拿出来分析。
本文从以下角度展开:
resultMap
的 列上加上 fetchType="lazy"
表明是懒加载
具体看一看例子:
https://github.com/anLA7856/mybatislearn/blob/master/mybatis-interceptor/src/test/java/MybatisTest.java
从结果入手,使用懒加载和不使用懒加载的返回的对象有什么区别呢?
两幅图片比较明显,懒加载返回对象不是原本的对象类型,而是带有后缀的字节码动态生成的类。
那么现在的目的就是找出懒加载为返回对象为何是动态生成字节码类?
配置懒加载是,在
增加
节点配置,那么是否为处理结果发现了 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> constructorArgTypes = new ArrayList<>();
final List
上面逻辑可以看出,默认会生成一个 正常的对应 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> constructorArgTypes, List
public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List> constructorArgTypes, List
上面代码可以使用以下逻辑:
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);
}
}
上面 代码有以下逻辑:
writeReplace
序列化用方法,是的话在序列化成文件时候,需要判断是否加入 懒加载map集合对象。lazyLoader
是否有值,即是否有未完成的懒加载查询。lazyLoader
是在 getRowValue中赋值的,也就是说在创建代理对象时 lazyLoader
为空,在后面给代理对象每个字段进行属性赋值时,会判断是否为懒加载(lazy loading),从而设置lazyLoader的值。lazyLoader
中加载过属性不会加载第二次,会从 lazyLoader
中删除,所以不用担心每次都会重新查询。aggressiveLazyLoading
为true
, aggressiveLazyLoading 为true则默认触发一个懒加载时会将所有都加载出来,或者 包含 “equals”, “clone”, “hashCode”, “toString” 之一,则全部加载。lazyLoader
对应字段删除。lozyLoader
执行sql加载出来。ResultLoaderMap
中维护了一个 Map
, key 为属性,而LoadPair则作为一个句柄去调用加载的语句 等语句。
最终到 LoadPair
的load
方法时,实际只会进行 select
操作。
LoadPair
属于 ResultLoaderMap
的内部类,最终会调用 ResultLoader
的 loadResult
方法:
public Object loadResult() throws SQLException {
List
loadResult
则是获取 Executor
而后执行 query
逻辑。
但是有意思的是,懒加载 使用
节点,虽然默认行为是 select
动作,但事实上你可以方任意一个 查询,可以为 select
、insert
、delete
、update
等语句,仍然会以懒加载机制,到该执行时候会被执行。
觉得博主写的有用,不妨关注博主公众号: 六点A君。
哈哈哈,一起研究Mybatis: