精盡MyBatis源碼分析 – SQL執行過程(四)之延遲載入

該系列文檔是本人在學習 Mybatis 的源碼過程中總結下來的,可能對讀者不太友好,請結合我的源碼注釋(Mybatis源碼分析 GitHub 地址Mybatis-Spring 源碼分析 GitHub 地址Spring-Boot-Starter 源碼分析 GitHub 地址)進行閱讀

MyBatis 版本:3.5.2

MyBatis-Spring 版本:2.0.3

MyBatis-Spring-Boot-Starter 版本:2.1.4

MyBatis的SQL執行過程

在前面一系列的文檔中,我已經分析了 MyBatis 的基礎支援層以及整個的初始化過程,此時 MyBatis 已經處於就緒狀態了,等待使用者發號施令了

那麼接下來我們來看看它執行SQL的整個過程,該過程比較複雜,涉及到二級快取,將返回結果轉換成 Java 對象以及延遲載入等等處理過程,這裡將一步一步地進行分析:

MyBatis中SQL執行的整體過程如下圖所示:

SQLExecuteProcess

在 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》文檔中講到

  1. 如果存在嵌套子查詢且需要延遲載入,則會通過ProxyFactory動態代理工廠,為返回結果的實例對象創建一個動態代理對象(Javassist),也就是說返回結果實際上是一個動態代理對象

    可以回到上一篇文檔的4.2.1createResultObject方法小節第4步看看

    resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, 
                                                               objectFactory, constructorArgTypes, constructorArgs;
    
  2. 後續屬性映射的過程中,如果該屬性是嵌套子查詢並且需要延遲載入,則會創建一個ResultLoader對象添加到上面的ResultLoaderMap對象lazyLoader

    可以回到上一篇文檔的4.2.4.2getNestedQueryMappingValue方法小節第6步看看

    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:延遲載入的載入器,在上面你可以看到需要延遲載入的屬性會被封裝成該對象

構造方法

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()方法,延遲載入的執行器的執行方法,獲取到查詢結果,並提取出結果,方法如下:

public Object loadResult() throws SQLException {
    // <1> 查詢結果
    List<Object> list = selectList();
    // <2> 提取結果
    resultObject = resultExtractor.extractObjectFromList(list, targetType);
    // <3> 返回結果
    return resultObject;
}

selectList方法

selectList()方法,執行延遲載入對應的子查詢,獲取到查詢結果,方法如下:

	private <E> List<E> 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執行器用於執行延遲載入的子查詢,執行完後需要關閉,方法如下:

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對象,程式碼如下:

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<Object> 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<Object> list查詢結果提取數據,轉換成目標類型,有以下四種場景:

  1. List類型,則直接返回

  2. 集合類型,則為該集合類型創建一個實例對象,並把list全部添加到該對象中,然後返回

  3. 數組類型

    1. 獲取數組的成員類型
    2. 創建數組對象,並設置大小
    3. 如果是基本類型則一個一個添加到數組中,否則直接將list轉換成數組,然後返回
  4. 其他類型,也就是一個實體類了,直接獲取list中的第一個元素返回(如果list集合的個數大於1則拋出異常)

ResultLoaderMap

org.apache.ibatis.executor.loader.ResultLoaderMap:用於保存某個對象中所有的延遲載入

構造方法

public class ResultLoaderMap {
    /**
   	 * 用於延遲載入的載入器
   	 * key:屬性名稱
   	 * value:ResultLoader 載入器的封裝對象 LoadPair
   	 */
	private final Map<String, LoadPair> loaderMap = new HashMap<>();
}

addLoader方法

addLoader(String property, MetaObject metaResultObject, ResultLoader resultLoader)方法,用於添加一個需要延遲載入屬性

入參分別表示:需要延遲載入的屬性名稱、該屬性所在的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的一個內部類,裡面有對序列化進行處理,最後還是調用ResultLoaderload()方法,這裡就不列出來了

load方法

load(String property)方法,用於觸發該屬性的延遲載入,方法如下:

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. 然後調用LoadPairload()方法,觸發延遲載入,並設置查詢結果設置到對象的屬性中

loadAll方法

loadAll() 方法,用於觸發所有還沒載入的延遲載入,方法如下:

public void loadAll() throws SQLException {
    final Set<String> methodNameSet = loaderMap.keySet();
    String[] methodNames = methodNameSet.toArray(new String[methodNameSet.size()]);
    for (String methodName : methodNames) {
        load(methodName);
    }
}

ProxyFactory

org.apache.ibatis.executor.loader.ProxyFactory:動態代理工廠介面

public interface ProxyFactory {

  default void setProperties(Properties properties) {
    // NOP
  }

  Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, 
                     ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, 
                     List<Object> constructorArgs);
}
  • 就定義了一個createProxy創建動態代理對象的方法,交由不同的子類去實現

實現類如下圖所示:

ProxyFactory

回到Configuration全局配置對象中,你會發現默認使用的是JavassistProxyFactory實現類

// Configuration.java
protected ProxyFactory proxyFactory = new JavassistProxyFactory();

JavassistProxyFactory

org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory:實現ProxyFactory介面,基於javassist(一個開源的分析、編輯和創建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<Class<?>> constructorArgTypes, List<Object> constructorArgs)方法

創建動態代理對象的入口,方法如下:

@Override
public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration,
        ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
  	// <1> 創建動態代實例對象
    return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, 
                                                     objectFactory, constructorArgTypes, constructorArgs);
}

內部直接調用EnhancedResultObjectProxyImplcreateProxy方法

crateProxy靜態方法

crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) 方法

用於創建一個動態代理的實例對象,並設置MethodHandler方法增強器,方法如下:

static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes,
        List<Object> 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方法增強器

構造方法
private static class EnhancedResultObjectProxyImpl implements MethodHandler {

    private final Class<?> type;
    private final ResultLoaderMap lazyLoader;
    /**
     * 開啟時,任一方法的調用都會載入該對象的所有延遲載入屬性,默認false
     */
    private final boolean aggressive;
    private final Set<String> lazyLoadTriggerMethods;
    private final ObjectFactory objectFactory;
    private final List<Class<?>> constructorArgTypes;
    private final List<Object> constructorArgs;

    private EnhancedResultObjectProxyImpl(Class<?> type, ResultLoaderMap lazyLoader, Configuration configuration,
            ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> 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<Class<?>> constructorArgTypes, List<Object> constructorArgs)方法

創建動態代理實例對象,方法如下:

public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration,
        ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> 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;
}

這個方法在JavassistProxyFactorycreateProxy方法被調用,然後自己內部又調用JavassistProxyFactory的靜態createProxy方法,這裡我已經按序號標明了步驟

  1. 創建EnhancedResultObjectProxyImpl方法的增強器callback
  2. 創建動態代理實例對象,並設置方法的增強器為callback,調用的是上面的靜態createProxy方法
  3. target的屬性值複製到enhanced動態代實例對象中
invoke方法

javassist.util.proxy.MethodHandler方法增強器的而實現方法,代理對象的方法都會進入這個方法

@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 執行過程我們已經全部分析完了,其中肯定有不對或者迷惑的地方,歡迎指正!!!感謝大家的閱讀!!!😄😄😄

參考文章:芋道源碼《精盡 MyBatis 源碼分析》