關於spring事務註解實戰

1.概述

spring的事務註解@Transaction 相信不少人都用過,而@Transaction 默認配置適合80%的配置。java

本篇文章不是對spring註解事務作詳細介紹,而是解決一些實際場景下遇到的問題spring

spring事務註解的基本原理數據庫

下面針對是否須要開啓事務和是否須要回滾事務在特定場景下的介紹ide

2.事務回滾

2.1 默認回滾策略

@Transactional
public void rollback() throws SQLException {
    // update db
    throw new SQLException("exception");
}

上述代碼事務會回滾嗎?不會的,就算拋出SQLException了,可是以前的數據庫操做依然會提交,緣由就是@Transactional默認狀況下只回滾RuntimeException和Error。函數

2.2 指定回滾異常

所以,若是要指定哪些異常須要回滾,則經過配置@Transactional中rollbackFor,例如spa

@Transactional(rollbackFor = {SQLException.class})
public void rollback() throws SQLException {
    // update db
    throw new SQLException("exception");
}

按照上面例子,那指定的SQLException,當拋出RuntimeException的時候,還會回滾嗎?.net

@Transactional(rollbackFor = {SQLException.class})
public void rollback() throws SQLException {
    // update db
    throw new Runtime("exception");
}

2.3 事務嵌套的回滾

假設有下面的邏輯,事務會回滾嗎3d

@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拋出如下異常,同時回滾外部事務code

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

2.4 小結

因此,在須要事務回滾的時候,最好仍是拋出RuntimeException,而且不要在代碼中捕獲此類異常

3、事務傳播性

@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 {
    //查詢數據中的最新值
}

4、內部調用事務方法

事務註解的實質就是在建立一個動態代理,在調用事務方法前開啓事務,在事務方法結束之後決定是事務提交仍是回滾。

所以,直接在類內部中調用事務方法,是不會通過動態代理的

所以,若是要使方法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如今對一些循環依賴是提供支持的,簡單來講,知足:

  1. Bean是單例
  2. 注入的方式不是構造函數注入

經過BeanFactory(不推薦,可能會引起一些註解不生效的bug,後續會有文章專門說明)

@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無效
相關文章
相關標籤/搜索