MyBatis 事務管理解析和有關事務的幾種特殊場景表現

優質文章,及時送達

作者 | 祖大俊

來源 | my.oschina.net/zudajun/blog/666764

1.說到數據庫事務,人們腦海裡自然不自然的就會浮現出事務的四大特性、四大隔離級別、七大傳播特性。

四大還好說,問題是七大傳播特性是哪兒來的?是Spring在當前線程內,處理多個數據庫操作方法事務時所做的一種事務應用策略。

事務本身並不存在什麼傳播特性,不要混淆事務本身和Spring的事務應用策略。(當然,找工作面試時,還是可以巧妙的描述傳播特性的)

2.一說到事務,人們可能又會想起create、begin、commit、rollback、close、suspend。

可實際上,只有commit、rollback是實際存在的,剩下的create、begin、close、suspend都是虛幻的,是業務層或數據庫底層應用語意,而非JDBC事務的真實命令。

create(事務創建):不存在。begin(事務開始):姑且認為存在於DB的命令行中,比如Mysql的start transaction命令,以及其他數據庫中的begin transaction命令。JDBC中不存在。

close(事務關閉):不存在。應用程序接口中的close方法,是為了把connection放回數據庫連接池中,供下一次使用,與事務毫無關係。suspend(事務掛起):不存在。

Spring中事務掛起的含義是,需要新事務時,將現有的connection1保存起來(它還有尚未提交的事務),然後創建connection2,connection2提交、回滾、關閉完畢後,再把connection1取出來,完成提交、回滾、關閉等動作,保存connection1的動作稱之為事務掛起。

在JDBC中,是根本不存在事務掛起的說法的,也不存在這樣的接口方法。

因此,記住事務的三個真實存在的方法,不要被各種事務狀態名詞所迷惑,它們分別是:conn.setAutoCommit、conn.commit、conn.rollback。

conn.close含義為關閉一個數據庫連接,這已經不再是事務方法了。

1. Mybaits中的事務接口Transaction

<code>

public

interface

Transaction

{
Connection getConnection

throws

SQLException;

void

commit

throws

SQLException;

void

rollback

throws

SQLException;

void

close

throws

SQLException;
}/<code>

有了文章開頭的分析,當你再次看到close方法時,千萬別再認為是關閉一個事務了,而是關閉一個conn連接,或者是把conn連接放回連接池內。

事務類層次結構圖:

MyBatis 事務管理解析和有關事務的幾種特殊場景表現

JdbcTransaction:單獨使用Mybatis時,默認的事務管理實現類,就和它的名字一樣,它就是我們常說的JDBC事務的極簡封裝,和編程使用mysql-connector-java-5.1.38-bin.jar事務驅動沒啥差別。其極簡封裝,僅是讓connection支持連接池而已。ManagedTransaction:含義為託管事務,空殼事務管理器,皮包公司。僅是提醒用戶,在其它環境中應用時,把事務託管給其它框架,比如託管給Spring,讓Spring去管理事務。

org.apache.ibatis.transaction.jdbc.JdbcTransaction.java部分源碼。

<code>@Override
public void

close

throws SQLException {

if

(connection != ) {
resetAutoCommit;

if

(

log

.isDebugEnabled) {

log

.

debug

(

"Closing JDBC Connection ["

+ connection +

"]"

);
}
connection.

close

;
}
}/<code>

面對上面這段代碼,我們不禁好奇,connection.close之前,居然調用了一個resetAutoCommit,含義為重置autoCommit屬性值。

connection.close含義為銷燬conn,既然要銷燬conn,為何還多此一舉的調用一個resetAutoCommit呢?消失之前多喝口水,真的沒有必要。

其實,原因是這樣的,connection.close不意味著真的要銷燬conn,而是要把conn放回連接池,供下一次使用,既然還要使用,自然就需要重置AutoCommit屬性了。

通過生成connection代理類,來實現重回連接池的功能。如果connection是普通的Connection實例,那麼代碼也是沒有問題的,雙重支持。

2. 事務工廠TransactionFactory

MyBatis 事務管理解析和有關事務的幾種特殊場景表現

顧名思義,一個生產JdbcTransaction實例,一個生產ManagedTransaction實例。兩個毫無實際意義的工廠類,除了new之外,沒有其他代碼。

<code>type=

"JDBC"

/>/<code>

mybatis-config.xml配置文件內,可配置事務管理類型。

3. Transaction的用法

無論是SqlSession,還是Executor,它們的事務方法,最終都指向了Transaction的事務方法,即都是由Transaction來完成事務提交、回滾的。

配一個簡單的時序圖。

MyBatis 事務管理解析和有關事務的幾種特殊場景表現

代碼樣例:

<code>

public

static

void

main

(String[] args)

{
SqlSession sqlSession = MybatisSqlSessionFactory.openSession;

try

{
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper

.

class

)

;

Student student =

new

Student;
student.setName(

"yy"

);
student.setEmail(

"[email protected]"

);
student.setDob(

new

Date);
student.setPhone(

new

PhoneNumber(

"123-2568-8947"

));

studentMapper.insertStudent(student);
sqlSession.commit;
}

catch

(Exception e) {
sqlSession.rollback;
}

finally

{
sqlSession.close;
}
}/<code>

注:Executor在執行insertStudent(student)方法時,與事務的提交、回滾、關閉毫無瓜葛(方法內部不會提交、回滾事務),需要像上面的代碼一樣,手動顯示調用commit、rollback、close等方法。

