Mybatis源碼剖析 -- 延遲加載

一、什么是延遲加載

  1. 在開發(fā)過程中,假設有一個用戶信息類,映射多個訂單信息類
    • 立即加載:如果每次加載用戶信息的同時就加載這個用戶下的所有訂單信息,那么這就叫做立即加載
    • 延遲加載:查詢用戶信息的時候僅僅只查詢用戶信息,等什么時候需要用到其訂單信息的時候再去查詢這個用戶下的所有訂單信息,這就叫延遲加載
  2. 舉個例子
    • 問題
      在一對多中,當我們有?個用戶,它有個100個訂單
      在查詢用戶的時候,要不要把關聯(lián)的訂單查出來?
      在查詢訂單的時候,要不要把關聯(lián)的用戶查出來?
    • 回答
      在查詢用戶時,用戶下的訂單應該是什么時候用,什么時候查詢
      在查詢訂單時,訂單所屬的用戶信息應該是隨著訂單?起查詢出來的
  3. 延遲加載
    就是在需要用到數(shù)據(jù)時才進行加載,不需要用到數(shù)據(jù)時就不加載數(shù)據(jù)。延遲加載也稱懶加載。
    • 優(yōu)點
      先從單表查詢,需要時再從關聯(lián)表去關聯(lián)查詢,大大提高數(shù)據(jù)庫性能
    • 缺點
      因為只有當需要用到數(shù)據(jù)時,才會進行數(shù)據(jù)庫查詢,這樣在大批量數(shù)據(jù)查詢時,因為查詢工作也要消耗時間,所以可能造成用戶等待時間變長,造成用戶體驗下降
    • 在多表中
      ?對多,多對多:通常情況下采用延遲加載
      ?對?(多對?):通常情況下采用立即加載
    • 注意
      延遲加載是基于嵌套查詢來實現(xiàn)的

二、延遲加載的應用

  1. 局部延遲加載
    在 association 和 collection 標簽中都有?個 fetchType 屬性,通過修改它的值,可以修改局部的加載策略
    • 懶加載策略:fetchType="lazy"
    • 立即加載策略:fetchType="eager"
  2. 全局延遲加載
    在 Mybatis 的核心配置文件中可以使用 setting 標簽修改全局的加載策略
    <settings>
        <!--開啟全局延遲加載功能-->
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>
    
  3. 注意:局部的加載策略 高于 全局的加載策略

三、延遲加載的實現(xiàn)原理

其實延遲加載的底層原理:還是動態(tài)代理,通過代理攔截到指定方法,執(zhí)行數(shù)據(jù)加載

  • 使用 CGLIB 或 Javassist(默認)創(chuàng)建目標對象的代理對象
  • 當調用代理對象的延遲加載屬性的 getting 方法時,進入攔截器方法
  • 比如調用 a.getB().getName() 方法,進入攔截器的invoke(...) 方法,發(fā)現(xiàn) a.getB() 需要延遲加載時,那么就會單獨發(fā)送事先保存好的查詢關聯(lián) B對象的 SQL ,把 B 查詢出來,然后調用 a.setB(b) 方法,于是 a 對象 b 屬性就有值了,接著完成 a.getB().getName() 方法的調用

