當前位置: 首頁>>編程語言>>正文


數據庫事務Spring @Transactional注解失效原因分析

數據庫事務(Database Transaction):將有限係列的執行命令作為單個邏輯執行單元,單元內的任務要麽全部成功,要麽全部失敗。數據庫事務擁有四大特性,通常稱為ACID,具體說明如下(摘自維基百科):

  • 原子性(Atomicity):事務作為一個整體被執行,包含在其中的對數據庫的操作要麽全部被執行,要麽都不執行。
  • 一致性(Consistency):事務應確保數據庫的狀態從一個一致狀態轉變為另一個一致狀態。一致狀態的含義是數據庫中的數據應滿足完整性約束。
  • 隔離性(Isolation):多個事務並發執行時,一個事務的執行不應影響其他事務的執行。
  • 持久性(Durability):已被提交的事務對數據庫的修改應該永久保存在數據庫中。

1. JDBC實現事務管理

Java 底層API JDBC對數據CURD操作提供了最基礎的支持,jdbc 同時也提供數據庫CRUD操作的手動提交(commit)和回滾(rollback)方法,用來自定義實現事務管理。

Connection conn = null;
try {
    conn = DriverManager.getConnection("jdbc:mysql://192.168.1.1:3306/dbname", "user", "password");  // 創建數據庫連接
} catch (SQLException e) {
    //e.printStackTrace()
}
PreparedStatement ps = null;
try {
    conn.setAutoCommit(false);    // false CURD操作不會自動提交 不設置默認自動提交
    ps = conn.prepareStatement("insert table .....");  // 新增操作
    ps.execute();                // 執行sql
    ps.close();
    ps = null;
    ps = conn.prepareStatement("update table .....");   // 更新操作
    ps.execute();                // 執行sql
    conn.commit();               // 將insert update事務單元提交至數據庫
}catch(SQLException e) {
    conn.rollback();
} finally {
    if (ps != null) ps.close();
    if (conn != null) conn.close();
}

使用JDBC實現事務管理的關鍵點在於:setAutoCommit、commit和rollback三個方法,隻有正確使用才能保證事務操作的完整性,缺一不可,方法說明如下:

  • setAutoCommit方法:設置提交方式true/false,手動提交/自動提交,JDBC默認自動提交
  • commit方法:手動提交,需結合setAutoCommit使用,設置為false
  • rollback方法:將SQL執行序列回滾,不提交更新至數據庫,需結合setAutoCommit,設置為false

從JDBC事務管理的實現上來看,事務管理代碼還是想對很簡單的,但是在所有需要事務管理的地方,都必須使用setAutoCommit、commit、rollback操作,存在大量重複代碼;所以未來更方便的管理事務,各種JAVA開發框架實現了更便捷的事務管理,比如Spring框架,隻需要使用簡單的注解Transactional就能完成事務管理的工作。

2.Spring 注解@Transactional事務管理

先看看Spring @Transactional的使用,代碼如下:

@Transactional
public void updateUserAndAccount(String user) {
    dao1.updateUserAccount();
    dao2.updateAccountAmount()
}

當然在xml中配置spring事務管理類DataSourceTransactionManager

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dynamicDataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />

雖然Spring隻需要提供簡單的配置,就能完成這個事務管理的工作,給實際開發節省了不少時間。合理的利用Spring的事務注解,也確實能提高不少效率。但是在最近的工作項目中,有不少開發人員不是太理解@Transactional的工作原理,導致不少業務數據操作的事務管理失效。

經過分析,spring的注解@Transactional使用也存在一些限製和注意的問題還是來看看具體的例子,看看到底是什麽原因導致事務失效呢?
定義Service服務類TestTransactionServiceImpl,以下方法實現都在此類中實現

1.方法不加注解拋異常,執行有異常拋出,z1表成功新增一條記錄

public void testTransaction() {
    db.update("insert into z1(c1) values('2')");   
    // 主動拋出異常 測試回滾
    String str = null;
    if (str.startsWith("111")) {            // 空指針異常
        db.update("insert into z2(c1,c2) values('2','3')");
    }
}

2.方法加注解拋異常,執行有異常拋出,z1、z2表都沒有新增記錄,事務正常

@Transactional
public void testTransaction() {
    db.update("insert into z1(c1) values('2')");
    // 主動拋出異常 測試回滾
    String str = null;
    if (str.startsWith("111")) {        // 空指針異常
        db.update("insert into z2(c1,c2) values('2','3')");
    }
}

3.方法加注解不拋異常,正常執行無異常,z1、z2表都新增了一條記錄

@Transactional
public void testTransaction() {
    db.update("insert into z1(c1) values('2')");
    String str = "111";
    if (str.startsWith("111")) {
        db.update("insert into z2(c1,c2) values('2','3')");
    }
}

4.方法加注解並調用該類其他方法並拋異常,執行有異常拋出,z1、z2表無新增記錄

@Transactional
public void testTransaction() {
    db.update("insert into z1(c1) values('2')");
    testTransaction2();
}
public void testTransaction2() {
    // 主動拋出異常 測試回滾
    String str = null;
    if (str.startsWith("111")) {
        db.update("insert into z2(c1,c2) values('2','3')");
    }
}

5.調用含注解的方法並拋異常,執行有異常拋出,z1表新增記錄、z2表無新增記錄,事務失效

public void testTransaction() {
    testTransaction3();
}
@Transactional
public void testTransaction3() {
    db.update("insert into z1(c1) values('2')");
    // 測試
    String str = null;
    if (str.startsWith("111")) {
        db.update("insert into z1(c1) values('adff2')");
    }
}

為什麽事務會失效呢?關於這一點Spring Transactional官方說明如下:

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.

在代理下(默認或當配置為proxy-target-class=”true”),隻有當前代理類的外部方法調用注解方法時代理才會被攔截。事實上,這意味著:一個目標對象的方法調用該目標對象的另外一個方法,即使被調用的方法已使用了@Transactional注解標記,事務也不會有效執行。

另外關於@Transactional的說明,Spring 有一段描述關於方法可見性:

Method visibility and @Transactional
When using 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. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

@Transactional注解隻對代理類時的public方法有效,被protected、private、package-visible修飾的方法使用@Transactional注解無效,對這類方法使用事務注解,推薦使用AspectJ進行事務管理。Spring框架雖然提升了效率,偶爾也會產生意外的問題,且行且研究。

本文由《純淨天空》出品。文章地址: https://vimsky.com/zh-tw/article/3431.html,未經允許,請勿轉載。