1. 程式人生 > >Spring 事務詳解

Spring 事務詳解

事務的基本概念

事務(Transaction)是訪問並可能更新資料庫中各種資料項的一個程式執行單元(unit)。

事務的特性

原子性(atomicity)

一個事務是一個不可分割的工作單元,在事務中,所有操作要麼都完成,要麼都不完成。

一致性(consistency)

事務必須是使資料庫從一個一致性狀態轉移到另一個一致性的狀態。一致性與原子性是密切相關的。

隔離性(isolation)

一個事務的執行不能被其他事務干擾。即一個事務內部的操作及使用的資料對併發的其他事務是隔離的,併發執行的各個事務之間不能互相干擾。

永續性(durability)

永續性,又稱之為永久性,指一個事務一旦提交,它對資料庫中資料的改變應該是永久性的,接下來的其他操作或故障不應該對其有任何影響。

事務的基本原理

Spring 事務的本質其實就是資料庫對事務的支援,如果沒有資料庫的事務支援,Spring 是無法提供事務功能的。對於純 JDBC 操作資料庫,如果想要用到事務,可以按照以下步驟進行:

  • 獲取資料庫連線

    Connection connection = DriverManager.getConnection(url, username, password);
    
  • 開啟事務

    connection.setAutoCommit(false); // 預設是 true,如果需要在觸發異常時,進行回滾操作,這需要關閉自動提交
    
  • 執行CRUD

  • 提交事務/回滾事務

    connection.
    commit(); // 提交事務 connection.rollback(); // 回滾事務
  • 關閉連線

    connection.close();
    

在使用 Spring 事務管理之後,事務的開啟、事務的提交與事務的回滾都交由 Spring 自動完成。在資料庫層,事務的提交和回滾是通過 binglog 或 Redo Log 實現的。

Spring 事務的配置方式

XML 配置方式

配置事務管理器

<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
>
<property name="dataSource" ref="dataSource" /> </bean>

配置事務切面

<!-- 配置事務切面 -->
<tx:advice id="advice" transaction-manager="transactionManager">
    <tx:attributes>

        <tx:method name="select*" read-only="true" />
    </tx:attributes>
</tx:advice>

配置AOP

<aop:config proxy-target-class="true">
    <aop:pointcut id="transactionPointcut" expression="execution(* com.garlic.service.impl.*.*(..))" />
    <aop:advisor advice-ref="advice" pointcut-ref="transactionPointcut" />
</aop:config>

註解配置的方式

配置事務管理器

<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

啟用事務註解

<tx:annotation-driven />

在 Service 層增加 @Transactional 註解配置

@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl implements UserService {
    ...
}

測試事務

案例一:丟擲異常,觸發事務回滾

@Service
public class UserServiceImpl implements UserService {
    ...	
	public boolean add(User user) throws Exception {
        int rows = this.userMapper.insert(user);
        throw new Exception("這是一個異常");

//        return rows > 0;
    }
	...
}

案例二:捕捉異常,不觸發事務回滾

