Spring數(shù)據(jù)庫事務的實現(xiàn)機制講解
事務控制的核心——Connection
在開始之前,先讓我們回憶一下數(shù)據(jù)庫較原始的JDBC是怎么管理事務的:
//僅做演示,代碼不完整,不完全規(guī)范 try { con.setAutoCommit(false); statement1 = con.prepareStatement(sql); statement1.executeUpdate(); statement2 = con.prepareStatement(sql1); statement2.executeUpdate(); con.commit(); } catch (SQLException e) { try { con.rollback(); } catch (SQLException e1) { } }
可以很明顯的看到,JDBC框架下的事務控制是由connection完成的。因為不論是MyBatis還是MyBatis-Spring都是在JDBC框架基礎上的高層框架,所以他們的原理仍然應該是一致的,也就是說想控制事務,必須要控制Connection。
我們常說事務要切在Service層,所以連接需要在整個Service請求中都是同一個,不能變。
用AOP技術保持當前的Connection
Spring的事務管理就是使用AOP技術,通過對Service層設置切面,注入事務管理的邏輯。
Spring的事務管理切面配置采用了聲明式事務,最常用的兩種方法是 tx:Advice 和 tx:annotation-driven 兩種方式。
兩種方式的配置文件解析器分別是:
org.springframework.transaction.config.TxAdviceBeanDefinitionParser
org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser
細看其中的代碼和配置內(nèi)容,就會發(fā)現(xiàn),不論哪種方式都會創(chuàng)建包含事務處理功能的動態(tài)代理。代理關聯(lián)的切面(Advice)類是 TransactionInterceptor 。
一起看下關鍵代碼:
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable { //...... if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);//-----1.開啟事務 Object retVal = null; try { retVal = invocation.proceedWithInvocation();//...2.執(zhí)行被代理的請求 } catch (Throwable ex) { completeTransactionAfterThrowing(txInfo, ex);//...3.異常回滾 throw ex; } finally { cleanupTransactionInfo(txInfo); } commitTransactionAfterReturning(txInfo);//...4.提交事務 return retVal; } //......
上邊的代碼里是不是沒有看到TransactionManger和Connection?那么這兩個東西的作用在哪里呢?
TransactionManger是上述TransactionInterceptor的一個屬性(不嚴的說),主要作用是用來創(chuàng)建connection,創(chuàng)建之后的Connection會被保存在TransactionInfo里面。
TransactionInfo在上述代碼片段中被后續(xù)傳遞給事務提交和事務回滾的代碼。
Service層和Dao層共享Connection
我們都知道事務要切在Service層,也就是說上一節(jié)的切面只是在Service層有效,那么Dao層怎么獲取到Connection連接呢?
如果Service層和Dao層的連接不是一個連接那么回滾和提交操作就等同于無效了!
這里只用MyBatis來說明,其他的ORM框架實現(xiàn)原理基本也是一樣的。
要明白這一點需要先弄明白MyBatis本身的事務管理機制,可以參考MyBatis源碼解析之Transaction事務模塊。MyBatis提供了兩種事務管理機制一種是自己內(nèi)部用的JDBC模式,一種是支持代理給外部控制的MANAGED模式。
第二種模式下會把事務的交給外部控制,外部只需要提供一個實現(xiàn)了 org.apache.ibatis.transaction.Transaction 接口的控制類即可。
一起來看一下Transaction需要提供哪些方法:
public interface Transaction { Connection getConnection() throws SQLException; void commit() throws SQLException; void rollback() throws SQLException; void close() throws SQLException; }
注意里邊的getConnection方法,也就是說MyBatis的連接也是交給外部來獲取的??!那么只需要想辦法把Service層的Connection存起來,然后讓自己實現(xiàn)Transaction獲取到即可。
Spring采用的是ThreadLocal本地線程變量的技術來做到的,我們可以看下mybatis-spring的 org.mybatis.spring.transaction.SpringManagedTransaction 中getConnection的實現(xiàn)就明白了:
public Connection getConnection() throws SQLException { if (this.connection == null) { openConnection(); } return this.connection; } private void openConnection() throws SQLException { this.connection = DataSourceUtils.getConnection(this.dataSource);//...1.關鍵點在這里??! this.autoCommit = this.connection.getAutoCommit(); this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource); if (LOGGER.isDebugEnabled()) { LOGGER.debug( "JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring"); } }
其中
this.connection = DataSourceUtils.getConnection(this.dataSource);
一行就會從ThreadLocal中拿到Connection對象。
事務為什么要切在Service層的理由
對于這個常識,有一點個人的理解:
事務的ACID要求事務要有原子性,也就是一個事務里邊的多項DB操作要同時成功,同時失敗,成功一半的情況是不允許的。
也就是說,一般需要事務的時候,都是包含多個功能單元的。那么我們都放在一個Dao里面就顯得不那么職能分明,也就是不那么符合設計原則的單一職責原則。
spring事務與數(shù)據(jù)庫事務的區(qū)別
先說一下什么是事務,事務(Transaction):
一般是指要做的或所做的事情。在計算機術語中是指訪問并可能更新數(shù)據(jù)庫中各種數(shù)據(jù)項的一個程序執(zhí)行單元(unit)。事務通常由高級數(shù)據(jù)庫操縱語言或編程語言(如SQL,C++或Java)書寫的用戶程序的執(zhí)行所引起,并用形如begin transaction和end transaction語句(或函數(shù)調(diào)用)來界定。事務由事務開始(begin transaction)和事務結束(end transaction)之間執(zhí)行的全體操作組成。。
之前一直覺得事務只針對于數(shù)據(jù)庫當中,5種隔離級別,7種傳播行為,后來才發(fā)現(xiàn)這是針對Spring的,對數(shù)據(jù)庫來說隔離級別只有4種,Spring多了一個DEFAULT 這是一個PlatfromTransactionManager默認的隔離級別,使用數(shù)據(jù)庫默認的事務隔離級別.
總的來說,本質(zhì)上其實是同一個概念
spring的事務是對數(shù)據(jù)庫的事務的封裝,最后本質(zhì)的實現(xiàn)還是在數(shù)據(jù)庫,假如數(shù)據(jù)庫不支持事務的話,spring的事務是沒有作用的
數(shù)據(jù)庫的事務說簡單就只有開啟,回滾和關閉,spring對數(shù)據(jù)庫事務的包裝,原理就是拿一個數(shù)據(jù)連接,根據(jù)spring的事務配置,操作這個數(shù)據(jù)連接對數(shù)據(jù)庫進行事務開啟,回滾或關閉操作.但是spring除了實現(xiàn)這些,還配合spring的傳播行為對事務進行了更廣泛的管理.其實這里還有個重要的點,那就是事務中涉及的隔離級別,以及spring如何對數(shù)據(jù)庫的隔離級別進行封裝.事務與隔離級別放在一起理解會更好些。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
java中快速創(chuàng)建帶初始值的List和Map實例
下面小編就為大家?guī)硪黄猨ava中快速創(chuàng)建帶初始值的List和Map實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10SpringBoot實現(xiàn)OneDrive文件上傳的詳細步驟
這篇文章主要介紹了SpringBoot實現(xiàn)OneDrive文件上傳的詳細步驟,文中通過代碼示例和圖文講解的非常詳細,對大家實現(xiàn)OneDrive文件上傳有一定的幫助,需要的朋友可以參考下2024-02-02SpringBoot-JWT生成Token和攔截器的使用(訪問受限資源)
本文主要介紹了SpringBoot-JWT生成Token和攔截器的使用(訪問受限資源),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-05-05解決執(zhí)行Junit單元測試報錯java.lang.ClassNotFoundException問題
這篇文章主要介紹了解決執(zhí)行Junit單元測試報錯java.lang.ClassNotFoundException問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11