四、延遲加載源碼剖析 -- 創(chuàng)建代理對象

  1. Setting 配置加載
    /**
     * MyBatis 配置
     *
     * @author Clinton Begin
     */
    public class Configuration {
        /**
         * 當開啟時,任何方法的調用都會加載該對象的所有屬性。否則,每個屬性會按需加載(參考lazyLoadTriggerMethods)
         */
        protected boolean aggressiveLazyLoading;
    
        /**
         * 延遲加載觸發(fā)?法
         */
         protected Set<String> lazyLoadTriggerMethods = new HashSet<String> (Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
         /** 是否開啟延遲加載 */
         protected boolean lazyLoadingEnabled = false;
    
         /**
          * 延遲加載代理對象創(chuàng)建 Mybatis 的查詢結果是由 ResultSetHandler 接口的 handleResultSets() 方法處理的,ResultSetHandler 接口只有?個實現(xiàn),DefaultResultSetHandler,接下來看下延遲加載相關的?個核心的方法
          * 默認使?Javassist代理??
          * @param proxyFactory
          */
          public void setProxyFactory(ProxyFactory proxyFactory) {
              if (proxyFactory == null) {
                  proxyFactory = new JavassistProxyFactory();
              }
              this.proxyFactory = proxyFactory;
          }
    
          //省略...
    }
    
  2. 代理對象創(chuàng)建
    Mybatis 的查詢結果是由 ResultSetHandler 接口的handleResultSets()方法處理的,ResultSetHandler 接口只有?個實現(xiàn),DefaultResultSetHandler,接下來看下延遲加載相關的?個核心的方法
    // 創(chuàng)建映射后的結果對象
    private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
        // useConstructorMappings ,表示是否使用構造方法創(chuàng)建該結果對象。此處將其重置
        this.useConstructorMappings = false; // reset previous mapping result
        final List<Class<?>> constructorArgTypes = new ArrayList<>(); // 記錄使用的構造方法的參數(shù)類型的數(shù)組
        final List<Object> constructorArgs = new ArrayList<>(); // 記錄使用的構造方法的參數(shù)值的數(shù)組
        // 創(chuàng)建映射后的結果對象
        Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
        if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
            // 如果有內嵌的查詢,并且開啟延遲加載,則創(chuàng)建結果對象的代理對象
            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;
                }
            }
        }
        // 判斷是否使用構造方法創(chuàng)建該結果對象
        this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
        return resultObject;
    }
    
  3. 當設置了fetchType="lazy"時,會執(zhí)行configuration.getProxyFactory().createProxy(...),而 configuration.getProxyFactory()就是protected ProxyFactory proxyFactory = new JavassistProxyFactory();,其實就是 new 了一個 JavassistProxyFactory,所以說默認就是 JavassistProxyFactory 去生成代理對象
  4. 創(chuàng)建代理對象
    public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        final Class<?> type = target.getClass();
        // 創(chuàng)建 EnhancedResultObjectProxyImpl 對象
        EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
        // 創(chuàng)建代理對象
        Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
        // 將 target 的屬性,復制到 enhanced 中
        PropertyCopier.copyBeanProperties(type, target, enhanced);
        return enhanced;
    }
    
    static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        // 創(chuàng)建 javassist ProxyFactory 對象
        ProxyFactory enhancer = new ProxyFactory();
        // 設置父類
        enhancer.setSuperclass(type);
    
        // 根據(jù)情況,設置接口為 WriteReplaceInterface 。和序列化相關,可以無視
        try {
            type.getDeclaredMethod(WRITE_REPLACE_METHOD); // 如果已經(jīng)存在 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
        }
    
        // 創(chuàng)建代理對象
        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);
        }
    
        // 設置代理對象的執(zhí)行器
        ((Proxy) enhanced).setHandler(callback);
        return enhanced;
    }
    

五、延遲加載源碼剖析 -- invoke方法執(zhí)行

@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 方法,則執(zhí)行延遲加載
                    } else if (PropertyNamer.isGetter(methodName)) {
                        final String property = PropertyNamer.methodToProperty(methodName);
                        if (lazyLoader.hasLoader(property)) {
                            lazyLoader.load(property);
                        }
                    }
                }
            }
        }
        // 繼續(xù)執(zhí)行原方法
        return methodProxy.invoke(enhanced, args);
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    } 
}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 引用《mybatis技術內幕》3.3.5 嵌套查詢&延遲加載,的一段話 “延遲加載”的含義是:暫時不用的對象不會真...
    一只老實的程序猿閱讀 271評論 0 0
  • 引言 題外話:idea中使用ctrl+H可以看類集成關系圖,很實用 MyBatis使用簡單的 XML或注解用于配置...
    WANGGGGG閱讀 600評論 0 1
  • 延遲加載概念:需要用到數(shù)據(jù)時才進行加載,不需要用到數(shù)據(jù)時就不加載數(shù)據(jù),延遲加載也叫做懶加載。 優(yōu)點:先從單表查詢,...
    何佳陽閱讀 347評論 0 0
  • Mybatis的延遲加載 一、什么是延遲加載 延遲加載:就是在需要用到數(shù)據(jù)時才進行加載,不需要用到數(shù)據(jù)時就不加載數(shù)...
    一只程序汪閱讀 557評論 0 1
  • 一、思考一個問題 假設 Mybatis 一級緩存和二級緩存同時開啟,那么到底是生效一級緩存還是二級緩存呢? 答案:...
    Travis_Wu閱讀 267評論 0 1

友情鏈接更多精彩內容