精盡MyBatis原始碼分析 - SQL執行過程(四)之延遲載入
阿新 • • 發佈:2020-11-26
> 該系列文件是本人在學習 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