@Service
public class UserServiceImpl implements UserService {
    ...
	public boolean add(User user) throws Exception {
        int rows = this.userMapper.insert(user);
        try {
            throw new Exception("這是一個異常");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return rows > 0;
    }
    ...
}

事務的傳播屬性

事務的傳播屬性是指在多個事務同時存在的時候,Spring 應該如何處理這些事務的行為,相關屬性定義在 org.springframework.transaction.TransactionDefinition 中,釋義如下表:

定義 釋義
PROPAGATION_REQUIRED 支援當前事務,如果當前不存在事務,則新建一個事務。這是最常見的事務傳播屬性配置,同時也屬於 Spring 預設的事務傳播行為
PROPAGATION_SUPPORTS 支援當前事務,如果當前不存在事務,則以非事務的方式執行
PROPAGATION_MANDATORY 支援當前事務,如果當前不存在事務,則以非事務的方式執行
PROPAGATION_REQUIRES_NEW 新建事務,如果當前不存在事務,則將當前事務掛起。新建的事務和被掛起的事務沒有任何聯絡,屬於兩個獨立的事務。當外層事務失敗回滾之後,不能回滾內層事務的執行結果。同時,當內層事務丟擲異常時,外層事務可以將其捕獲,不處理回滾操作。
PROPAGATION_NOT_SUPPORTED 以非事務的方式執行,如果當前存在事務,則將其掛起
PROPAGATION_NEVER 以非事務的方式執行,如果當前存在事務,則丟擲異常資訊
PROPAGATION_NESTED

事務的隔離級別

定義 釋義 取值 導致問題
ISOLATION_READ_UNCOMMITTED 讀未提交 java.sql.Connection#TRANSACTION_READ_UNCOMMITTED 髒讀
ISOLATION_READ_COMMITTED 讀已提交 java.sql.Connection#TRANSACTION_READ_COMMITTED 避免髒讀,允許不可重複讀和幻讀
ISOLATION_REPEATABLE_READ 可重複讀 java.sql.Connection#TRANSACTION_REPEATABLE_READ 避免髒讀,不可重複讀,允許幻讀
ISOLATION_SERIALIZABLE 可序列化 java.sql.Connection#TRANSACTION_SERIALIZABLE 序列化讀取,事務只能依次執行,避免了髒讀、不可重複讀、幻讀。執行效率低

髒讀:當一事務對資料進行了增、刪、改操作時,但是未提交,另外一個事務可以讀取到未提交的資料。此時,如果第一個事務這時候執行了回滾操作,這第二個事務讀取到了髒資料

不可重複讀:當一事務中發生了兩次讀取操作,第一次讀操作和第二次讀操作之間,另外一個事務對資料進行了修改,這時候兩次讀取的資料是不一致的。

幻讀:第一個事務對一定範圍內的資料進行批量修改,第二個事務在這個範圍增加一條資料,這時候第一個事務會丟失對新增資料的修改。

在事務中,事務的隔離級別越高,資料的完整性和一致性越能得到保障,同時,這樣對資料庫的併發效能影響越大。

常用資料庫的預設隔離級別:

主流資料庫 預設隔離級別
MySQL Repeatable Read
Oracle Read Commited
SQL Server Read COmmited

巢狀型事務

在瞭解了事務的傳播屬性與事務的隔離級別之後,接下來通過巢狀型事務,來深入理解 Spring 事務的傳播機制。

UserService#add(..)RoleService#add(..) 方法為例

案例分析

傳播行為 Propagation.REQUIRED

  • UserService#add(..)RoleService#add(..) 方法開啟事務,並且事務傳播行為同時設定為 Propagation.REQUIRED ,當RoleService#add(..) 發現自己執行在 UserService#add(..) 事務內部,則不開啟新的事務。同時,任何一個事務出現異常,事務都將回滾。
  • UserService#add(..) 未開啟事務,RoleService#add(..) 開啟事務,事務的傳播行為設定為 Propagation.REQUIRED,當RoleService#add(..)發現自己未執行在事務中,則將開啟一個新的事務。如果 RoleService#add(..) 發生異常,RoleService#add(..)所在事務將回滾,但是 UserService#add(..) 所做的資料更改仍然為生效。

傳播行為 Propagation.REQUIRES_NEW

  • UserService#add(..)開啟事務,事務的傳播行為設定為 Propagation.REQUIREDRoleService#add(..)開啟事務,事務的傳播行為設定為Propagation.REQUIRES_NEW,在執行時,UserService#add(..)事務將會掛起,RoleService#add(..)新建一個事務進行執行,當RoleService#add(..)執行結束之後,UserService#add(..) 事務再恢復執行。該事務的傳播行為和Propagation.REQUIRES_NEW 的區別在於事務的回滾程度,如果RoleService#add(..)發生異常,事務回滾之後,在UserService#add(..)中捕捉了異常,那麼UserService#add(..) 將會繼續執行,不會回滾。