mybatis 懒加载是怎么搞的

mybatis 懒加载是怎么实现的

  • 假设条件
    懒加载开启,有个嵌套查询, 假设User用户表,和Order订单表,要查询的结果是用户信息和关联的订单信息:
  • userMapper

        
        

        

            
            
            
        
    

  • orderMapper
   

  • 测试文件

    InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

    SqlSession sqlSession = factory.openSession();
//    IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
//    User user = mapper.findById(1);
    User user = sqlSession.selectOne("com.lagou.mapper.IUserMapper.findById", 1);
//
    System.out.println(user.getUsername());
    System.out.println(user.getOrderList());

  • fetchType=“eager”:

 1. queryFromDatabase  数据库查询用户信息 select * from user where id = #{id}
 2. createResultObject  创建响应对象User
 3. getNestedQueryMappingValue  根据嵌套查询条件,直接利用当前的excutor去查询数据库的结果 select * from orders where uid = #{uid}
 4. handleRowValuesForNestedResultMap  处理嵌套查询的结果

  • fetchType=“lazy”:
 1. queryFromDatabase  数据库查询用户信息 select * from user where id = #{id}
 2. createResultObject  创建响应对象,注意这个时候是利用javassit生成的是代理对象
 源代码如下:
 
    private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
        this.useConstructorMappings = false; // reset previous mapping result
        final List> constructorArgTypes = new ArrayList<>(); 
        final List constructorArgs = new ArrayList<>();
        Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
        if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
            
            final List propertyMappings = resultMap.getPropertyResultMappings();
            for (ResultMapping propertyMapping : propertyMappings) {
                // issue gcode #109 && issue #149
                // 如果有内嵌的查询,并且开启延迟加载,则创建结果对象的代理对象
                if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
                //注意这个lazyLoader
                    resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
                    break;
                }
            }
        }
        this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
        return resultObject;
    }
 
  
3 getNestedQueryMappingValue  在处理嵌套查询的值得时候,如果是懒加载,会先把这个查询的信息存起来,代码如下:

                // 创建 ResultLoader 对象, 里面是查询相关的信息
                final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
                // 如果要求延迟加载,则延迟加载
                if (propertyMapping.isLazy()) {
                    // 如果该属性配置了延迟加载, 注意这个lazyLoader和创建代理对象的是相同的
                    lazyLoader.addLoader(property, metaResultObject, resultLoader);
                    value = DEFERED;
                // 如果不要求延迟加载,则直接执行加载对应的值
                } else {
                    value = resultLoader.loadResult();
                }

这里并不会对嵌套查询执行真正的查数据库操作,而是把嵌套查询相关的信息(执行器信息,配置信息,查询信息等)放在了lazyLoader,而这个lazyLoader正式创建代理对象的参数。(user对象里包含了懒加载的信息)

User user = sqlSession.selectOne(“com.lagou.mapper.IUserMapper.findById”, 1);

所以我们此时拿到的user是个代理对象,
在我们使用这个user对象的时候,实际走的是代理对象的invoke方法,
那么我们就需要看下他的invoke方法:

        @Override
        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)) {
                        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();
                         
                            } else if (PropertyNamer.isSetter(methodName)) {
                                final String property = PropertyNamer.methodToProperty(methodName);
                                lazyLoader.remove(property); 
                            // 如果调用了 get 方法,则执行延迟加载, 比如执行user.getOrder()
                            } 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);
            }
        }
    }

所以在获取嵌套查询信息的时候user.getOrderList(),才会执行对应的sql。

所以懒加载利用了动态代理(感觉这玩意那块都有它), 如有错误, 感谢指正!

你可能感兴趣的:(mybatis)