【Mybatis】Mybatis源码之懒加载原理

1,005 阅读8分钟

这是我参与8月更文挑战的第30天,活动详情查看:8月更文挑战

嵌套查询与懒加载的代码实现这里不再赘述,见文章Mybatis之缓存、懒加载。下面我们来直接分析源码,看看懒加载的执行过程。

时序图

sequenceDiagram
participant A as BaseExecutor
participant B as SimpleExecutor
participant C as PreparedStatementHandler
participant D as DefaultResultSetHandler

A ->> A : query
A ->> A : queryFromDatabase
A ->> B : doQuery
B ->> C : query
C ->>+ D : handleResultSets
D ->> D : handleResultSet
D ->> D : handleRowValues
D ->> D : handleRowValuesForSimpleResultMap
D ->> D : getRowValue
D ->> D : applyPropertyMappings
D ->> D : getPropertyMappingValue
D ->> D : getPropertyMappingValue
D -->>- A : List<Object>

关键代码

BaseExecutor#query

  • query方法,所有查询都需要经过这里,如果是普通查询,则进入查询时queryStack=1,查询结束后queryStack=0
  • 如果是嵌套查询,进入嵌套外部查询时,queryStack=1,进入嵌套内部查询时,queryStack=2,嵌套内部查询结束后queryStack=1,嵌套外部查询结束后queryStack=0.
/**
 * 查询数据库中的数据
 * @param ms 映射语句
 * @param parameter 参数对象
 * @param rowBounds 翻页限制条件
 * @param resultHandler 结果处理器
 * @param key 缓存的键
 * @param boundSql 查询语句
 * @param <E> 结果类型
 * @return 结果列表
 * @throws SQLException
 */
@SuppressWarnings("unchecked")
@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());
    if (closed) {
        // 执行器已经关闭
        throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) { // 新的查询栈且要求清除缓存
        // 新的查询栈,故清除本地缓存,即清除一级缓存
        clearLocalCache();
    }
    List<E> list;
    try {
        /**
         * 查询栈,第一次进入时,压栈
         * 普通查询时,压栈后执行查询,完成后出栈,查询栈为0
         * 嵌套查询时,第一次进入后压栈,在第一次查询还未完成(查询栈未出栈)时执行嵌套查询,会再次压栈,因此就会出现查询栈大于1的情况
         */
        queryStack++;
        // 尝试从本地缓存获取结果
        list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
        if (list != null) {
            // 本地缓存中有结果,则对于CALLABLE语句还需要绑定到IN/INOUT参数上
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
        } else {
            // 本地缓存没有结果,故需要查询数据库
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
    } finally {
        // 查询结束后,出栈
        queryStack--;
    }
    if (queryStack == 0) {
        // 懒加载操作的处理
        for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        // issue #601
        deferredLoads.clear();
        // 如果本地缓存的作用域为STATEMENT,则立刻清除本地缓存
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
        }
    }
    return list;
}

DefaultResultSetHandler#getRowValue

  • 在getRowValue方法中,会先创建接收结果的空对象

  • 判断是否开启自动映射,开启自动映射的配置及枚举如下

    <setting name="autoMappingBehavior" value="PARTIAL"/>
    
    // 自动映射选项
    public enum AutoMappingBehavior {
    
        /**
         * Disables auto-mapping.
         */
        // 关闭自动映射
        NONE,
    
        /**
         * Will only auto-map results with no nested result mappings defined inside.
         */
        // 仅仅自动映射单层属性
        PARTIAL,
    
        /**
         * Will auto-map result mappings of any complexity (containing nested or otherwise).
         */
        // 映射所有属性,含嵌套属性
        FULL
    }
    
  • 根据resultMap标签配置的映射关系进行映射

/**
 * 将一条记录转化为一个对象
 *
 * @param rsw          结果集包装
 * @param resultMap    结果映射
 * @param columnPrefix 列前缀
 * @return 转化得到的对象
 * @throws SQLException
 */
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    // 懒加载
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    // 创建这一行记录对应的空对象
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
        // 根据对象得到其MetaObject
        final MetaObject metaObject = configuration.newMetaObject(rowValue);
        boolean foundValues = this.useConstructorMappings;
        // 是否允许自动映射未明示的字段
        if (shouldApplyAutomaticMappings(resultMap, false)) {
            // 自动映射未明示的字段(resultType),映射时的TypeHandler通过属性名与set方法参数类型的映射来获取属性的类型,并据此获取对应的TypeHandler
            foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
        }
        // 按照明示的字段进行重新映射(resultMap),解析XML时获取TypeHandler
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
        foundValues = lazyLoader.size() > 0 || foundValues;
        rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
}

DefaultResultSetHandler#createResultObject

  • 在createResultObject方法中,会根据resultMap标签中的type属性来创建结果集的接收对象
  • 如果当前属性是嵌套查询,并且是懒加载,则会创建接收对象的代理对象,代理类是EnhancedResultObjectProxyImpl
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;
}

