Mybatis源码剖析 -- 延迟加载

一、什么是延迟加载

  1. 在开发过程中,假设有一个用户信息类,映射多个订单信息类
    • 立即加载:如果每次加载用户信息的同时就加载这个用户下的所有订单信息,那么这就叫做立即加载
    • 延迟加载:查询用户信息的时候仅仅只查询用户信息,等什么时候需要用到其订单信息的时候再去查询这个用户下的所有订单信息,这就叫延迟加载
  2. 举个例子
    • 问题
      在一对多中,当我们有⼀个用户,它有个100个订单
      在查询用户的时候,要不要把关联的订单查出来?
      在查询订单的时候,要不要把关联的用户查出来?
    • 回答
      在查询用户时,用户下的订单应该是什么时候用,什么时候查询
      在查询订单时,订单所属的用户信息应该是随着订单⼀起查询出来的
  3. 延迟加载
    就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载。
    • 优点
      先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能
    • 缺点
      因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降
    • 在多表中
      ⼀对多,多对多:通常情况下采用延迟加载
      ⼀对⼀(多对⼀):通常情况下采用立即加载
    • 注意
      延迟加载是基于嵌套查询来实现的

二、延迟加载的应用

  1. 局部延迟加载
    在 association 和 collection 标签中都有⼀个 fetchType 属性,通过修改它的值,可以修改局部的加载策略
    • 懒加载策略:fetchType="lazy"
    • 立即加载策略:fetchType="eager"
  2. 全局延迟加载
    在 Mybatis 的核心配置文件中可以使用 setting 标签修改全局的加载策略
    
        
        
    
    
  3. 注意:局部的加载策略 高于 全局的加载策略

三、延迟加载的实现原理

其实延迟加载的底层原理:还是动态代理,通过代理拦截到指定方法,执行数据加载

  • 使用 CGLIB 或 Javassist(默认)创建目标对象的代理对象
  • 当调用代理对象的延迟加载属性的 getting 方法时,进入拦截器方法
  • 比如调用 a.getB().getName() 方法,进入拦截器的invoke(...) 方法,发现 a.getB() 需要延迟加载时,那么就会单独发送事先保存好的查询关联 B对象的 SQL ,把 B 查询出来,然后调用 a.setB(b) 方法,于是 a 对象 b 属性就有值了,接着完成 a.getB().getName() 方法的调用

四、延迟加载源码剖析 -- 创建代理对象

  1. Setting 配置加载
    /**
     * MyBatis 配置
     *
     * @author Clinton Begin
     */
    public class Configuration {
        /**
         * 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载(参考lazyLoadTriggerMethods)
         */
        protected boolean aggressiveLazyLoading;
    
        /**
         * 延迟加载触发⽅法
         */
         protected Set lazyLoadTriggerMethods = new HashSet (Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
         /** 是否开启延迟加载 */
         protected boolean lazyLoadingEnabled = false;
    
         /**
          * 延迟加载代理对象创建 Mybatis 的查询结果是由 ResultSetHandler 接口的 handleResultSets() 方法处理的,ResultSetHandler 接口只有⼀个实现,DefaultResultSetHandler,接下来看下延迟加载相关的⼀个核心的方法
          * 默认使⽤Javassist代理⼯⼚
          * @param proxyFactory
          */
          public void setProxyFactory(ProxyFactory proxyFactory) {
              if (proxyFactory == null) {
                  proxyFactory = new JavassistProxyFactory();
              }
              this.proxyFactory = proxyFactory;
          }
    
          //省略...
    }
    
  2. 代理对象创建
    Mybatis 的查询结果是由 ResultSetHandler 接口的handleResultSets()方法处理的,ResultSetHandler 接口只有⼀个实现,DefaultResultSetHandler,接下来看下延迟加载相关的⼀个核心的方法
    // 创建映射后的结果对象
    private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
        // useConstructorMappings ,表示是否使用构造方法创建该结果对象。此处将其重置
        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()) {
                    resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
                    break;
                }
            }
        }
        // 判断是否使用构造方法创建该结果对象
        this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
        return resultObject;
    }
     
    
  3. 当设置了fetchType="lazy"时,会执行configuration.getProxyFactory().createProxy(...),而 configuration.getProxyFactory()就是protected ProxyFactory proxyFactory = new JavassistProxyFactory();,其实就是 new 了一个 JavassistProxyFactory,所以说默认就是 JavassistProxyFactory 去生成代理对象
  4. 创建代理对象
    public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) {
        final Class type = target.getClass();
        // 创建 EnhancedResultObjectProxyImpl 对象
        EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
        // 创建代理对象
        Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
        // 将 target 的属性,复制到 enhanced 中
        PropertyCopier.copyBeanProperties(type, target, enhanced);
        return enhanced;
    }
     
    static Object crateProxy(Class type, MethodHandler callback, List> constructorArgTypes, List constructorArgs) {
        // 创建 javassist ProxyFactory 对象
        ProxyFactory enhancer = new ProxyFactory();
        // 设置父类
        enhancer.setSuperclass(type);
    
        // 根据情况,设置接口为 WriteReplaceInterface 。和序列化相关,可以无视
        try {
            type.getDeclaredMethod(WRITE_REPLACE_METHOD); // 如果已经存在 writeReplace 方法,则不用设置接口为 WriteReplaceInterface
            // ObjectOutputStream will call writeReplace of objects returned by writeReplace
            if (log.isDebugEnabled()) {
                log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this");
            }
        } catch (NoSuchMethodException e) {
            enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class}); // 如果不存在 writeReplace 方法,则设置接口为 WriteReplaceInterface
        } catch (SecurityException e) {
            // nothing to do here
        }
    
        // 创建代理对象
        Object enhanced;
        Class[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]);
        Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]);
        try {
            enhanced = enhancer.create(typesArray, valuesArray);
        } catch (Exception e) {
            throw new ExecutorException("Error creating lazy proxy.  Cause: " + e, e);
        }
    
        // 设置代理对象的执行器
        ((Proxy) enhanced).setHandler(callback);
        return enhanced;
    }
     
    
    

    五、延迟加载源码剖析 -- invoke方法执行

    @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);
        } 
    }
    

    你可能感兴趣的:(Mybatis源码剖析 -- 延迟加载)