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

月圓吖發表於2020-11-26

該系列文件是本人在學習 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 原始碼分析》

相關文章