1. 程式人生 > >Mybatis【2.2】-- Mybatis關於建立SqlSession原始碼分析的幾點疑問?

Mybatis【2.2】-- Mybatis關於建立SqlSession原始碼分析的幾點疑問?

> 程式碼直接放在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是很重要的。 **【作者簡介】**: 秦懷,公眾號【**秦懷雜貨店**】作者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。這個世界希望一切都很快,更快,但是我希望自己能走好每一步,寫好每一篇文章,期待和你們一起交流。 此文章僅代表自己(本菜鳥)學習積累記錄,或者學習筆記,如有侵權,請聯絡作者核實刪除。人無完人,文章也一樣,文筆稚嫩,在下不才,勿噴,如果有錯誤之處,還望指出,感激