Spring資料庫事務的實現機制
事務控制的核心——Connection
在開始之前,先讓我們回憶一下資料庫較原始的JDBC是怎麼管理事務的:
//僅做演示,程式碼不完整,不完全規範
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
。
細看其中的程式碼和配置內容,就會發現,不論哪種方式都會建立包含事務處理功能的動態代理。代理關聯的切面(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.執行被代理的請求
}
catch (Throwable ex) {
completeTransactionAfterThrowing(txInfo, ex);//...3.異常回滾
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
commitTransactionAfterReturning(txInfo);//...4.提交事務
return retVal;
}
//......
上邊的程式碼裡是不是沒有看到TransactionManger和Connection?那麼這兩個東西的作用在哪裡呢?
TransactionManger是上述TransactionInterceptor的一個屬性(不嚴的說),主要作用是用來建立connection,建立之後的Connection會被儲存在TransactionInfo裡面。
TransactionInfo在上述程式碼片段中被後續傳遞給事務提交和事務回滾的程式碼。
Service層和Dao層共享Connection
我們都知道事務要切在Service層,也就是說上一節的切面只是在Service層有效,那麼Dao層怎麼獲取到Connection連線呢?
如果Service層和Dao層的連線不是一個連線那麼回滾和提交操作就等同於無效了!
這裡只用MyBatis來說明,其他的ORM框架實現原理基本也是一樣的。
要明白這一點需要先弄明白MyBatis本身的事務管理機制,可以參考唯一浩哥的博文MyBatis原始碼解析(三)——Transaction事務模組。MyBatis提供了兩種事務管理機制一種是自己內部用的JDBC模式,一種是支援代理給外部控制的MANAGED模式。
第二種模式下會把事務的交給外部控制,外部只需要提供一個實現了 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存起來,然後讓自己實現Transaction獲取到即可。
Spring採用的是ThreadLocal本地執行緒變數的技術來做到的,我們可以看下mybatis-spring的 org.mybatis.spring.transaction.SpringManagedTransaction
中getConnection的實現就明白了:
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裡面就顯得不那麼職能分明,也就是不那麼符合設計原則的單一職責原則。