精盡MyBatis源碼分析 – SQL執行過程(四)之延遲載入
- 2020 年 11 月 26 日
- 筆記
- mybatis, 源碼解析, 精盡MyBatis源碼分析
該系列文檔是本人在學習 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執行的整體過程如下圖所示:
在 SqlSession 中,會將執行 SQL 的過程交由Executor
執行器去執行,過程大致如下:
- 通過
DefaultSqlSessionFactory
創建與資料庫交互的SqlSession
「會話」,其內部會創建一個Executor
執行器對象 - 然後
Executor
執行器通過StatementHandler
創建對應的java.sql.Statement
對象,並通過ParameterHandler
設置參數,然後執行資料庫相關操作 - 如果是資料庫更新操作,則可能需要通過
KeyGenerator
先設置自增鍵,然後返回受影響的行數 - 如果是資料庫查詢操作,則需要將資料庫返回的
ResultSet
結果集對象包裝成ResultSetWrapper
,然後通過DefaultResultSetHandler
對結果集進行映射,最後返回 Java 對象
上面還涉及到一級快取、二級快取和延遲載入等其他處理過程
SQL執行過程(四)之延遲載入
在前面SQL執行過程一系列的文檔中,已經詳細地分析了在 MyBatis 的SQL執行過程中,SqlSession 會話將資料庫相關操作交由 Executor 執行器去完成,通過 StatementHandler 去執行資料庫的操作,並獲取到資料庫的執行結果,如果是查詢結果則通過 DefaultResultSetHandler 對結果集進行映射,轉換成 Java 對象
其中 MyBatis 也提供了延遲載入的功能,當調用實體類需要延遲載入的屬性的 getter 方法時,才會觸發其對應的子查詢,獲取到查詢結果,設置該對象的屬性值
在上一篇《SQL執行過程(三)之ResultSetHandler》文檔中講到
-
如果存在嵌套子查詢且需要延遲載入,則會通過
ProxyFactory
動態代理工廠,為返回結果的實例對象創建一個動態代理對象(Javassist),也就是說返回結果實際上是一個動態代理對象可以回到上一篇文檔的4.2.1createResultObject方法小節第
4
步看看resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs;
-
後續屬性映射的過程中,如果該屬性是嵌套子查詢並且需要延遲載入,則會創建一個
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 TypeboundSql
: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);
}
}
}
- 獲得 Executor 執行器,如果當前執行緒不是創建 ResultLoader 對象時所在的執行緒的,或者這個執行器被關閉了,那麼需要調用
newExecutor()
方法創建一個新的執行器 - 通過該執行器進行數據的查詢,並返回查詢結果
- 如果這個執行器是新創建的,則需要關閉它
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
查詢結果提取數據,轉換成目標類型,有以下四種場景:
-
List類型,則直接返回
-
集合類型,則為該集合類型創建一個實例對象,並把
list
全部添加到該對象中,然後返回 -
數組類型
- 獲取數組的成員類型
- 創建數組對象,並設置大小
- 如果是基本類型則一個一個添加到數組中,否則直接將
list
轉換成數組,然後返回
-
其他類型,也就是一個實體類了,直接獲取
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));
}
-
如果
property
屬性名稱包含.
點,且最前面一部分已經有對應的延遲載入對象了,則出現重複添加,需要拋出異常 -
將入參資訊封裝成
LoadPair
對象,並放入loaderMap
中關於
LoadPair
,是ResultLoaderMap的一個內部類,裡面有對序列化進行處理,最後還是調用ResultLoader
的load()
方法,這裡就不列出來了
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;
}
- 先將該屬性對應的延遲載入從
loaderMap
集合中刪除 - 然後調用
LoadPair
的load()
方法,觸發延遲載入,並設置查詢結果設置到對象的屬性中
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
創建動態代理對象的方法,交由不同的子類去實現
實現類如下圖所示:
回到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);
}
內部直接調用EnhancedResultObjectProxyImpl
的createProxy
方法
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;
}
- 創建 ProxyFactory 動態代理對象工廠
- 設置父類,需要代理的類對象
- 設置和序列化相關配置
- 創建動態代理實例對象,傳入代理類對象的構造方法的入參類型數組和入參數組
- 設置動態代理實例對象的
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;
}
這個方法在JavassistProxyFactory
的createProxy
方法被調用,然後自己內部又調用JavassistProxyFactory
的靜態createProxy
方法,這裡我已經按序號標明了步驟
- 創建
EnhancedResultObjectProxyImpl
方法的增強器callback
- 創建動態代理實例對象,並設置方法的增強器為
callback
,調用的是上面的靜態createProxy
方法 - 將
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
關鍵字,保證執行緒安全
-
如果加強的方法是
writeReplace
,則進行一些序列化相關的操作,暫不分析,其實是沒看懂~ -
如果
lazyLoader
中有延遲載入的屬性,並且加強的方法不是finalize
- 如果開啟了任一方法的調用都會載入該對象的所有延遲載入屬性,或者是
equals clone hashCode toString
其中的某個方法,則觸發所有的延遲載入 - 否則,如果是屬性的setter方法,則從
lazyLoader
中將該屬性的延遲載入刪除(如果存在),因為主動設置了這個屬性值,則需要取消該屬性的延遲載入 - 否則,如果是屬性的getter方法,則執行延遲載入(會將結果設置到該對象的這個屬性中),裡面也會從
lazyLoader
中將該屬性的延遲載入刪除
- 如果開啟了任一方法的調用都會載入該對象的所有延遲載入屬性,或者是
-
繼續執行原方法
到這裡,延遲載入已經實現了
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 源碼分析》