Spring 事務詳解
事務的基本概念
事務(Transaction)是訪問並可能更新資料庫中各種資料項的一個程式執行單元(unit)。
事務的特性
原子性(atomicity)
一個事務是一個不可分割的工作單元,在事務中,所有操作要麼都完成,要麼都不完成。
一致性(consistency)
事務必須是使資料庫從一個一致性狀態轉移到另一個一致性的狀態。一致性與原子性是密切相關的。
隔離性(isolation)
一個事務的執行不能被其他事務干擾。即一個事務內部的操作及使用的資料對併發的其他事務是隔離的,併發執行的各個事務之間不能互相干擾。
永續性(durability)
永續性,又稱之為永久性,指一個事務一旦提交,它對資料庫中資料的改變應該是永久性的,接下來的其他操作或故障不應該對其有任何影響。
事務的基本原理
Spring 事務的本質其實就是資料庫對事務的支援,如果沒有資料庫的事務支援,Spring 是無法提供事務功能的。對於純 JDBC 操作資料庫,如果想要用到事務,可以按照以下步驟進行:
-
獲取資料庫連線
Connection connection = DriverManager.getConnection(url, username, password);
-
開啟事務
connection.setAutoCommit(false); // 預設是 true,如果需要在觸發異常時,進行回滾操作,這需要關閉自動提交
-
執行CRUD
-
提交事務/回滾事務
connection.
-
關閉連線
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.REQUIRED
,RoleService#add(..)
開啟事務,事務的傳播行為設定為Propagation.REQUIRES_NEW
,在執行時,UserService#add(..)
事務將會掛起,RoleService#add(..)
新建一個事務進行執行,當RoleService#add(..)
執行結束之後,UserService#add(..)
事務再恢復執行。該事務的傳播行為和Propagation.REQUIRES_NEW
的區別在於事務的回滾程度,如果RoleService#add(..)
發生異常,事務回滾之後,在UserService#add(..)
中捕捉了異常,那麼UserService#add(..)
將會繼續執行,不會回滾。