MyBatis 如何支持延迟加载?现实原理是什么?

支持延迟加载的配置:
在配置文件的标签内设置参数

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

  • aggressiveLazyLoading:开启时,任一方法的调用都会加载该对象的所有延迟加载属性。否则,每个延迟加载属性会按需加载

  • lazyLoadTriggerMethods:指定对象的哪些方法触发一次延迟加载

resultMap 中配置 <association> 或 <collection>

 

配置与测试示例

//配置文件
< settings>
    < setting name="lazyLoadingEnabled" value="true"/>
    < setting name="aggressiveLazyLoading" value="false"/>
    < setting name="lazyLoadTriggerMethods" value=""/>
< /settings>


//Mapper xml
< select id="selectUserWithLazyInfo" resultMap="UserWithLazyInfo">
    select * from user where id = 1
< /select>

< resultMap id="UserWithLazyInfo" type="constxiong.po.User">
    < id property="id" column="id"/>
    < result property="name" column="name"/>
    < association property="info" javaType="constxiong.po.Info" select="constxiong.mapper.InfoMapper.selectInfoByUserId" column="id"/>
< /resultMap>


//InfoMapper
public interface InfoMapper {
    @Select("select * from info where user_id = #{userId}")
    @Results(value = {@Result(column="user_id", property = "userId")})
    Info selectInfoByUserId(int userId);
}


//测试代码
System.out.println("------ selectUserWithLazyInfo ------");
User user = userMapper.selectUserWithLazyInfo();
System.out.println(user);
System.out.println(user.getInfo());


//打印 User 对象里的 Info 为空,使用 getInfo 能够查询对应的值
------ selectUserWithLazyInfo ------
User{id=1, name='ConstXiong1', mc='null', info=null, articles=null}
Info{userId=1, name=大熊}

 

实现原理:

支持延迟加载是通过字节码增强实现的,MyBatis 3.3 及以上默认使用了 javassist,3.3 以前使用 cglib 实现。

我本地用的 MyBatis 3.5.5,使用了 javassist 增强,核心源码如下

//DefaultResultSetHandler getRowValue 获取每条的查询数据,resultMap 中如果包含懒加载 rowValue 在 createResultObject 方法通过 javassist 代理增强
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    //对象数据,通过 javassist 代理增强
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
        final MetaObject metaObject = configuration.newMetaObject(rowValue);
        boolean foundValues = this.useConstructorMappings;
        if (shouldApplyAutomaticMappings(resultMap, false)) {
            foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
        }
        //根据从数据库查询到的 resultSet,根据 resultMap 通过反射设置 rowValue 的值
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
        foundValues = lazyLoader.size() > 0 || foundValues;
        rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
}

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) {
            //如果返回对象的属性中包含懒加载,使用 javassist 代理增强,当设置属性值时被代理到 JavassistProxyFactory 的 invoke 方法
            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;
}
//JavassistProxyFactory 的 invoke 方法
public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
    final String methodName = method.getName();
    try {
        synchronized (lazyLoader) {
            if (WRITE_REPLACE_METHOD.equals(methodName)) {
                ...
            } else {
                if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
                    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)) {
                        //测试代码中 user.getInfo() 方法的调用,在此执行懒加载查询关联 SQL 设置 info 属性
                        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);
    }
}

 

完整 Demo:

https://javanav.com/val/973ded541e9244aa8b3169b9fb869d60.html

 

 


【Java面试题与答案】整理推荐

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值