延时加载
延时加载
当出现嵌套查询的时候,第一层返回值BeanA存在属性BeanB,而第二层返回值BeanB存在属性BeanA,这个时候就可能出现死循环,也就是循环依赖问题。
Mybatis解决嵌套查询的循环依赖采用的是延迟加载+缓存占位符(一级缓存) 。
嵌套查询源码:
# DefaultResultSetHandler.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();
// 获得内嵌查询的 MappedStatement 对象
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) {
// 获得 BoundSql 对象
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)) {
// 创建 DeferredLoad 对象,并通过该 DeferredLoad 对象从缓存中加载结采对象
executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
// 返回已定义
value = DEFERED;
// 检查缓存中不存在
} else {
// 创建 ResultLoader 对象
final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
// 如果要求延迟加载,则延迟加载
if (propertyMapping.isLazy()) {
// 如果该属性配置了延迟加载,则将其添加到 `ResultLoader.loaderMap` 中,等待真正使用时再执行嵌套查询并得到结果对象。
lazyLoader.addLoader(property, metaResultObject, resultLoader);
// 返回已定义
value = DEFERED;
// 如果不要求延迟加载,则直接执行加载对应的值
} else {
value = resultLoader.loadResult();
}
}
}
return value;
}
在BaseExecutor中实现延迟加载,可以看到当可以加载的时候,才能加载,否则加入到队列中。
# BaseExecutor.java
@Override
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
// 如果执行器已关闭,抛出 ExecutorException 异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 创建 DeferredLoad 对象
DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
// 如果可加载,则执行加载
if (deferredLoad.canLoad()) {
deferredLoad.load();
// 如果不可加载,则添加到 deferredLoads 中
} else {
deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
}
}
public boolean canLoad() {
return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
}
public void load() {
// 从缓存 localCache 中获取
List<Object> list = (List<Object>) localCache.getObject(key);
// 解析结果
Object value = resultExtractor.extractObjectFromList(list, targetType);
// 设置到 resultObject 中
resultObject.setValue(property, value);
}
当我们在调用查询的时候,可以看到有一个queryStack,它是记录了嵌套查询的层数,当等于0的时候,也就是最后的查询,把全部延迟加载队列中的数据进行加载。
@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());
// 已经关闭,则抛出 ExecutorException 异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存。
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
// queryStack + 1
queryStack++;
// 从一级缓存中,获取查询结果
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
// 获取到,则进行处理
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
// 获得不到,则从数据库中查询
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
// queryStack - 1
queryStack--;
}
if (queryStack == 0) {
// 执行延迟加载
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
// 清空 deferredLoads
deferredLoads.clear();
// 如果缓存级别是 LocalCacheScope.STATEMENT ,则进行清理
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
在第一层查询的时候,用占位符进行站位进行缓存:
在解析第一层的结果的时候,会触发第二层的SQL查询:
这时候queryStack变为2:
在解析第二层的数据的时候,因为Blog评论里面有博客对象,博客对象在上一层查询中进行了缓存,所以触发到的缓存,进行延迟加载:
又因为第一层缓存的value是占位符,所以放入延迟队列中:
当queryStack为0的时候,把所有延迟加载队列中的数据全部加载
可以看到,在最后一层的时候,对缓存进行清空,说明了之前说的所谓的一级缓存关闭,只是减小了作用域,同时可以看出,一级缓存在嵌套子查询中起到很大的作用。
懒加载
懒加载的意思就是只用调用某个属性的get方法的时候,采去加载数据,这样保证只有使用的时候数据才进行加载不使用不去加载,在一定程度上提高了效率。在Mybatis中,手动映射结果集的时候才会存在懒加载。
懒加载主要采用动态代理实现的,就是在原始的Bean做一层代理,这样在调用get方法的时候,可以操作自己想操作的。
代理完之后的对象,存在一个handler,指向一个执行器,进行数据的查询。
当调用set之后再调用get的时候,get请求是不会覆盖set请求的数据的。
当期望序列化、反序列化也触发懒加载的时候,需要满足两个条件:
① 必须使用jdk原生的序列化方式
② 必须实现一个配置工厂类,同时在setting中注册这个类,并不需要实现上面业务逻辑。
简单说明一下上述结构的功能:
① MethodHandler:处理个个属性get方法,就是对get方法进行增强。
② ResultLoaderMap:存放还未进行懒加载的属性,当进行完懒加载的时候,就进行移除。
③ LoadPair:执行序列化、反序列化操作。
④ ResultLoader:最终执行查询数据库操作。
在JavassistProxyFactory的invoke方法中对代理对象进行增强,首先判断是否开启懒加载,然后判断是方法是触发了全部懒加载,然后再判断是否调用了set方法,最后才是进行懒加载。
# JavassistProxyFactory.java
@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);
}
}
}
# ResultLoaderMap.java
/**
* 执行指定属性的加载
*
* @param property 属性
* @return 加载是否成功
*/
public boolean load(String property) throws SQLException {
// 获得 LoadPair 对象,并移除
LoadPair pair = loaderMap.remove(property.toUpperCase(Locale.ENGLISH));
// 执行加载
if (pair != null) {
pair.load();
return true; // 加载成功
}
return false; // 加载失败
}
整个逻辑分为2部分,第一部分是判断是否是通过metaResultObject和resultLoader两个属性是否为空判断是否是序列化,第二部分是直接查询数据库设置数据。
# LoadPair.java
/**
* Meta object which sets loaded properties.
*/
private transient MetaObject metaResultObject;
/**
* Result loader which loads unread properties.
*/
private transient ResultLoader resultLoader;
public void load(final Object userObject) throws SQLException {
if (this.metaResultObject == null || this.resultLoader == null) {
if (this.mappedParameter == null) {
throw new ExecutorException("Property [" + this.property + "] cannot be loaded because "
+ "required parameter of mapped statement ["
+ this.mappedStatement + "] is not serializable.");
}
// 获得 Configuration 对象
final Configuration config = this.getConfiguration();
// 获得 MappedStatement 对象
final MappedStatement ms = config.getMappedStatement(this.mappedStatement);
if (ms == null) {
throw new ExecutorException("Cannot lazy load property [" + this.property
+ "] of deserialized object [" + userObject.getClass()
+ "] because configuration does not contain statement ["
+ this.mappedStatement + "]");
}
// 获得对应的 MetaObject 对象
this.metaResultObject = config.newMetaObject(userObject);
// 创建 ResultLoader 对象
this.resultLoader = new ResultLoader(config, new ClosedExecutor(), ms, this.mappedParameter,
metaResultObject.getSetterType(this.property), null, null);
}
/* We are using a new executor because we may be (and likely are) on a new thread
* and executors aren't thread safe. (Is this sufficient?)
*
* A better approach would be making executors thread safe. */
if (this.serializationCheck == null) {
final ResultLoader old = this.resultLoader;
this.resultLoader = new ResultLoader(old.configuration, new ClosedExecutor(), old.mappedStatement,
old.parameterObject, old.targetType, old.cacheKey, old.boundSql);
}
this.metaResultObject.setValue(property, this.resultLoader.loadResult());
}
同时在初始化resultLoader的时候,传入一个ClosedExecutor,证明这个执行器执行完毕后就关闭了。
触发懒加载位置
在DefaultResultSetHandler创建结果集的时候,判断有子查询同时懒加载就进行代理对象的创建:
wirteReplace和readResolve
主要用于在序列化和反序列化之前,对bean的操作,可以更改属性值,甚至更改bean的类型。