聊聊Mybatis的延迟加载

延迟加载就是在需要的数据的时候再进行加载,也就是懒加载,延迟加载是基于嵌套查询来实现的,一般在一对多,多对多的时候使用延迟加载,一对一或者多对一的时候使用立即加载

全局延迟加载

全局延迟加载:

在settings标签下配置lazyLoadingEnabled属性:

<settings>
  <setting name="lazyLoadingEnabled" value="true"/>
</settings>

lazyLoadingEnabled,当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。

局部延迟加载

局部延迟加载:

association和collection标签中都有fetchType属性,有效值为 lazy 和 eager。 指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled

<resultMap id="orderMap" type="order">
    <collection property="orderList" ofType="order" column="id" select="findByUid" fetchType="lazy">

    </collection>

</resultMap>

具体延迟加载逻辑

延迟加载入口代码是结果集的映射,ResultSetHandler的handleResultSets,ResultSetHandler默认实现类是DefaultResultSetHandler,最后调用它的getRowValue()方法得到结果值,这个方法内的第一步就是根据ResultMap的type属性创建对应的结果对象

Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
       ...
        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;
    }

关键代码判断属性有没有嵌套查询,如果有的话就创建代理对象:configuration.getProxyFactory().createProxy(),调用的是ProxyFactory接口,它有两个实现类:JavassistProxyFactory和 CglibProxyFactory,默认是JavassistProxyFactory,调用createProxy()中通过调用内部类EnhancedResultObjectProxyImpl的createProxy()方法从而创建出代理对象

EnhancedResultObjectProxyImpl实现了MethodHandler接口,它重写invoke()方法进行拦截,在invoke()方法中有这么一段代码:

if (PropertyNamer.isGetter(methodName)) {
                                final String property = PropertyNamer.methodToProperty(methodName);
                                if (lazyLoader.hasLoader(property)) {
                                    lazyLoader.load(property);
                                }

也就是如果启用了延迟加载并且是getter方法的话,就执行load加载方法,否则执行原方法

ResultLoaderMap维护延迟加载的属性和ResultLoader的映射关系,它的loadMap属性是hashmap,value 是LoadPair,LoadPair中有ResultLoader属性, ResultLoader记录了一次延迟加载涉及的所有的信息,调用LoadPair的load方法中再执行一次sql,并将结果通过setValue()方法设置

总结

这篇文章我们讲了全局延迟加载的配置和局部延迟加载的配置,并介绍了延迟加载的大体过程:使用JavassistProxyFactory创建代理对象,当调用代理对象的延迟加载属性的getter方法的时候,进入invoke()方法进行拦截,如果属性需要延迟加载,就发送之前保存好的sql,执行sql把结果设置到类的属性中。