1.概述
spring的事務注解@Transaction 相信很多人都用過,而@Transaction 默認配置適合80%的配置。
本篇文章不是對spring注解事務做詳細介紹,而是解決一些實際場景下遇到的問題
spring事務注解的基本原理

下面針對是否需要開啟事務和是否需要回滾事務在特定場景下的介紹
2.事務回滾
2.1 默認回滾策略
@Transactional
public void rollback() throws SQLException {
// update db
throw new SQLException("exception");
}
上述代碼事務會回滾嗎?不會的,就算拋出SQLException了,但是之前的數據庫操作依然會提交,原因就是@Transactional默認情況下只回滾RuntimeException和Error。
2.2 指定回滾異常
因此,如果要指定哪些異常需要回滾,則通過配置@Transactional中rollbackFor,例如
@Transactional(rollbackFor = {SQLException.class})
public void rollback() throws SQLException {
// update db
throw new SQLException("exception");
}
按照上面例子,那指定的SQLException,當拋出RuntimeException的時候,還會回滾嗎?
@Transactional(rollbackFor = {SQLException.class})
public void rollback() throws SQLException {
// update db
throw new Runtime("exception");
}
還是會回滾的。
2.3 事務嵌套的回滾
假設有下面的邏輯,事務會回滾嗎(或者說 updateA,updateB,updateC)哪些更新會提交
@Transactional
public void rollback() {
// updateA
try{
selfProxy.innelTransaction()
}catch(RuntimeException e){
//do nothing
}
//updateC
}
@Transactional
public void innelTransaction() throws SQLException {
// updateB
throw new RuntimeException("exception");
}
答案是會回滾,因為內部事務觸發回滾,當前事務被標記為 rollback-only,
當外部事務提交的時候,Spring拋出以下異常,同時回滾外部事務
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
|
2.4 小結
所以,在需要事務回滾的時候,最好還是拋出RuntimeException,並且不要在代碼中捕獲此類異常
三、事務傳播性
@Transaction中的propagation的可以配置事務的傳播性,網上介紹的很多,就直接復制一段
PROPAGATION_REQUIRED--支持當前事務,如果當前沒有事務,就新建一個事務。這是最常見的選擇。 (也是默認策略) PROPAGATION_SUPPORTS--支持當前事務,如果當前沒有事務,就以非事務方式執行。 PROPAGATION_MANDATORY--支持當前事務,如果當前沒有事務,就拋出異常。 PROPAGATION_REQUIRES_NEW--新建事務,如果當前存在事務,把當前事務掛起。 PROPAGATION_NOT_SUPPORTED--以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。 PROPAGATION_NEVER--以非事務方式執行,如果當前存在事務,則拋出異常。
3.1 如何在事務中讀取最新配置
有時候需要在一個事務中,讀取最新數據(默認是讀取事務開始前的快照)。其實很簡單,只要使用上面PROPAGATION_NOT_SUPPORTED傳播性就可以了。
@Transactional
public void transaction() throws SQLException {
// do something
selfProxy.queryNewValue();
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void queryNewValue() throws SQLException {
//查詢數據中的最新值
}
四、內部調用事務方法
事務注解的實質就是在創建一個動態代理,在調用事務方法前開啟事務,在事務方法結束以后決定是事務提交還是回滾。
因此,直接在類內部中調用事務方法,是不會經過動態代理的

。 因此,如果要使方法B點事務生效,必須這樣

4.1 解決辦法
解決思路:需要在內部調用方法B的時候,找到當前類的代理類,用代理類去調用方法B
4.1.1 解決辦法1
@Service
public class MyService{
@Transactional
public void transaction(){
// do something
((MyService) AopContext.currentProxy()).queryNewValue();
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void queryNewValue(){
//查詢數據中的最新值
}
}
通過AopContext.currentProxy()可以拿到當前類的代理類,但是要使用這個時候,必須在啟動類上加上
@EnableAspectJAutoProxy(exposeProxy=true)
4.1.2 解決辦法2
既然是要拿到當前代理類,那其實直接在Spring的容器里面去拿也可以啊。在spring中拿Bean的方法大致有2種
通過注入
@Service
public class MyService{
@Autowired
private MyService self;
@Transactional
public void transaction() {
// do something
self.queryNewValue();
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void queryNewValue() {
//查詢數據中的最新值
}
}
tips:spring現在對一些循環依賴是提供支持的,簡單來說,滿足:
- Bean是單例
- 注入的方式不是構造函數注入
通過BeanFactory
@Service
public class MyService implements BeanFactoryAware{
private MyService self;
@Transactional
public void transaction(){
// do something
self.queryNewValue();
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void queryNewValue() {
//查詢數據中的最新值
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
self = beanFactory.getBean(MyService.class);
}
}
4.2 需要注意的地方
- 使用@Transaction注解的方法,必須用public來修飾。
- 其實不止是@Transaction,其他類似@Cacheable,@Retryable等依賴spring proxy也必須使用上述方式達到內部調用。
- @Transactional,@Async放在同一個類中,如果使用Autowire注入會循環依賴,而使用BeanFactoryAware會使得@Transactional無效
