延时加载

fxz大约 14 分钟

延时加载

当出现嵌套查询的时候,第一层返回值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的类型。