Java spring事務及事務不生效的原因詳解

註解 @Transactional 的屬性參數

屬性 類型 描述
value String 可選,指定事務管理器
propagation enum: Propagation 可選,指定事務傳播行為
isolation enum: Isolation 可選,指定事務隔離級別
readOnly boolean 讀寫或隻讀事務,默認讀寫
timeout int (in seconds granularity) 事務超時時間設置
rollbackFor Class 對象數組,必須繼承自Throwable 導致事務回滾的異常類數組
rollbackForClassName 類名數組,必須繼承自Throwable 導致事務回滾的異常類名字數組
noRollbackFor Class對象數組,必須繼承自Throwable 不會導致事務回滾的異常類數組
noRollbackForClassName 類名數組,必須繼承自Throwable 不會導致事務回滾的

propagation 事務的傳播機制

事務的傳播機制:如果在開始當前事務之前,一個事務上下文已經存在,此時有若幹選項可以指定一個事務性方法的執行行為

枚舉 Propagation 中定義瞭 7 個傳播機制的值

  • Propagation.REQUIRED:如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。是 spring 默認的傳播機制
  • Propagation.SUPPORTS:持當前事務,如果當前有事務,就以事務方式執行;如果當前沒有事務,就以非事務方式執行
  • Propagation.MANDATORY:使用當前的事務,且必須在一個已有的事務中執行,如果當前不存在事務,否則拋出異常
  • Propagation.REQUIRES_NEW:不管是否存在事務,都創建一個新的事務,原來的掛起,新的執行完畢,繼續執行老的事務
  • Propagation.NOT_SUPPORTED:以非事務方式執行,如果當前存在事務,就把當前事務掛起
  • Propagation.NEVER:以非事務方式執行,且必須在一個沒有的事務中執行,如果當前存在事務,則拋出異常
  • Propagation.NESTED:如果當前存在事務,則在嵌套事務內執行;如果當前沒有事務,則執行與 Propagation.REQUIRED 類似的操作

isolation 事務的隔離級別

隔離級別:若幹個並發的事務之間的隔離程度,與我們開發時候主要相關的場景包括:臟讀取、重復讀、幻讀

枚舉 Isolation 中定義瞭 5 個表示隔離級別的值

  • Isolation.DEFAULT:使用各個數據庫默認的隔離級別,是 spring 默認的隔離級別
  • Isolation.READ_UNCOMMITTED:讀取未提交數據(會出現臟讀, 不可重復讀)
  • Isolation.READ_COMMITTED:讀取已提交數據(會出現不可重復讀和幻讀)
  • Isolation.REPEATABLE_READ:可重復讀(會出現幻讀)
  • Isolation.SERIALIZABLE:串行化

常用數據庫的默認隔離級別

MYSQL:默認為 REPEATABLE_READ

SQLSERVER:默認為 READ_COMMITTED

Oracle:默認為 READ_COMMITTED

readOnly 事務的讀寫性

默認情況下是 false(不指定隻讀性);設置為 true 的含義: 該方法下使用的是隻讀操作,如果進行其他非讀操作,則會跑出異常

事務的隻讀性概念

從這一點設置的時間點開始(時間點 a),到這個事務結束的過程中,其他事務所提交的數據,該事務將看不見!!即查詢中不會出現別人在時間點 a 之後提交的數據

應用場景

  • 如果你一次執行單條查詢語句,則沒有必要啟用事務的隻讀性支持,數據庫默認支持 SQL 執行期間的讀一致性
  • 如果你一次執行多條查詢語句,例如統計查詢,報表查詢。在這種場景下,多條查詢 SQL 必須保證整體的讀一致性;否則,在前條 SQL 查詢之後,後條 SQL 查詢之前,數據被其他用戶改變,則該次整體的統計查詢將會出現讀數據不一致的狀態。此時,就有必要啟用事務的隻讀性支持

是一次執行多次查詢來統計某些信息,這時為瞭保證數據整體的一致性,要用隻讀事務

timeout 超時時間

  • 用於設置事務處理的時間長度,阻止可能出現的長時間的阻塞系統或者占用系統資源,單位為秒
  • 如果超時設置事務回滾,並拋出 TransactionTimedOutException 異常

rollbackFor 和 rollbackForClassName 遇到時回滾

  • 用來指明回滾的條件是哪些異常類或者異常類名
  • spring 默認情況下會對運行期異常 RunTimeException 進行事務回滾,如果遇到 checked 異常就不回滾

noRollbackFor 和 noRollbackForClassName 遇到時不回滾

用來指明不回滾的條件是哪些異常類或者異常類名

value 指定使用的事務管理器

  • value 主要用來指定不同的事務管理器,主要用來滿足在同一個系統中,存在不同的事務管理器的場景需要
  • 比如,在 spring 中聲明瞭兩種事務管理器 txManager1,txManager2。然後用戶可以根據需要,修改這個參數來指定特定的 txManage

存在多個事務管理器的情況:在一個系統中,需要訪問多個數據源,則必然會配置多個事務管理器

