Spring事務為什么會失效?
不用Spring管理事務?
讓我們先來看一下不用spring管理事務時,各種框架是如何管理事務的。
使用JDBC來管理事務:
使用Hibernate來管理事務:
業務邏輯和事務代碼是耦合到一塊的,并且和框架的具體api綁定了。當我們換一種框架來實現時,里面對事務控制的代碼就要推倒重寫,并不一定能保證替換后的api和之前的api有相同的行為。
「統一的事務抽象」
基于這些問題,Spring抽象了一些事務相關的頂層接口。無論是全局事務還是本地事務,JTA,JDBC還是Hibernate,Spring都使用統一的編程模型。使得應用程序可以很容易的在全局事務與本地事務,或者不同事物框架之間進行切換。
「下圖為Spring事物抽象的核心類」
常用api | 接口 |
PlatformTransactionManager | 對事務進行管理 |
TransactionDefinition | 定義事務的相關屬性,例如隔離級別,傳播行為 |
TransactionStatus | 保存事務狀態 |
針對不同的數據訪問技術,使用不用的PlatformTransactionManager類即可。
數據訪問技術 | PlatformTransactionManager實現類 |
JDBC/Mybatis | DataSourceTransactionManager |
Hibernate | HibernateTransactionManager |
Jpa | JpaTransactionManager |
Jms | JmsTransactionManager |
編程式事務管理
當我們使用Spring的事務時,可以使用編程式事務或者聲明式事務。
當使用編程式事務的時候,可以直接使用事務的頂層接口,也可以使用模版類TransactionTemplate。
使用PlatformTransactionManager
使用TransactionTemplate
當我們直接使用PlatformTransactionManager來管理事務時,有很多模版代碼。例如業務代碼正常執行,提交事務,否則回滾事務。我們可以把這部分模版代碼封裝成一個模版類,這樣使用起來就很方便了,如下所示:
如下圖所示,TransactionTemplate#execute方法就是一個典型的模版方法:
我們可以傳入如下2個接口的實現類來執行業務邏輯,TransactionCallback(需要返回執行結果)或TransactionCallbackWithoutResult(不需要返回結果)。
聲明式事務管理
為了讓使用更加簡潔,Spring直接把事務代碼的執行放到切面中了,我們只需要在業務代碼方法上加上一個@Transactional注解即可,這種方式我們最常用哈。
使用@Transactional注解
此時事務相關的定義我們就可以通過@Transactional注解來設置了。
屬性名 | 類型 | 描述 | 默認值 |
value(和transactionManager互為別名) | String | 當在配置文件中有多個PlatformTransactionManager ,用該屬性指定選擇哪個事務管理器 | 空字符串"" |
propagation | 枚舉:Propagation | 事務的傳播行為 | REQUIRED |
isolation | 枚舉:Isolation | 事務的隔離度 | DEFAULT |
timeout | int | 事務的超時時間。如果超過該時間限制但事務還沒有完成,則自動回滾事務 | -1 |
readOnly | boolean | 指定事務是否為只讀事務 | false |
rollbackFor | Class[] | 需要回滾的異常 | 空數組{} |
rollbackForClassName | String[] | 需要回滾的異常類名 | 空數組{} |
noRollbackFor | Class[] | 不需要回滾的異常 | 空數組{} |
noRollbackForClassName | String[] | 不需要回滾的異常類名 | 空數組{} |
源碼解析
我們需要在配置類上加上@EnableTransactionManagement注解,來開啟spring事務管理功能。
「TransactionManagementConfigurationSelector#selectImports」
往容器中注入AutoProxyRegistrar和ProxyTransactionManagementConfiguration這2個類,那這2個類有啥作用呢?(源碼太多了,我就不貼代碼一步一步分析了,主要是理清思路)。
AutoProxyRegistrar主要就是往容器中注入一個類InfrastructureAdvisorAutoProxyCreator,這個類有什么作用呢?
「看一下繼承關系,原來是繼承自AbstractAutoProxyCreator,用來實現自動代理沒跑了!」
BeanFactoryTransactionAttributeSourceAdvisor主要就是往容器中注入了一個Advisor類,用來保存Pointcut和Advice。
對應的Pointcut為TransactionAttributeSourcePointcut的實現類,是一個匿名內部類,即篩選的邏輯是通過TransactionAttributeSourcePointcut類來實現的。
BeanFactoryTransactionAttributeSourceAdvisor
對應的Advice的實現類為TransactionInterceptor,即針對事務增強的邏輯都在這個類中。
篩選的邏輯我們就先不分析了,后面會再簡單提一下。
我們來看針對事務增強的邏輯,當執行被@Transactional標記的方法時,會調用到如下方法(TransactionInterceptor#invoke有點類似我們的@Around)。
TransactionInterceptor#invoke
TransactionAspectSupport#invokeWithinTransaction
我挑出這個方法比較重要的幾個部分來分析吧(上圖圈出來的部分)。
- 如果需要的話開啟事務(和傳播屬性相關,我們后面會提到)。
- 執行業務邏輯。
- 如果發生異常則會滾事務。
- 如果正常執行則提交事務。
「所以當發生異常需要會滾的時候,我們一定不要自己把異常try catch掉,不然事務會正常提交」。
TransactionAspectSupport#createTransactionIfNecessary
當開啟事務的時候,可以看到各種傳播屬性的行為(即@Transactional方法調用@Transactional方法會發生什么?)
AbstractPlatformTransactionManager#getTransaction
Spring事務的傳播行為在Propagation枚舉類中定義了如下幾種選擇。
「支持當前事務」
- REQUIRED :如果當前存在事務,則加入該事務。如果當前沒有事務,則創建一個新的事務。
- SUPPORTS:如果當前存在事務,則加入該事務 。如果當前沒有事務, 則以非事務的方式繼續運行。
- MANDATORY :如果當前存在事務,則加入該事務 。如果當前沒有事務,則拋出異常。
「不支持當前事務」
- REQUIRES_NEW :如果當前存在事務,則把當前事務掛起,創建一個新事務。
- NOT_SUPPORTED :如果當前存在事務,則把當前事務掛起,以非事務方式運行,。
- NEVER :如果當前存在事務,則拋出異常。
「其他情況」
NESTED :如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來執行 。如果當前沒有事務,則該取值等價于REQUIRED。
以NESTED啟動的事務內嵌于外部事務中 (如果存在外部事務的話),此時內嵌事務并不是一個獨立的事務,它依賴于外部事務。只有通過外部事務的提交,才能引起內部事務的提交,嵌套的子事務不能單獨提交。
事務失效的場景有哪些?
因為我們經常使用聲明式事務,如果一步消息就會導致事務失效,所以我們就從源碼角度來盤一下事務為什么失效。
異常被你try catch了
首先就是我們上面剛提到的,「異常被你try catch了」。因為聲明式事物是通過目標方法是否拋出異常來決定是提交事物還是會滾事物的。
自調用
當自調用時,方法執行不會經過代理對象,所以會導致事務失效。
// 事務失效
@Service
public class UserServiceV2Impl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void addUser(String name, String location) {
doAdd(name);
}
@Transactional
public void doAdd(String name) {
String sql = "insert into user (`name`) values (?)";
jdbcTemplate.update(sql, new Object[]{name});
throw new RuntimeException("保存用戶失敗");
}
}
我們可以通過如下三種方式來解決自調用失效的場景。
「1.@Autowired注入代理對象,然后調用方法」
// @Service
public class UserServiceV3Impl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private UserService userService;
@Override
public void addUser(String name, String location) {
userService.doAdd(name);
}
@Override
@Transactional
public void doAdd(String name) {
String sql = "insert into user (`name`) values (?)";
jdbcTemplate.update(sql, new Object[]{name});
throw new RuntimeException("保存用戶失敗");
}
}
「2.從ApplicationContext獲取代理對象,然后調用方法」
@Service
public class UserServiceV4Impl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private ApplicationContext applicationContext;
@Override
public void addUser(String name, String location) {
UserService userService = applicationContext.getBean(UserService.class);
userService.doAdd(name);
}
@Override
@Transactional
public void doAdd(String name) {
String sql = "insert into user (`name`) values (?)";
jdbcTemplate.update(sql, new Object[]{name});
throw new RuntimeException("保存用戶失敗");
}
}
「3.進行如下設置@EnableAspectJAutoProxy(exposeProxy = true),從AopContext中獲取代理對象,然后調用方法」
@Service
public class UserServiceV5Impl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void addUser(String name, String location) {
UserService userService = (UserService) AopContext.currentProxy();
userService.doAdd(name);
}
@Override
@Transactional
public void doAdd(String name) {
String sql = "insert into user (`name`) values (?)";
jdbcTemplate.update(sql, new Object[]{name});
throw new RuntimeException("保存用戶失敗");
}
}
非public方法導致事務失效
我們先來猜一下為什么非public方法會導致事務失效?
「難道是因為非public方法不會生成代理對象?」
我們給一個非public方法加上@Transactional,debug到如下代碼看一下是否會生成代理對象。
AbstractAutoProxyCreator#wrapIfNecessary
「結論是不會生成代理對象,那為什么不會生成代理對象呢?」
應該就是不符合Pointcut的要求了唄,我們在前面已經提到了事務對應的Pointcut為TransactionAttributeSourcePointcut。
TransactionAttributeSourcePointcut#matches
matches方法返回false,為什么會返回false呢?
一直debug發現是如下代碼導致的。
AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
即public方法能正常生成代理對象,而非public方法因為不符合Pointcut的要求,根本就不會生成代理對象。
異常類型不正確,默認只支持RuntimeException和Error,不支持檢查異常
「為什么不支持檢查異常呢?」
拿出我們上面分析過的代碼:
當執行業務邏輯發生異常的時候,會調用到TransactionAspectSupport#completeTransactionAfterThrowing方法。
可以看到對異常類型做了判斷,根據返回的結果來決定是否會滾事務,會調用到如下方法進行判斷。
RuleBasedTransactionAttribute#rollbackOn
如果用戶指定了回滾的異常類型,則根據用戶指定的規則來判斷,否則用默認的規則。
DefaultTransactionAttribute
默認的規則為只支持RuntimeException和Error。
我們可以通過@Transactional屬性指定回滾的類型,一般為Exception即可。
@Transactional(rollbackFor = Exception.class)