1. 程式人生 > >關於spring事務註解實戰

關於spring事務註解實戰

val 一個 get tips .class ane rop cat require

1.概述

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

本篇文章不是對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{
        innelTransaction()
    }catch(RuntimeException e){
        //do nothing
    }
    //updateC
}
 
@Transactional
public void innelTransaction() throws SQLException {
    // updateB
    throw new RuntimeException("exception");
}

答案是都會提交,因為事務的回滾的匹配規則是嵌套最外層的為準 那如果換成下面的代碼

@Transactional
public void rollback() throws SQLException{
    // updateA
    innelTransaction()
    //updateC
}
 
@Transactional(rollbackFor = SQLException.class)
public void innelTransaction() throws SQLException {
    // updateB
    throw new SQLException("exception");
}

updateA和updateB依然會提交,updateC不會提交(因為代碼執行不到這裏)

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
    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現在對一些循環依賴是提供支持的,簡單來說,滿足:

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

通過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);
    }
}

雖然在前一種方法在大部分情況下是可行的,但是有些情況下還是會報循環依賴的問題(可以嘗試在該類中即有@Transaction,又有@Async)。而使用BeanFactoryAware來註入,這樣是在Bean初始完成後再set進來,而不是依賴Spring來解決循環依賴

4.2 需要註意的地方

    • 使用@Transaction註解的方法,必須用public來修飾。
    • 其實不止是@Transaction,其他類似@Cacheable,@Retryable等依賴spring proxy也必須使用上述方式達到內部調用。

關於spring事務註解實戰