spring 事務不生效的原因

spring 團隊建議在具體的 類或類的方法上 使用 @Transactional 註解,而不要使用在類所要實現的任何接口上。在接口上使用 @Transactional 註解,隻能當你設置瞭基於接口的代理時它才生效。因為註解是不能繼承的,這就意味著如果正在使用基於類的代理時,那麼事務的設置將不能被基於類的代理所識別,而且對象也將不會被事務代理所包裝

對 spring 來說,方法調用者所屬的類或方法調用者就是主體對象,spring 會從二者身上獲取合適的事務增強器和事務屬性,如果獲取不到合適的增強器和事務屬性,那麼事務就會失效

數據庫引擎不支持事務

比如我們常用的 mysql,從 mysql 5.5.5 開始的默認存儲引擎是 InnoDB,之前默認的都是 MyISAM,引擎 MyISAM 是不支持事務操作的,需要改成 InnoDB 才能支持。所以這點要值得註意,底層引擎不支持事務再怎麼搞都是白搭

@Transactional 所在類非 spring 容器的 bean

// @Service 
public class OrderServiceImpl implements OrderService {
    @Transactional
    public void updateOrder(Order order) {
        // update order
    }
}

如果此時把 @Service 註解註釋掉,這個類就不會被加載成一個 bean,那這個類就不會被 spring 管理瞭,事務自然就失效瞭

方法不是 public 的

spring 事務官方文檔

Method visibility and @Transactional
When you use proxies, you should apply the @Transactional annotation only to methods with
public visibility. If you do annotate protected, private or package-visible methods with the
@Transactional annotation, no error is raised, but the annotated method does not exhibit the
configured transactional settings. If you need to annotate non-public methods, consider using
AspectJ (described later).

@Transactional 隻能用於 public 的方法上,否則事務會失效;即使方法是 public 的,但是如果被 private 的方法調用,事務同樣也會失效

數據源沒有配置事務管理器

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

當前數據源如果沒有配置事務管理器,那事務是不會生效的

事務的 propagation 傳播機制設置錯誤

@Service
public class OrderServiceImpl implements OrderService {
    @Transactional
    public void update(Order order) {
        updateOrder(order);
    }
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void updateOrder(Order order) {
        // update order
    }
}

Propagation.NOT_SUPPORTED:以非事務方式執行,如果當前存在事務,就把當前事務掛起

catch 語句沒有拋出異常

@Service
public class ClassServiceImpl implements ClassService {
    @Override
    @Transactional
    public void insertClassByException(ClassDo classDo) {
        classMapper.insertClass(classDo);
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

把異常吃瞭,然後又不拋出來,事務也不會回滾!

拋出的異常類型錯誤

@Service
public class OrderServiceImpl implements OrderService {
    @Transactional
    public void updateOrder(Order order) {
        try {
            // update order
        } catch {
            throw new Exception("更新錯誤");
        }
    }
}

@Transactional 默認回滾的是 RuntimeException Error,而 Exception RuntimeException 的父類,事務不生效的

如果你想觸發其他異常的回滾,需要在註解上配置一下,如

@Transactional(rollbackFor = Exception.class)

確保業務和事務入口在同一個線程

@Transactional
@Override
public void save(User user1, User user2) {
    new Thread(() -> {
          saveError(user1, user2);
          System.out.println(1 / 0);
    }).start();
}

自身調用問題

案列一

@Transactional 的事務開啟,或者是基於接口的或者是基於類的代理被創建。所以在同一個類中一個無事務的方法調用另一個有事務的方法,事務是不會起作用的 (這就是業界老問題:類內部方法調用事務不生效的問題原因)

在這裡插入圖片描述

因為 addInfo()上沒有事務,而 addInfo() 調用 create() 的時候是類內部調用,沒有走代理類,也就沒有事務切面

案列二

事務生效

在這裡插入圖片描述

由於 spring 事務默認的傳播機制是 Propagation.REQUIREDcreate() 方法的事務會加入到 addInfo() 方法的事務之中;而所在的類是可以產生代理對象的

案列三

事務生效

在這裡插入圖片描述

由於 spring 事務默認的傳播機制是 Propagation.REQUIREDcreate() 方法的事務會加入到 addInfo() 方法的事務之中;而所在的類是可以產生代理對象的

案列四

事務生效

在這裡插入圖片描述

案列五

事務生效

在這裡插入圖片描述

這裡雖然是方法內部調用,但是事務切入瞭 addInfo() 方法,所以即使內部拋出異常,也是可以生效的

案列六

事務不生效

在這裡插入圖片描述

案列七

事務生效

在這裡插入圖片描述

這是我們解決方法內部調用事務不生效的最常用方法之一:內部維護一個註入自己的 bean,然後使用這個屬性來調用方法。其實還有一種方法,那就是利用 Aop 上下文來獲取代理對象((TestService)AopContext.currentProxy()).create();,然後通過代理對象來調用。這裡需要註意:Aop 上下文 spring 默認是關閉的,需要手動開啟

總結

本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: