1. 程式人生 > >精盡MyBatis原始碼分析 - SQL執行過程(四)之延遲載入

精盡MyBatis原始碼分析 - SQL執行過程(四)之延遲載入

> 該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋([Mybatis原始碼分析 GitHub 地址](https://github.com/liu844869663/mybatis-3)、[Mybatis-Spring 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring)、[Spring-Boot-Starter 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-boot-starter))進行閱讀 > > MyBatis 版本:3.5.2 > > MyBatis-Spring 版本:2.0.3 > > MyBatis-Spring-Boot-Starter 版本:2.1.4 ## MyBatis的SQL執行過程 在前面一系列的文件中,我已經分析了 MyBatis 的基礎支援層以及整個的初始化過程,此時 MyBatis 已經處於就緒狀態了,等待使用者發號施令了 那麼接下來我們來看看它執行SQL的整個過程,該過程比較複雜,涉及到二級快取,將返回結果轉換成 Java 物件以及延遲載入等等處理過程,這裡將一步一步地進行分析: - [**《SQL執行過程(一)之Executor》**](https://www.cnblogs.com/lifullmoon/p/14015111.html) - [**《SQL執行過程(二)之StatementHandler》**](https://www.cnblogs.com/lifullmoon/p/14015149.html) - [**《SQL執行過程(三)之ResultSetHandler》**](https://www.cnblogs.com/lifullmoon/p/14015178.html) - [**《SQL執行過程(四)之延遲載入》**](https://www.cnblogs.com/lifullmoon/p/14015223.html) MyBatis中SQL執行的整體過程如下圖所示:
在 SqlSession 中,會將執行 SQL 的過程交由`Executor`執行器去執行,過程大致如下: 1. 通過`DefaultSqlSessionFactory`建立與資料庫互動的 `SqlSession` “會話”,其內部會建立一個`Executor`執行器物件 2. 然後`Executor`執行器通過`StatementHandler`建立對應的`java.sql.Statement`物件,並通過`ParameterHandler`設定引數,然後執行資料庫相關操作 3. 如果是資料庫**更新**操作,則可能需要通過`KeyGenerator`先設定自增鍵,然後返回受影響的行數 4. 如果是資料庫**查詢**操作,則需要將資料庫返回的`ResultSet`結果集物件包裝成`ResultSetWrapper`,然後通過`DefaultResultSetHandler`對結果集進行對映,最後返回 Java 物件 上面還涉及到**一級快取**、**二級快取**和**延遲載入**等其他處理過程 ## SQL執行過程(四)之延遲載入 在前面SQL執行過程一系列的文件中,已經詳細地分析了在 MyBatis 的SQL執行過程中,SqlSession 會話將資料庫相關操作交由 Executor 執行器去完成,通過 StatementHandler 去執行資料庫的操作,並獲取到資料庫的執行結果,如果是查詢結果則通過 DefaultResultSetHandler 對結果集進行對映,轉換成 Java 物件 其中 MyBatis 也提供了延遲載入的功能,當呼叫實體類需要延遲載入的屬性的 getter 方法時,才會觸發其對應的子查詢,獲取到查詢結果,設定該物件的屬性值 在上一篇[**《SQL執行過程(三)之ResultSetHandler》**](https://www.cnblogs.com/lifullmoon/p/14015178.html)文件中講到 1. 如果存在巢狀子查詢且需要延遲載入,則會通過`ProxyFactory`動態代理工廠,為返回結果的例項物件建立一個動態代理物件(**Javassist**),也就是說返回結果實際上是一個動態代理物件 可以回到上一篇文件的**4.2.1createResultObject方法**小節第`4`步看看 ```java resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs; ``` 2. 後續屬性對映的過程中,如果該屬性是巢狀子查詢並且需要延遲載入,則會建立一個`ResultLoader`物件新增到上面的`ResultLoaderMap`物件`lazyLoader`中 可以回到上一篇文件的**4.2.4.2getNestedQueryMappingValue方法**小節第`6`步看看 ```java final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql); if (propertyMapping.isLazy()) { // <6.2> 如果要求延遲載入,則延遲載入 // <6.2.1> 如果該屬性配置了延遲載入,則將其新增到 `ResultLoader.loaderMap` 中,等待真正使用時再執行巢狀查詢並得到結果物件 lazyLoader.addLoader(property, metaResultObject, resultLoader); // <6.2.2> 返回延遲載入佔位符 value = DEFERRED; } else { // <6.3> 如果不要求延遲載入,則直接執行載入對應的值 value = resultLoader.loadResult(); } ``` 那麼接下來我們來看看 MyBatis 中的**延遲載入**是如何實現的 ### ResultLoader `org.apache.ibatis.executor.loader.ResultLoader`:延遲載入的載入器,在上面你可以看到需要延遲載入的屬性會被封裝成該物件 #### 構造方法 ```java public class ResultLoader { /** * 全域性配置物件 */ protected final Configuration configuration; /** * 執行器 */ protected final Executor executor; /** * MappedStatement 查詢物件 */ protected final MappedStatement mappedStatement; /** * 查詢的引數物件 */ protected final Object parameterObject; /** * 目標的型別,返回結果的 Java Type */ protected final Class targetType; /** * 例項工廠 */ protected final ObjectFactory objectFactory; protected final CacheKey cacheKey; /** * SQL 相關資訊 */ protected final BoundSql boundSql; /** * 結果抽取器 */ protected final ResultExtractor resultExtractor; /** * 建立 ResultLoader 物件時,所在的執行緒的 id */ protected final long creatorThreadId; /** * 是否已經載入 */ protected boolean loaded; /** * 查詢的結果物件 */ protected Object resultObject; public ResultLoader(Configuration config, Executor executor, MappedStatement mappedStatement, Object parameterObject, Class targetType, CacheKey cacheKey, BoundSql boundSql) { this.configuration = config; this.executor = executor; this.mappedStatement = mappedStatement; this.parameterObject = parameterObject; this.targetType = targetType; this.objectFactory = configuration.getObjectFactory(); this.cacheKey = cacheKey; this.boundSql = boundSql; this.resultExtractor = new ResultExtractor(configuration, objectFactory); this.creatorThreadId = Thread.currentThread().getId(); } } ``` 主要包含以下資訊: - `executor`:執行器 - `mappedStatement`:查詢語句的MappedStatement物件 - `parameterObject`:子查詢的入參 - `targetType`:返回結果的Java Type - `boundSql`:SQL相關資訊 - `resultExtractor`:查詢結果的抽取器 - `loaded`:是否已經載入 #### loadResult方法 `loadResult()`方法,延遲載入的執行器的執行方法,獲取到查詢結果,並提取出結果,方法如下: ```java public Object loadResult() throws SQLException { // <1> 查詢結果 List list = selectList(); // <2> 提取結果 resultObject = resultExtractor.extractObjectFromList(list, targetType); // <3> 返回結果 return resultObject; } ``` #### selectList方法 `selectList()`方法,執行延遲載入對應的子查詢,獲取到查詢結果,方法如下: ```java private List selectList() throws SQLException { // <1> 獲得 Executor 物件 Executor localExecutor = executor; if (Thread.currentThread().getId() != this.creatorThreadId || localExecutor.isClosed()) { // 建立一個的 Executor 物件,保證執行緒安全 localExecutor = newExecutor(); } try { // <2> 執行查詢 return localExecutor.query(mappedStatement, parameterObject, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, cacheKey, boundSql); } finally { // <3> 關閉 Executor 物件 if (localExecutor != executor) { localExecutor.close(false); } } } ``` 1. 獲得 Executor 執行器,如果當前執行緒不是建立 ResultLoader 物件時所在的執行緒的,或者這個執行器被關閉了,那麼需要呼叫`newExecutor()`方法建立一個新的執行器 2. 通過該執行器進行資料的查詢,並返回查詢結果 3. 如果這個執行器是新建立的,則需要關閉它 #### newExecutor方法 `newExecutor()`方法,建立一個新的Executor執行器用於執行延遲載入的子查詢,執行完後需要關閉,方法如下: ```java private Executor newExecutor() { // 校驗 environment final Environment environment = configuration.getEnvironment(); if (environment == null) { throw new ExecutorException("ResultLoader could not load lazily. Environment was not configured."); } // 校驗 DataSource final DataSource ds = environment.getDataSource(); if (ds == null) { throw new ExecutorException("ResultLoader could not load lazily. DataSource was not configured."); } // 建立 Transaction 物件 final TransactionFactory transactionFactory = environment.getTransactionFactory(); final Transaction tx = transactionFactory.newTransaction(ds, null, false); // 建立 Executor 物件 return configuration.newExecutor(tx, ExecutorType.SIMPLE); } ``` ### ResultExtractor `org.apache.ibatis.executor.ResultExtractor`:結果提取器,用於提取延遲載入對應的子查詢的查詢結果,轉換成Java物件,程式碼如下: ```java public class ResultExtractor { /** * 全域性配置物件 */ private final Configuration configuration; /** * 例項工廠 */ private final ObjectFactory objectFactory; public ResultExtractor(Configuration configuration, ObjectFactory objectFactory) { this.configuration = configuration; this.objectFactory = objectFactory; } /** * 從 list 中,提取結果 * * @param list list * @param targetType 結果型別 * @return 結果 */ public Object extractObjectFromList(List list, Class targetType) { Object value = null; /* * 從查詢結果中抽取資料轉換成目標型別 */ if (targetType != null && targetType.isAssignableFrom(list.getClass())) { // <1> 場景1,List 型別 // 直接返回 value = list; } else if (targetType != null && objectFactory.isCollection(targetType)) { // <2> 場景2,集合型別 // <2.1> 建立集合的例項物件 value = objectFactory.create(targetType); // <2.2> 將結果新增到其中 MetaObject metaObject = configuration.newMetaObject(value); // <2.3> 將查詢結果全部新增到集合物件中 metaObject.addAll(list); } else if (targetType != null && targetType.isArray()) { // <3> 場景3,陣列型別 // <3.1> 獲取陣列的成員型別 Class arrayComponentType = targetType.getComponentType(); // <3.2> 建立陣列物件,並設定大小 Object array = Array.newInstance(arrayComponentType, list.size()); if (arrayComponentType.isPrimitive()) { // <3.3> 如果是基本型別 for (int i = 0; i < list.size(); i++) { // 一個一個新增到陣列中 Array.set(array, i, list.get(i)); } value = array; } else { // <3.4> 將 List 轉換成 Array value = list.toArray((Object[]) array); } } else { // <4> 場景4 if (list != null && list.size() > 1) { throw new ExecutorException("Statement returned more than one row, where no more than one was expected."); } else if (list != null && list.size() == 1) { // 取首個結果 value = list.get(0); } } return value; } } ``` 從`List list`查詢結果提取資料,轉換成目標型別,有以下四種場景: 1. List型別,則直接返回 2. 集合型別,則為該集合型別建立一個例項物件,並把`list`全部新增到該物件中,然後返回 3. 陣列型別 1. 獲取陣列的成員型別 2. 建立陣列物件,並設定大小 3. 如果是基本型別則一個一個新增到陣列中,否則直接將`list`轉換成陣列,然後返回 4. 其他型別,也就是一個實體類了,直接獲取`list`中的第一個元素返回(如果`list`集合的個數大於1則丟擲異常) ### ResultLoaderMap `org.apache.ibatis.executor.loader.ResultLoaderMap`:用於儲存某個物件中所有的延遲載入 #### 構造方法 ```java public class ResultLoaderMap { /** * 用於延遲載入的載入器 * key:屬性名稱 * value:ResultLoader 載入器的封裝物件 LoadPair */ private final Map loaderMap = new HashMap<>(); } ``` #### addLoader方法 `addLoader(String property, MetaObject metaResultObject, ResultLoader resultLoader)`方法,用於新增一個需要延遲載入屬性 入參分別表示:需要延遲載入的屬性名稱、該屬性所在的Java物件(也就是查詢返回的結果物件)、延遲載入對應的載入器,方法如下: ```java public void addLoader(String property, MetaObject metaResultObject, ResultLoader resultLoader) { // 獲取第一個屬性名稱 String upperFirst = getUppercaseFirstProperty(property); if (!upperFirst.equalsIgnoreCase(property) && loaderMap.containsKey(upperFirst)) { throw new ExecutorException("省略..."); } loaderMap.put(upperFirst, new LoadPair(property, metaResultObject, resultLoader)); } ``` 1. 如果`property`屬性名稱包含`.`點,且最前面一部分已經有對應的延遲載入物件了,則出現重複新增,需要丟擲異常 2. 將入參資訊封裝成`LoadPair`物件,並放入`loaderMap`中 > 關於`LoadPair`,是ResultLoaderMap的一個內部類,裡面有對序列化進行處理,最後還是呼叫`ResultLoader`的`load()`方法,這裡就不列出來了 #### load方法 `load(String property)`方法,用於觸發該屬性的延遲載入,方法如下: ```java public boolean load(String property) throws SQLException { LoadPair pair = loaderMap.remove(property.toUpperCase(Locale.ENGLISH)); if (pair != null) { pair.load(); return true; } return false; } ``` 1. 先將該屬性對應的延遲載入從`loaderMap`集合中刪除 2. 然後呼叫`LoadPair`的`load()`方法,觸發延遲載入,並設定查詢結果設定到物件的屬性中 #### loadAll方法 `loadAll() `方法,用於觸發所有還沒載入的延遲載入,方法如下: ```java public void loadAll() throws SQLException { final Set methodNameSet = loaderMap.keySet(); String[] methodNames = methodNameSet.toArray(new String[methodNameSet.size()]); for (String methodName : methodNames) { load(methodName); } } ``` ### ProxyFactory `org.apache.ibatis.executor.loader.ProxyFactory`:動態代理工廠介面 ```java public interface ProxyFactory { default void setProperties(Properties properties) { // NOP } Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs); } ``` - 就定義了一個`createProxy`建立動態代理物件的方法,交由不同的子類去實現 實現類如下圖所示:
回到Configuration全域性配置物件中,你會發現預設使用的是`JavassistProxyFactory`實現類 ```java // Configuration.java protected ProxyFactory proxyFactory = new JavassistProxyFactory(); ``` ### JavassistProxyFactory `org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory`:實現ProxyFactory介面,基於`javassist`(一個開源的分析、編輯和建立Java位元組碼的類庫)建立動態代理物件 #### 構造方法 ```java public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory { private static final String FINALIZE_METHOD = "finalize"; private static final String WRITE_REPLACE_METHOD = "writeReplace"; public JavassistProxyFactory() { try { // 載入 javassist.util.proxy.ProxyFactory 類 Resources.classForName("javassist.util.proxy.ProxyFactory"); } catch (Throwable e) { throw new IllegalStateException( "Cannot enable lazy loading because Javassist is not available. Add Javassist to your classpath.", e); } } } ``` - `載入 javassist.util.proxy.ProxyFactory` 類 #### createProxy方法 `createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs)`方法 建立動態代理物件的入口,方法如下: ```java @Override public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) { // <1> 建立動態代例項物件 return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); } ``` 內部直接呼叫`EnhancedResultObjectProxyImpl`的`createProxy`方法 #### crateProxy靜態方法 `crateProxy(Class type, MethodHandler callback, List> constructorArgTypes, List constructorArgs) `方法 用於建立一個動態代理的例項物件,並設定`MethodHandler`方法增強器,方法如下: ```java static Object crateProxy(Class type, MethodHandler callback, List> constructorArgTypes, List constructorArgs) { // <3.1> 建立 ProxyFactory 動態代理物件工廠 ProxyFactory enhancer = new ProxyFactory(); // <3.2> 設定父類,需要代理的類物件 enhancer.setSuperclass(type); // <3.3> 和序列化相關 try { // 獲取需要代理的類物件中的 writeReplace 方法 type.getDeclaredMethod(WRITE_REPLACE_METHOD); // ObjectOutputStream will call writeReplace of objects returned by writeReplace if (LogHolder.log.isDebugEnabled()) { LogHolder.log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this"); } } catch (NoSuchMethodException e) { // 如果沒有 writeReplace 方法,則設定介面為 WriteReplaceInterface enhancer.setInterfaces(new Class[] { WriteReplaceInterface.class }); } 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 { // <3.4> 建立動態代理例項物件 enhanced = enhancer.create(typesArray, valuesArray); } catch (Exception e) { throw new ExecutorException("Error creating lazy proxy. Cause: " + e, e); } // <3.5> 設定動態代理例項物件的 MethodHandler 方法增強器 ((Proxy) enhanced).setHandler(callback); return enhanced; } ``` 1. 建立 ProxyFactory 動態代理物件工廠 2. 設定父類,需要代理的類物件 3. 設定和序列化相關配置 4. 建立動態代理例項物件,傳入代理類物件的構造方法的入參型別陣列和入引數組 5. 設定動態代理例項物件的`MethodHandler`方法增強器 #### EnhancedResultObjectProxyImpl JavassistProxyFactory的內部類,動態代理物件的`MethodHandler`方法增強器 ##### 構造方法 ```java private static class EnhancedResultObjectProxyImpl implements MethodHandler { private final Class type; private final ResultLoaderMap lazyLoader; /** * 開啟時,任一方法的呼叫都會載入該物件的所有延遲載入屬性,預設false */ private final boolean aggressive; private final Set lazyLoadTriggerMethods; private final ObjectFactory objectFactory; private final List> constructorArgTypes; private final List constructorArgs; private EnhancedResultObjectProxyImpl(Class type, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) { this.type = type; this.lazyLoader = lazyLoader; this.aggressive = configuration.isAggressiveLazyLoading(); this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods(); this.objectFactory = objectFactory; this.constructorArgTypes = constructorArgTypes; this.constructorArgs = constructorArgs; } } ``` - 我們主要看到`ResultLoaderMap lazyLoader`屬性,裡面儲存了需要延遲載入的屬性和載入器 ##### createProxy方法 `createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs)`方法 建立動態代理例項物件,方法如下: ```java public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) { final Class type = target.getClass(); // <2> 建立方法的增強器 EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); // <3> 建立動態代理例項物件,設定方法的增強器 Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs); // <4> 將 target 的屬性值複製到 enhanced 動態代例項物件中 PropertyCopier.copyBeanProperties(type, target, enhanced); return enhanced; } ``` 這個方法在`JavassistProxyFactory`的`createProxy`方法被呼叫,然後自己內部又呼叫`JavassistProxyFactory`的靜態`createProxy`方法,這裡我已經按序號標明瞭步驟 1. 建立`EnhancedResultObjectProxyImpl`方法的增強器`callback` 2. 建立動態代理例項物件,並設定方法的增強器為`callback`,呼叫的是上面的靜態`createProxy`方法 3. 將`target`的屬性值複製到`enhanced`動態代例項物件中 ##### invoke方法 `javassist.util.proxy.MethodHandler`方法增強器的而實現方法,代理物件的方法都會進入這個方法 ```java @Override public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable { final String methodName = method.getName(); try { synchronized (lazyLoader) { // <1> 如果方法名為 writeReplace,和序列化相關 if (WRITE_REPLACE_METHOD.equals(methodName)) { Object original; if (constructorArgTypes.isEmpty()) { original = objectFactory.create(type); } else { original = objectFactory.create(type, constructorArgTypes, constructorArgs); } // 從動態代理例項物件中複製屬性值到 original 中 PropertyCopier.copyBeanProperties(type, enhanced, original); if (lazyLoader.size() > 0) { return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory,constructorArgTypes, constructorArgs); } else { return original; } } else { // <2> 載入延遲載入的屬性 if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) { // <2.1> 如果開啟了任一方法的呼叫都會載入該物件的所有延遲載入屬性,或者是 "equals", "clone", "hashCode", "toString" 其中的某個方法 if (aggressive || lazyLoadTriggerMethods.contains(methodName)) { // 載入所有延遲載入的屬性 lazyLoader.loadAll(); } else if (PropertyNamer.isSetter(methodName)) { // <2.2> 如果為 setter 方法,從需要延遲載入屬性列表中移除 final String property = PropertyNamer.methodToProperty(methodName); lazyLoader.remove(property); } else if (PropertyNamer.isGetter(methodName)) { // <2.3> 如果呼叫了 getter 方法,則執行延遲載入,從需要延遲載入屬性列表中移除 final String property = PropertyNamer.methodToProperty(methodName); if (lazyLoader.hasLoader(property)) { // 載入該屬性值 lazyLoader.load(property); } } } } } // <3> 繼續執行原方法 return methodProxy.invoke(enhanced, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } ``` 先給`ResultLoaderMap lazyLoader`新增`synchronized`關鍵字,保證執行緒安全 1. 如果加強的方法是`writeReplace`,則進行一些序列化相關的操作,暫不分析,其實是沒看懂~ 2. 如果`lazyLoader`中有延遲載入的屬性,並且加強的方法不是`finalize` 1. 如果開啟了任一方法的呼叫都會載入該物件的所有延遲載入屬性,或者是`equals clone hashCode toString`其中的某個方法,則觸發所有的延遲載入 2. 否則,如果是屬性的setter方法,則從`lazyLoader`中將該屬性的延遲載入刪除(如果存在),因為主動設定了這個屬性值,則需要取消該屬性的延遲載入 3. 否則,如果是屬性的getter方法,則執行延遲載入(會將結果設定到該物件的這個屬性中),裡面也會從`lazyLoader`中將該屬性的延遲載入刪除 3. 繼續執行原方法 到這裡,延遲載入已經實現了 ### CglibProxyFactory `org.apache.ibatis.executor.loader.cglib.CglibProxyFactory`:實現ProxyFactory介面,基於`cglib`(一個強大的,高效能,高質量的Code生成類庫,它可以在執行期擴充套件Java類與實現Java介面)建立動態代理物件 實現方式和`JavassistProxyFactory`類似,這裡就不進行分析了,感興趣的可以看一下 ### 總結 本文分析了 MyBatis 中延遲載入的實現方法,在 DefaultResultSetHandler 對映結果集的過程中,如果返回物件有屬性是巢狀子查詢,且需要延遲載入,則通過`JavassistProxyFactory`為返回結果建立一個動態代理物件,並設定`MethodHandler`方法增強器為`EnhancedResultObjectProxyImpl`物件 其中傳入`ResultLoaderMap`物件,該物件儲存了這個結果物件中所有的`ResultLoader`延遲載入 在`EnhancedResultObjectProxyImpl`中攔截結果物件的方法,進行增強處理,通過`ResultLoader`延遲載入器獲取到該屬性值,然後從`ResultLoaderMap`中刪除,在你呼叫該屬性的getter方法時才載入資料,這樣就實現了延遲載入 好了,對於 MyBatis 的整個 SQL 執行過程我們已經全部分析完了,其中肯定有不對或者迷惑的地方,歡迎指正!!!感謝大家的閱讀!!!:smile::smile::smile: > 參考文章:**芋道原始碼**[《精盡 MyBatis 原始碼分析》](http://svip.iocoder.cn/categories/M