精盡MyBatis原始碼分析 - SqlSession 會話與 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
## SqlSession會話與SQL執行入口
在前面一系列的文件中,已經詳細的介紹了 MyBatis 的初始化和執行 SQL 的過程,在執行 SQL 的過程中,還存在著一些疑問,例如其中一直提到的 SqlSession 會話在 MyBatis 中是如何被建立的?如何呼叫 Executor 執行器執行 SQL 的?
那麼接下來就關於上面兩個的兩個問題,一起來探討一下 MyBatis 中 SqlSession 會話的相關內容
先回顧一下[**《基礎支援層》**](https://www.cnblogs.com/lifullmoon/p/14014934.html)的**Binding模組**,每個`Mapper Interface`會有對應的`MapperProxyFactory`動態代理物件工廠,用於建立`MapperProxy`動態代理物件,Mapper 介面中的每個方法都有對應的`MapperMethod`物件,該物件會通過 SqlSession 會話執行資料操作
再來看到這張MyBatis整體圖:
在單獨使用 MyBatis 進行資料庫操作時,需要通過`SqlSessionFactoryBuilder`構建一個`SessionFactory`物件,然後通過它建立一個`SqlSession`會話進行資料庫操作
我們通常都會先呼叫`SqlSession`會話的`getMapper(Class mapper)`方法,為 Mapper 介面生成一個“實現類”(JDK動態代理物件),然後就可以通過它進行資料庫操作
### 示例
```java
// <1> 構建 SqlSessionFactory 物件
Reader reader = Resources.getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml");
// <2> 預設 DefaultSqlSessionFactory 物件
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
// <3> 獲得 SqlSession 物件,預設 DefaultSqlSession 物件
SqlSession sqlSession = sqlSessionFactory.openSession();
// <4> 獲得 Mapper 物件
final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
// <5> 執行查詢
final Object subject = mapper.getSubject(1);
```
### SqlSessionFactoryBuilder
`org.apache.ibatis.session.SqlSessionFactoryBuilder`:構建SqlSessionFactory工廠類,裡面定義了許多build過載方法,主要分為處理Reader和InputStream兩種檔案資源物件,程式碼如下:
```java
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
/**
* 構造 SqlSessionFactory 物件
*
* @param reader Reader 物件
* @param environment 環境
* @param properties Properties 變數
* @return SqlSessionFactory 物件
*/
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
/*
* <1> 建立 XMLConfigBuilder 物件
* 會生成一個 XPathParser,包含 Document 物件
* 會建立一個 Configuration 全域性配置物件
*/
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
/*
* <2> 解析 XML 檔案並配置到 Configuration 全域性配置物件中
* <3> 建立 DefaultSqlSessionFactory 物件
*/
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null);
}
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
```
在[**《MyBatis初始化(一)之載入mybatis-config.xml》**](https://www.cnblogs.com/lifullmoon/p/14015009.html)中已經分析了,這裡就不再贅述,就是根據檔案資源建立Configuration全域性配置物件,然後構建一個DefaultSqlSessionFactory物件
### DefaultSqlSessionFactory
`org.apache.ibatis.session.defaults.DefaultSqlSessionFactory`:實現 SqlSessionFactory 介面,預設的 SqlSessionFactory 實現類
#### openSession方法
`openSession`方法,建立一個`DefaultSqlSession`物件,如下:
```java
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
@Override
public SqlSession openSession(boolean autoCommit) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
}
@Override
public SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null, false);
}
@Override
public SqlSession openSession(TransactionIsolationLevel level) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
return openSessionFromDataSource(execType, level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
return openSessionFromDataSource(execType, null, autoCommit);
}
@Override
public SqlSession openSession(Connection connection) {
return openSessionFromConnection(configuration.getDefaultExecutorType(), connection);
}
@Override
public SqlSession openSession(ExecutorType execType, Connection connection) {
return openSessionFromConnection(execType, connection);
}
}
```
`openSession`有很多過載的方法,主要是提供以下幾種入參的支援:
| 型別 | 引數名稱 | 預設值 | 描述 |
| ------------------------- | ---------- | ------------------- | ------------------ |
| boolean | autoCommit | false | 事務是否自動提交 |
| ExecutorType | execType | ExecutorType.SIMPLE | Executor執行器型別 |
| TransactionIsolationLevel | level | 無 | 事務隔離級別 |
| java.sql.Connection | connection | 無 | 資料庫連線 |
內部直接呼叫`openSessionFromDataSource`私有方法,內部也需要呼叫`openSessionFromConnection`私有方法,如果存在`connection`入參,內部則直接呼叫`openSessionFromConnection`私有方法
#### openSessionFromDataSource方法
`openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit)`方法,用於建立一個`DefaultSqlSession`物件,方法如下:
```java
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,
boolean autoCommit) {
Transaction tx = null;
try {
// 獲得 Environment 物件
final Environment environment = configuration.getEnvironment();
// 建立 Transaction 物件
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 建立 Executor 物件
final Executor executor = configuration.newExecutor(tx, execType);
// 建立 DefaultSqlSession 物件
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
// 如果發生異常,則關閉 Transaction 物件
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
```
1. 獲得`Environment`當前環境物件
2. 通過`getTransactionFactoryFromEnvironment`方法,從 Environment 環境物件中`TransactionFactory`物件,用於建立一個`Transaction`事務
3. 然後再建立一個`Executor`執行器物件
4. 根據全域性配置物件、執行器和事務是否自動提交建立一個`DefaultSqlSession`物件
#### openSessionFromConnection方法
`openSessionFromConnection(ExecutorType execType, Connection connection)`方法,用於建立一個`DefaultSqlSession`物件
和上面的方法邏輯相同,只不過它的入參直接傳入了一個Connection資料庫連線,方法如下:
```java
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
// 獲得是否可以自動提交
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
autoCommit = true;
}
// 獲得 Environment 物件
final Environment environment = configuration.getEnvironment();
// 建立 Transaction 物件
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = transactionFactory.newTransaction(connection);
// 建立 Executor 物件
final Executor executor = configuration.newExecutor(tx, execType);
// 建立 DefaultSqlSession 物件
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
```
和上面的方法不同的是,建立`Transaction`事務物件時,傳入的引數直接是`Connection`資料庫連線物件
#### getTransactionFactoryFromEnvironment方法
`getTransactionFactoryFromEnvironment(Environment environment)`方法,用於建立一個`TransactionFactory`物件,在建立 SqlSessionFactory 時,就可以通過設定 Environment 的 DataSource 資料來源和 TransactionFactory 事務工廠來整合第三方資料來源和事務管理器,程式碼如下:
```java
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
// 情況一,建立 ManagedTransactionFactory 物件
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
// 情況二,使用 `environment` 中的
return environment.getTransactionFactory();
}
```
#### closeTransaction方法
`closeTransaction(Transaction tx)`方法,關閉事務,方法如下:
```java
private void closeTransaction(Transaction tx) {
if (tx != null) {
try {
tx.close();
} catch (SQLException ignore) {
// Intentionally ignore. Prefer previous error.
}
}
}
```
### DefaultSqlSession
`org.apache.ibatis.session.defaults.DefaultSqlSession`:實現 SqlSession 介面,預設的 SqlSession 實現類,呼叫 Executor 執行器,執行資料庫操作
#### 構造方法
```java
public class DefaultSqlSession implements SqlSession {
/**
* 全域性配置
*/
private final Configuration configuration;
/**
* 執行器物件
*/
private final Executor executor;
/**
* 是否自動提交事務
*/
private final boolean autoCommit;
/**
* 是否發生資料變更
*/
private boolean dirty;
/**
* Cursor 陣列
*/
private List> cursorList;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
}
```
#### select方法
執行資料庫查詢操作,提供了許多過載方法
```java
@Override
public void select(String statement, Object parameter, ResultHandler handler) {
select(statement, parameter, RowBounds.DEFAULT, handler);
}
@Override
public void select(String statement, ResultHandler handler) {
select(statement, null, RowBounds.DEFAULT, handler);
}
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public T selectOne(String statement) {
return this.selectOne(statement, null);
}
@Override
public T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException(
"Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
@Override
public Map selectMap(String statement, String mapKey) {
return this.selectMap(statement, null, mapKey, RowBounds.DEFAULT);
}
@Override
public Map selectMap(String statement, Object parameter, String mapKey) {
return this.selectMap(statement, parameter, mapKey, RowBounds.DEFAULT);
}
@Override
public Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
// <1> 執行查詢
final List extends V> list = selectList(statement, parameter, rowBounds);
// <2> 建立 DefaultMapResultHandler 物件
final DefaultMapResultHandler mapResultHandler = new DefaultMapResultHandler<>(mapKey,
configuration.getObjectFactory(), configuration.getObjectWrapperFactory(),
configuration.getReflectorFactory());
// <3> 建立 DefaultResultContext 物件
final DefaultResultContext context = new DefaultResultContext<>();
// <4> 遍歷查詢結果
for (V o : list) {
// 設定 DefaultResultContext 中
context.nextResultObject(o);
// 使用 DefaultMapResultHandler 處理結果的當前元素
mapResultHandler.handleResult(context);
}
// <5> 返回結果
return mapResultHandler.getMappedResults();
}
@Override
public List selectList(String statement) {
return this.selectList(statement, null);
}
@Override
public List selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// <1> 獲得 MappedStatement 物件
MappedStatement ms = configuration.getMappedStatement(statement);
// <2> 執行查詢
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
```
上面有很多的資料庫查詢方法,主要分為以下幾種:
- `select`:執行資料庫查詢操作,通過入參中的`ResultHandler`處理結果集,無返回結果
- `selectList`:執行資料庫查詢操作,返回List集合
- `selectOne`:呼叫`selectList`方法,執行資料庫查詢操作,最多隻能返回一條資料
- `selectMap`:呼叫`selectList`方法,執行資料庫查詢操作,通過`DefaultMapResultHandler`進行處理,將返回結果轉換成Map集合
可以看到通過Executor執行器的`query`方法執行查詢操作,可以回顧[**《SQL執行過程(一)之Executor》**](https://www.cnblogs.com/lifullmoon/p/14015111.html)中的內容
這裡會先呼叫`wrapCollection`方法對入參進行包裝(如果是集合型別)
#### update方法
執行資料庫更新操作
```java
@Override
public int insert(String statement) {
return insert(statement, null);
}
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int delete(String statement) {
return update(statement, null);
}
@Override
public int delete(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int update(String statement) {
return update(statement, null);
}
@Override
public int update(String statement, Object parameter) {
try {
// <1> 標記 dirty ,表示執行過寫操作
dirty = true;
// <2> 獲得 MappedStatement 物件
MappedStatement ms = configuration.getMappedStatement(statement);
// <3> 執行更新操作
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
```
`insert`和`delete`方法最終都是呼叫`update`方法,通過Executor執行器的`update`方法執行資料庫更新操作
這裡會先呼叫`wrapCollection`方法對入參進行包裝(如果是集合型別)
#### wrapCollection方法
`wrapCollection(final Object object)`方法,將集合型別的引數包裝成`StrictMap`物件,方法如下:
```java
public static class StrictMap extends HashMap {
private static final long serialVersionUID = -5741767162221585340L;
@Override
public V get(Object key) {
if (!super.containsKey(key)) {
throw new BindingException(
"Parameter '" + key + "' not found. Available parameters are " + this.keySet());
}
return super.get(key);
}
}
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {
// 如果是集合,則新增到 collection 中
StrictMap