因此,後續在分析到類似insert、update等方法內部時,需要忘記事務的存在,不要試圖在insert等方法內部尋找有關事務的任何方法。

4. 你可能關心的有關事務的幾種特殊場景表現(重要)

1. 一個conn生命週期內,可以存在無數多個事務。
<code>// 執行了connection.setAutoCommit(

false

),並返回
SqlSession sqlSession = MybatisSqlSessionFactory.openSession;
try {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

Student student = new Student;
student.setName(

"yy"

);
student.setEmail(

"[email protected]"

);
student.setDob(new Date);
student.setPhone(new PhoneNumber(

"123-2568-8947"

));


studentMapper.insertStudent(student);
// 提交
sqlSession.commit;

studentMapper.insertStudent(student);
// 多次提交
sqlSession.commit;
} catch (Exception e) {
// 回滾,只能回滾當前未提交的事務
sqlSession.rollback;
} finally {
sqlSession.

close

;
}/<code>

對於JDBC來說,autoCommit=false時,是自動開啟事務的,執行commit後,該事務結束。

以上代碼正常情況下,開啟了2個事務,向數據庫插入了2條數據。

JDBC中不存在Hibernate中的session的概念,在JDBC中,insert了幾次,數據庫就會有幾條記錄,切勿混淆。而rollback,只能回滾當前未提交的事務。

2. autoCommit=false,沒有執行commit,僅執行close,會發生什麼?

<code>

try

{
studentMapper.insertStudent(student);
}

finally

{
sqlSession.close;
}/<code>

就像上面這樣的代碼,沒有commit,固執的程序員總是好奇這樣的特例。

insert後,close之前,如果數據庫的事務隔離級別是read uncommitted,那麼,我們可以在數據庫中查詢到該條記錄。

接著執行sqlSession.close時,經過SqlSession的判斷,決定執行rollback操作,於是,事務回滾,數據庫記錄消失。

下面,我們看看org.apache.ibatis.session.defaults.DefaultSqlSession.java中的close方法源碼。

<code>

void

close {

try

{
executor.close(isCommitOrRollbackRequired(

false

));
dirty =

false

;
}

finally

{
ErrorContext.instance.reset;
}
}/<code>

事務是否回滾,依靠isCommitOrRollbackRequired(false)方法來判斷。

<code>

private

boolean

isCommitOrRollbackRequired

(

boolean

force)

{

return

(!autoCommit && dirty) || force;
}/<code>

在上面的條件判斷中,!autoCommit=true(取反當然是true了),force=false,最終是否回滾事務,只有dirty參數了,dirty含義為是否是髒數據。

<code>

int

insert

(String statement, Object parameter)

{

return

update(statement, parameter);
}


int

update

(String statement, Object parameter)

{

try

{
dirty =

true

;
MappedStatement ms = configuration.getMappedStatement(statement);

return

executor.update(ms, wrapCollection(parameter));
}

catch

(Exception e) {

throw

ExceptionFactory.wrapException(

"Error updating database. Cause: "

+ e, e);
}

finally

{
ErrorContext.instance.reset;
}
}/<code>

源碼很明確,只要執行update操作,就設置dirty=true。insert、delete最終也是執行update操作。

只有在執行完commit、rollback、close等方法後,才會再次設置dirty=false。

<code>

void

commit

(

boolean

force)

{

try

{
executor.commit(isCommitOrRollbackRequired(force));
dirty =

false

;
}

catch

(Exception e) {

throw

ExceptionFactory.wrapException(

"Error committing transaction. Cause: "

+ e, e);
}

finally

{
ErrorContext.instance.reset;
}
}/<code>

因此,得出結論:autoCommit=false,但是沒有手動commit,在sqlSession.close時,Mybatis會將事務進行rollback操作,然後才執行conn.close關閉連接,當然數據最終也就沒能持久化到數據庫中了。

3. autoCommit=false,沒有commit,也沒有close,會發生什麼?

<code>

studentMapper

.insertStudent

(

student

);/<code>

乾脆,就這一句話,即不commit,也不close。

結論:insert後,jvm結束前,如果事務隔離級別是read uncommitted,我們可以查到該條記錄。jvm結束後,事務被rollback,記錄消失。通過斷點debug方式,你可以看到效果。

這說明JDBC驅動實現,已經Kao慮到這樣的特例情況,底層已經有相應的處理機制了。這也超出了我們的探究範圍。

但是,一萬個屌絲程序員會對你說:Don't do it like this. Go right way。

警告:請按正確的try-catch-finally編程方式處理事務,若不從,本人概不負責後果。

注:無參的openSession方法,會自動設置autoCommit=false。

總結:Mybatis的JdbcTransaction,和純粹的Jdbc事務,幾乎沒有差別,它僅是擴展支持了連接池的connection。

另外,需要明確,無論你是否手動處理了事務,只要是對數據庫進行任何update操作(update、delete、insert),都一定是在事務中進行的,這是數據庫的設計規範之一。

-END-

如果看到這裡,說明你喜歡這篇文章,請轉發。同時標星(置頂)本公眾號可以第一時間接受到博文推送。1. 從寫簡歷,到面試、談薪酬全文檢索微服務簡易集成3.數據庫緩存最終一致性的四種方案基友說:“自己整一個”

MyBatis 事務管理解析和有關事務的幾種特殊場景表現


分享到:


相關文章: