Mybatis【2.2】-- Mybatis關於建立SqlSession原始碼分析的幾點疑問?
阿新 • • 發佈:2020-11-28
> 程式碼直接放在Github倉庫【https://github.com/Damaer/Mybatis-Learning 】,可直接執行,就不佔篇幅了。
[TOC]
# 1.為什麼我們使用SQLSessionFactoryBuilder的時候不需要自己關閉流?
我們看我們的程式碼:
``` java
public class StudentDaoImpl implements IStudentDao {
private SqlSession sqlSession;
public void insertStu(Student student) {
try {
InputStream inputStream;
inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
sqlSession=sqlSessionFactory.openSession();
sqlSession.insert("insertStudent",student);
sqlSession.commit();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(sqlSession!=null){
sqlSession.close();
}
}
}
}
```
當我們使用`inputStream = Resources.getResourceAsStream("mybatis.xml");`的時候,我們並需要去關閉inputstream,我們可以檢視原始碼,首先看到`SqlSessionFactoryBuilder().build()`這個方法:
``` java
// 將inputstream傳遞進去,呼叫了另一個分裝的build()方法
public SqlSessionFactory build(InputStream inputStream) {
return this.build((InputStream)inputStream, (String)null, (Properties)null);
}
```
跟進去,我們再來看另一個build方法,裡面有一個finally模組,無論怎麼樣都會執行close方法,所以這就是為什麼我們在使用的時候為什麼不用關閉inputstream的原因:因為這個流是在finally程式碼塊中被關閉了。
``` java
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
// 關閉流
inputStream.close();
} catch (IOException var13) {
;
}
}
return var5;
}
```
# 2. Sqlsession是如何建立的?
語句裡面執行程式碼:使用`SQLSessionFactory`去開啟一個`session`,這裡的`session`我們可以初步理解為一個`sql`的會話,類似我們想要發信息給別人,肯定需要開啟一個和別人的會話。
``` java
sqlSession=sqlSessionFactory.openSession();
```
我們需要檢視原始碼,我們發現opensession是sqlSessionFactory的一個介面方法,sqlSessionFactory是一個介面。
``` java
public interface SqlSessionFactory {
// 在這裡只貼出了一個方法,其他的就不貼了
SqlSession openSession();
}
```
idea選中該方法,`ctrl + alt +B`,我們可以發現有DefaultSqlSessionFactory,和SqlSessionManager這兩個類實現了SqlSessionFactory這個介面
![](http://markdownpicture.oss-cn-qingdao.aliyuncs.com/18-6-2/75470738.jpg)
那麼我們需要跟進去DefaultSqlSessionFactory這個類的openSesseion方法,在裡面呼叫了一個封裝好的方法:openSessionFromDataSource()
``` java
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
```
當然在`DefaultSqlSessionFactory`這個類裡面還有一個方法,引數是autoCommit,也就是可以指定是否自動提交:
``` java
public SqlSession openSession(boolean autoCommit) {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit);
}
```
我們再跟進去原始碼,我們會發現有一個引數是`autoCommit`,也就是自動提交,我們可以看到上一步傳值是false,也就是不會自動提交,通過configuration(主配置)獲取environment(執行環境),然後通過environment(環境)開啟和獲取一個事務工廠,通過事務工廠獲取事務物件Transaction,通過事務物件建立一個執行器executor,Executor是一個介面,實現類有比如SimpleExecutor,BatchExecutor,ReuseExecutor,所以我們下面程式碼裡的execType,是指定它的型別,生成指定型別的Executor,把引用給介面物件,有了執行器之後就可以return一個DefaultSqlSession物件了。
``` java
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
// configuration是主配置檔案
Environment environment = this.configuration.getEnvironment();
// 獲取事務工廠,事務管理器可以使jdbc之類的
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
// 獲取事務物件Transaction
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 通過事務物件建立一個執行器executor
Executor executor = this.configuration.newExecutor(tx, execType);
// DefaultSqlSession是SqlSession實現類,建立一個DefaultSqlSession並返回
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
```
我們跟` var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);`這句程式碼,我們這是初始化函式賦值於各個成員變數,我們發現裡面有一個dirty成員,這是幹什麼用的呢?從名字上來講我們理解是髒的,這裡既然設定為false,那就是不髒的意思。那到底什麼是髒呢?**髒是指記憶體裡面的資料與資料庫裡面的資料存在不一致的問題,如果一致就是不髒的**
後面會解釋這個dirty的作用之處,到這裡一個SqlSession就建立完成了。
``` java
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
```
# 3.增刪改是怎麼執行的
我們使用到這句程式碼:
``` java
sqlSession.insert("insertStudent",student);
```
我們發現同樣是介面方法,上面我們知道SqlSession其實是DefaultSqlSession所實現的介面,那麼我們跟進去DefaultSqlSession的insert()方法,我們發現其實inset方法底層也是實現了update這個方法,同樣的delete方法在底層也是呼叫了update這個方法,**增,刪,改本質上都是改**。
``` java
public int insert(String statement, Object parameter) {
return this.update(statement, parameter);
}
public int update(String statement) {
return this.update(statement, (Object)null);
}
```
那麼我們現在跟進去update方法中,dirty變成ture,表明即將改資料,所以資料庫資料與記憶體中資料不一致了,statement是我們穿過來的id,這樣就可以通過id拿到statement的物件,然後就通過執行器執行修改的操作:
``` java
public int update(String statement, Object parameter) {
int var4;
try {
// dirty變成ture,表明資料和資料庫資料不一致,需要更新
this.dirty = true;
// 通過statement的id把statement從配置中拿到對映關係
MappedStatement ms = this.configuration.getMappedStatement(statement);
// 執行器執行修改的操作
var4 = this.executor.update(ms, this.wrapCollection(parameter));
} catch (Exception var8) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + var8, var8);
} finally {
ErrorContext.instance().reset();
}
return var4;
}
```
# 4.SqlSession.commit()為什麼可以提交事務(transaction)?
首先,我們使用到的原始碼,同樣選擇DefaultSqlSession這個介面的方法,我們發現commit裡面呼叫了另一個commit方法,傳進去一個false的值:
``` java
public void commit() {
this.commit(false);
}
```
我們跟進去,發現上面傳進去的false是變數force,裡面呼叫了一個`isCommitOrRollbackRequired(force)`方法,執行的結果返回給commit方法當引數。
``` java
public void commit(boolean force) {
try {
this.executor.commit(this.isCommitOrRollbackRequired(force));
// 提交之後dirty置為false,因為資料庫與記憶體的資料一致了。
this.dirty = false;
} catch (Exception var6) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + var6, var6);
} finally {
ErrorContext.instance().reset();
}
}
```
我們跟進去`isCommitOrRollbackRequired(force)`這個方法,這個方法從命名上是**需要提交還是回滾**的意思。在前面我們知道autoCommit是false,那麼取反之後就是true,關於dirty我們知道前面我們執行過insert()方法,insert的底層呼叫了update方法,將dirty置為true,表示即將修改資料,那我們知道`!this.autoCommit && this.dirty`的值就是true,那麼就短路了,所以整個表示式的值就是true。
``` java
private boolean isCommitOrRollbackRequired(boolean force) {
return !this.autoCommit && this.dirty || force;
}
```
返回上一層的,我們知道`this.isCommitOrRollbackRequired(force)`的返回值是true。
```
this.executor.commit(this.isCommitOrRollbackRequired(force));
```
跟進去commit方法,這個commit方法是一個介面方法,實現介面的有BaseExecutor,還有CachingExecutor,我們選擇BaseExecutor這個介面實現類:
``` java
// required是true
public void commit(boolean required) throws SQLException {
// 如果已經 關閉,那麼就沒有辦法提交,丟擲異常
if (this.closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
} else {
this.clearLocalCache();
this.flushStatements();
// 如果required是true,那麼就提交事務
if (required) {
this.transaction.commit();
}
}
}
```
# 5.為什麼sqlsession關閉就不需要回滾了?
假如我們在上面已經提交過了,那麼dirty的值就為false。我們使用的是`sqlSession.close();`,跟進去原始碼,同樣是介面,我們跟DefaoultSqlsession的方法,同樣呼叫了isCommitOrRollbackRequired()這個方法:
``` java
public void close() {
try {
this.executor.close(this.isCommitOrRollbackRequired(false));
this.dirty = false;
} finally {
ErrorContext.instance().reset();
}
}
```
我們跟進去isCommitOrRollbackRequired(false)這個方法,我們知道force傳進來的值是false,autoCommit是false(只要我們使用無參的`sqlSessionFactory.openSession();`),取反之後**!autoCommit**是true,但是dirty已經是false,所以`!this.autoCommit && this.dirty`的值是false,那麼force也是false,所以整一個表示式就是false:
``` java
private boolean isCommitOrRollbackRequired(boolean force) {
return !this.autoCommit && this.dirty || force;
}
```
我們返回上一層,executor.close()方法,引數是false:
``` java
this.executor.close(this.isCommitOrRollbackRequired(false));
```
跟進去close()方法,forceRollback的值是false,我們發現有一個`this.rollback(forceRollback)`:
``` java
public void close(boolean forceRollback) {
try {
try {
this.rollback(forceRollback);
} finally {
// 最後如果事務不為空,那麼我們就關閉事務
if (this.transaction != null) {
this.transaction.close();
}
}
} catch (SQLException var11) {
log.warn("Unexpected exception on closing transaction. Cause: " + var11);
} finally {
this.transaction = null;
this.deferredLoads = null;
this.localCache = null;
this.localOutputParameterCache = null;
this.closed = true;
}
}
```
我們跟進去rollback()這個方法,我們可以發現required是fasle,所以` this.transaction.rollback();`是不會執行的,這個因為我們在前面做了提交了,所以是不用回滾的:
``` java
public void rollback(boolean required) throws SQLException {
if (!this.closed) {
try {
this.clearLocalCache();
this.flushStatements(true);
} finally {
if (required) {
this.transaction.rollback();
}
}
}
}
```
假如我們現在執行完insert()方法,但是沒有使用commit(),那麼現在的dirty就是true,也就是資料庫資料與記憶體的資料不一致。我們再執行close()方法的時候,dirty是true,!this.autoCommit是true,那麼整個表示式就是true。
``` java
private boolean isCommitOrRollbackRequired(boolean force) {
return !this.autoCommit && this.dirty || force;
}
```
返回上一層,close的引數就會變成true
``` java
this.executor.close(this.isCommitOrRollbackRequired(false));
```
close()方法裡面呼叫了` this.rollback(forceRollback);`,引數為true,我們跟進去,可以看到確實執行了回滾:
``` java
public void rollback(boolean required) throws SQLException {
if (!this.closed) {
try {
this.clearLocalCache();
this.flushStatements(true);
} finally {
if (required) {
this.transaction.rollback();
}
}
}
}
```
所以只要我們執行了提交(commit),那麼關閉的時候就不會執行回滾,只要沒有提交事務,就會發生回滾,所以裡面的dirty是很重要的。
**【作者簡介】**:
秦懷,公眾號【**秦懷雜貨店**】作者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。這個世界希望一切都很快,更快,但是我希望自己能走好每一步,寫好每一篇文章,期待和你們一起交流。
此文章僅代表自己(本菜鳥)學習積累記錄,或者學習筆記,如有侵權,請聯絡作者核實刪除。人無完人,文章也一樣,文筆稚嫩,在下不才,勿噴,如果有錯誤之處,還望指出,感激