DefaultResultSetHandler#applyPropertyMappings

  1. 获取ResultSet中的列名
  2. 获取resultMap标签中的映射配置
  3. 调用方法getPropertyMappingValue获取对应的结果值
  4. 拿到映射关系对应的属性名称
  5. 调用metaObject.setValue方法对属性进行赋值
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
        throws SQLException {
    // 获取ResultSet中的列名
    final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    // 获取resultMap映射配置
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    for (ResultMapping propertyMapping : propertyMappings) {
        // 获取映射配置中的完整列名
        String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
        if (propertyMapping.getNestedResultMapId() != null) {
            // the user added a column attribute to a nested result map, ignore it
            column = null;
        }
        if (propertyMapping.isCompositeResult()
                || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
                || propertyMapping.getResultSet() != null) {
            // 根据列名获取对应的值
            Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
            // issue #541 make property optional
            // 获取映射配置中那个属性名
            final String property = propertyMapping.getProperty();
            if (property == null) {
                continue;
            } else if (value == DEFERRED) {
                // 如果是懒加载,则设置当前查询成功,并且直接进入下一次循环,不再为当前属性赋值
                foundValues = true;
                continue;
            }
            if (value != null) {
                foundValues = true;
            }
            if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
                // gcode issue #377, call setter on nulls (value is not 'found')
                // 为属性设置值
                metaObject.setValue(property, value);
            }
        }
    }
    return foundValues;
}

DefaultResultSetHandler#getPropertyMappingValue

  • 如果当前映射是嵌套查询,则调用getNestedQueryMappingValue方法获取对应列的值
  • 如果是普通查询,则直接调用TypeHandler中的getResult方法获取对应列的值
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
        throws SQLException {
    if (propertyMapping.getNestedQueryId() != null) {
        // 执行嵌套查询
        return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
    } else if (propertyMapping.getResultSet() != null) {
        // 处理resultSet属性(该属性与多结果集查询属性resultSets搭配)
        addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?
        return DEFERRED;
    } else {
        // 普通查询
        final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
        final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
        return typeHandler.getResult(rs, column);
    }
}

DefaultResultSetHandler#getNestedQueryMappingValue

  • 如果是非懒加载,则会根据加载好的参数,立即执行查询BaseExecutor#query,并将结果返回
  • 如果是懒加载,则会以当前属性名的全大写作为key,将执行查询所需的各项条件封装值作为value,存入lazyLoader中
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) {
        // 获取SQL
        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)) {
            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中
                lazyLoader.addLoader(property, metaResultObject, resultLoader);
                value = DEFERRED;
            } else {
                // 非懒加载直接执行查询
                value = resultLoader.loadResult();
            }
        }
    }
    return value;
}

到这里,如果是懒加载,则懒加载的属性值为空,查询结束;如果不是懒加载,嵌套内部查询在查询出结果后,对该属性赋值,整个嵌套查询就结束了。而如果是懒加载,返回的对象是一个代理对象,当调用对象中的方法时就会走到代理类的intercept方法中。

由于在XML中配置了CGLIB代理,因此这里走到了CglibProxyFactory类中。这里可以配置使用CGLIB代理或者JDK代理。

<!--指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。CGLIB | JAVASSIST-->
<setting name="proxyFactory" value="CGLIB"/>

CglibProxyFactory.EnhancedResultObjectProxyImpl#intercept

  • 普通方法调用,进入到else代码块

  • 对于激进加载属性aggressive,可以在XML中进行配置

    <!--当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载,默认值false-->
    <setting name="aggressiveLazyLoading" value="false"/>
    
  • 当调用属性的get方法,并且lazyLoader中包含有该属性,则加载该属性的值

/**
 * 代理类的拦截方法
 * @param enhanced 代理对象本身
 * @param method 被调用的方法
 * @param args 每调用的方法的参数
 * @param methodProxy 用来调用父类的代理
 * @return 方法返回值
 * @throws Throwable
 */
@Override
public Object intercept(Object enhanced, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    // 取出被代理类中此次被调用的方法的名称
    final String methodName = method.getName();
    try {
        synchronized (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) { // 存在懒加载属性
                    // 则此时返回的信息要更多,不仅仅是原对象,还有相关的懒加载的设置等信息。因此使用CglibSerialStateHolder进行一次封装
                    return new CglibSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
                } else {
                    // 没有未懒加载的属性了,那直接返回原对象进行序列化
                    return original;
                }
            } else {
                if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) { // 存在懒加载属性且被调用的不是finalize方法
                    if (aggressive || lazyLoadTriggerMethods.contains(methodName)) { // 设置了激进懒加载或者被调用的方法是能够触发全局懒加载的方法
                        // 完成所有属性的懒加载
                        lazyLoader.loadAll();
                    } else if (PropertyNamer.isSetter(methodName)) {  // 调用了属性写方法
                        // 则先清除该属性的懒加载设置。该属性不需要被懒加载了
                        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);
                        }
                    }
                }
            }
        }
        // 触发被代理类的相应方法。能够进行到这里的是除去writeReplace方法外的方法,例如读写方法、toString方法等
        return methodProxy.invokeSuper(enhanced, args);
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
}

以上就是Mybatis懒加载的实现原理。