MyBatis 事務管理解析:顛覆你心中對事務的理解!
四大還好說,問題是七大傳播特性是哪兒來的?是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
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
}
有了文章開頭的分析,當你再次看到close()方法時,千萬別再認為是關閉一個事務了,而是關閉一個conn連線,或者是把conn連線放回連線池內。
事務類層次結構圖:
JdbcTransaction:單獨使用Mybatis時,預設的事務管理實現類,就和它的名字一樣,它就是我們常說的JDBC事務的極簡封裝,和程式設計使用mysql-connector-java-5.1.38-bin.jar事務驅動沒啥差別。其極簡封裝,僅是讓connection支援連線池而已。
ManagedTransaction:含義為託管事務,空殼事務管理器,皮包公司。僅是提醒使用者,在其它環境中應用時,把事務託管給其它框架,比如託管給Spring,讓Spring去管理事務。
org.apache.ibatis.transaction.jdbc.JdbcTransaction.java部分原始碼。
@Override
public void close() throws SQLException {
if (connection != null) {
resetAutoCommit();
if (log.isDebugEnabled()) {
log.debug("Closing JDBC Connection [" + connection + "]");
}
connection.close();
}
}
面對上面這段程式碼,我們不禁好奇,connection.close()之前,居然呼叫了一個resetAutoCommit(),含義為重置autoCommit屬性值。
connection.close()含義為銷燬conn,既然要銷燬conn,為何還多此一舉的呼叫一個resetAutoCommit()呢?消失之前多喝口水,真的沒有必要。
其實,原因是這樣的,connection.close()不意味著真的要銷燬conn,而是要把conn放回連線池,供下一次使用,既然還要使用,自然就需要重置AutoCommit屬性了。
透過生成connection代理類,來實現重回連線池的功能。如果connection是普通的Connection例項,那麼程式碼也是沒有問題的,雙重支援。
2. 事務工廠TransactionFactory
顧名思義,一個生產JdbcTransaction例項,一個生產ManagedTransaction例項。兩個毫無實際意義的工廠類,除了new之外,沒有其他程式碼。
<transactionmanager type="JDBC" />
mybatis-config.xml配置檔案內,可配置事務管理型別。
3. Transaction的用法
無論是SqlSession,還是Executor,它們的事務方法,最終都指向了Transaction的事務方法,即都是由Transaction來完成事務提交、回滾的。
配一個簡單的時序圖。
程式碼樣例:
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@email.com");
student.setDob(new Date());
student.setPhone(new PhoneNumber("123-2568-8947"));
studentMapper.insertStudent(student);
sqlSession.commit();
} catch (Exception e) {
sqlSession.rollback();
} finally {
sqlSession.close();
}
}
注:Executor在執行insertStudent(student)方法時,與事務的提交、回滾、關閉毫無瓜葛(方法內部不會提交、回滾事務),需要像上面的程式碼一樣,手動顯示呼叫commit()、rollback()、close()等方法。
因此,後續在分析到類似insert()、update()等方法內部時,需要忘記事務的存在,不要試圖在insert()等方法內部尋找有關事務的任何方法。
4. 你可能關心的有關事務的幾種特殊場景表現(重要)
1. 一個conn生命週期內,可以存在無數多個事務。
// 執行了connection.setAutoCommit(false),並返回
SqlSession sqlSession = MybatisSqlSessionFactory.openSession();
try {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student = new Student();
student.setName("yy");
student.setEmail("email@email.com");
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();
}
對於JDBC來說,autoCommit=false時,是自動開啟事務的,執行commit()後,該事務結束。
以上程式碼正常情況下,開啟了2個事務,向資料庫插入了2條資料。
JDBC中不存在Hibernate中的session的概念,在JDBC中,insert了幾次,資料庫就會有幾條記錄,切勿混淆。而rollback(),只能回滾當前未提交的事務。
2. autoCommit=false,沒有執行commit(),僅執行close(),會發生什麼?
try {
studentMapper.insertStudent(student);
} finally {
sqlSession.close();
}
就像上面這樣的程式碼,沒有commit(),固執的程式設計師總是好奇這樣的特例。
insert後,close之前,如果資料庫的事務隔離級別是read uncommitted,那麼,我們可以在資料庫中查詢到該條記錄。
接著執行sqlSession.close()時,經過SqlSession的判斷,決定執行rollback()操作,於是,事務回滾,資料庫記錄消失。
下面,我們看看org.apache.ibatis.session.defaults.DefaultSqlSession.java中的close()方法原始碼。
@Override
public void close() {
try {
executor.close(isCommitOrRollbackRequired(false));
dirty = false;
} finally {
ErrorContext.instance().reset();
}
}
事務是否回滾,依靠isCommitOrRollbackRequired(false)方法來判斷。
private boolean isCommitOrRollbackRequired(boolean force) {
return (!autoCommit && dirty) || force;
}
在上面的條件判斷中,!autoCommit=true(取反當然是true了),force=false,最終是否回滾事務,只有dirty引數了,dirty含義為是否是髒資料。
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public 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();
}
}
原始碼很明確,只要執行update操作,就設定dirty=true。insert、delete最終也是執行update操作。
只有在執行完commit()、rollback()、close()等方法後,才會再次設定dirty=false。
@Override
public 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();
}
}
因此,得出結論:autoCommit=false,但是沒有手動commit,在sqlSession.close()時,Mybatis會將事務進行rollback()操作,然後才執行conn.close()關閉連線,當然資料最終也就沒能持久化到資料庫中了。
3. autoCommit=false,沒有commit,也沒有close,會發生什麼?
studentMapper.insertStudent(student);
乾脆,就這一句話,即不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),都一定是在事務中進行的,這是資料庫的設計規範之一。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69908602/viewspace-2673191/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 對事務的理解
- MyBatis中的事務MyBatis
- 初學事務管理:SpringBoot+MybatisSpring BootMyBatis
- Spring的事務管理(二)宣告式事務管理Spring
- Mybatis--事務控制MyBatis
- 解析Spring Boot中的事務管理機制Spring Boot
- MySQL 中的事務理解MySql
- Spring的事務管理Spring
- 理解分散式事務分散式
- Redis--事務理解Redis
- Spring 事務管理Spring
- Spring的事務管理入門:程式設計式事務管理(TransactionTemplate)Spring程式設計
- Spring 中的事務管理Spring
- Spring系列.事務管理Spring
- Spring系列-事務管理Spring
- SpringBoot新增事務管理Spring Boot
- Spring 事務原始碼解析Spring原始碼
- 深入理解Redis事務、事務異常、樂觀鎖、管道Redis
- 關於事務、事務的隔離級別以及對髒讀、不可重複讀、幻讀的理解
- React事務的一些理解React
- 從根上理解 MySQL 的事務MySql
- 深入理解 Spring 的事務原理Spring
- 關於分散式事務的理解分散式
- (四)Spring中的事務管理Spring
- golang的巢狀事務管理Golang巢狀
- Spring的事務管理(一) Spring事務管理的實現,事務的屬性(隔離級別,傳播行為,只讀)Spring
- 深入理解「分散式事務」分散式
- 分散式事務 TCC-Transaction 原始碼解析 —— 事務儲存器分散式原始碼
- 十、Redis事務、事務鎖Redis
- Spring事務管理總結Spring
- [轉帖]帶你讀懂Spring 事務——事務的傳播機制Spring
- spring事務管理原始碼分析(二)事務處理流程分析Spring原始碼
- 分散式事務之Spring事務與JMS事務(二)分散式Spring
- MongoDB 4.0 事務實現解析MongoDB
- Spring事務原理完全解析Spring
- 12張圖帶你徹底理解分散式事務!!分散式
- 關於Spring+Mybatis事務管理中資料來源的思考SpringMyBatis
- 理解mysql的事務隔離級別MySql