1.說到數據庫事務,人們腦海裏天然不天然的就會浮現出事務的四大特性、四大隔離級別、七大傳播特性。四大還好說,問題是七大傳播特性是哪兒來的?是Spring在當前線程內,處理多個數據庫操做方法事務時所作的一種事務應用策略。事務自己並不存在什麼傳播特性,不要混淆事務自己和Spring的事務應用策略。(固然,找工做面試時,仍是能夠巧妙的描述傳播特性的)
java
2.一說到事務,人們可能又會想起create、begin、commit、rollback、close、suspend。可實際上,只有commit、rollback是實際存在的,剩下的create、begin、close、suspend都是虛幻的,是業務層或數據庫底層應用語意,而非JDBC事務的真實命令。mysql
create(事務建立):不存在。程序員
begin(事務開始):姑且認爲存在於DB的命令行中,好比Mysql的start transaction命令,以及其餘數據庫中的begin transaction命令。JDBC中不存在。面試
close(事務關閉):不存在。應用程序接口中的close()方法,是爲了把connection放回數據庫鏈接池中,供下一次使用,與事務毫無關係。sql
suspend(事務掛起):不存在。Spring中事務掛起的含義是,須要新事務時,將現有的connection1保存起來(它還有還沒有提交的事務),而後建立connection2,connection2提交、回滾、關閉完畢後,再把connection1取出來,完成提交、回滾、關閉等動做,保存connection1的動做稱之爲事務掛起。在JDBC中,是根本不存在事務掛起的說法的,也不存在這樣的接口方法。數據庫
所以,記住事務的三個真實存在的方法,不要被各類事務狀態名詞所迷惑,它們分別是:conn.setAutoCommit()、conn.commit()、conn.rollback()。apache
conn.close()含義爲關閉一個數據庫鏈接,這已經再也不是事務方法了。編程
public interface Transaction { Connection getConnection() throws SQLException; void commit() throws SQLException; void rollback() throws SQLException; void close() throws SQLException; }
有了文章開頭的分析,當你再次看到close()方法時,千萬別再認爲是關閉一個事務了,而是關閉一個conn鏈接,或者是把conn鏈接放回鏈接池內。
網絡
事務類層次結構圖:
session
(Made In Intellij Idea IDE)
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實例,那麼代碼也是沒有問題的,雙重支持。
顧名思義,一個生產JdbcTransaction實例,一個生產ManagedTransaction實例。兩個毫無實際意義的工廠類,除了new以外,沒有其餘代碼。
<transactionManager type="JDBC" />
mybatis-config.xml配置文件內,可配置事務管理類型。
不管是SqlSession,仍是Executor,它們的事務方法,最終都指向了Transaction的事務方法,即都是由Transaction來完成事務提交、回滾的。
配一個簡單的時序圖。
(Made In Visual Paradigm)
代碼樣例:
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()等方法內部尋找有關事務的任何方法。
// 執行了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(),只能回滾當前未提交的事務。
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()關閉鏈接,固然數據最終也就沒能持久化到數據庫中了。
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),都必定是在事務中進行的,這是數據庫的設計規範之一。讀完本篇文章,是否顛覆了你心中目前對事務的理解呢?歡迎點評。
版權提示:文章出自開源中國社區,若對文章感興趣,可關注個人開源中國社區博客(http://my.oschina.net/zudajun)。(通過網絡爬蟲或轉載的文章,常常丟失流程圖、時序圖,格式錯亂等,仍是看原版的比較好)