20 Spring 事務管理
- Spring 事務
- 事務特點
- 區域性事物 vs 全域性事務
- 程式設計式 VS 宣告式
- 事務傳播性
- 事務隔離級別
- 髒讀、不可重複讀、幻象讀概念說明
- Spring 事務常見介面/類介紹
- Spring 事務實現方式
- 程式設計式事務實現步驟
- Spring 宣告式事務管理
- 總結
Spring 事務
事務概念:事務是邏輯上的一組操作,這組操作要麼全部成功,要麼全部失敗。
事務特點
- 原子性(Atomicity):原子性是指事務是一個不可分割的工作單元,事務中的操作要麼都成功,要麼都失敗。
- 一致性(Consistency):一致性指事務前後資料的完整性必須保持一致。
- 隔離性(Isolation):隔離性指多個使用者併發訪問資料時,一個使用者的事務不能被其他使用者的事務所幹擾,多個併發事務之間資料要相互隔離。
- 永續性(Durability):永續性是指一個事務一旦被提交,他對資料庫中資料的改變就是永久性的,即使資料庫發生故障也不應該對其有任何影響。
區域性事物 vs 全域性事務
區域性事務是特定於一個單一的事務資源,如一個 JDBC 連線,而全域性事務可以跨多個事務資源事務,如在一個分散式系統中的事務。
區域性事務管理在一個集中的計算環境中是有用的,該計算環境中應用程式元件和資源位於一個單位點,而事務管理只涉及到一個執行在一個單一機器中的本地資料管理器。區域性事務更容易實現。
全域性事務管理需要在分散式計算環境中,所有的資源都分佈在多個系統中。在這種情況下事務管理需要同時在區域性和全域性範圍內進行。分散式或全域性事務跨多個系統執行,它的執行需要全域性事務管理系統和所有相關係統的區域性資料管理人員之間的協調。
程式設計式 VS 宣告式
Spring 支援兩種型別的事務管理:
- 程式設計式事務管理 :這意味著你在程式設計的幫助下有管理事務。這給了你極大的靈活性,但卻很難維護。
- 宣告式事務管理 :這意味著你從業務程式碼中分離事務管理。你僅僅使用註釋或 XML 配置來管理事務。
宣告式事務管理比程式設計式事務管理更可取,儘管它不如程式設計式事務管理靈活,但它允許你通過程式碼控制事務。但作為一種橫切關注點,宣告式事務管理可以使用 AOP 方法進行模組化。Spring 支援使用 Spring AOP 框架的宣告式事務管理。
事務傳播性
事務傳播行為就是多個事務方法呼叫時,如何定義方法間事務的傳播。Spring定義了7中傳播行為:
傳播行為就是解決多個業務方法相互之間呼叫問題
No | 事務傳播性 | 說明 |
---|---|---|
1 | propagation_requierd | 如果當前沒有事務,就新建一個事務,如果已存在一個事務中, 加入到這個事務中,這是Spring預設的選擇。 |
2 | propagation_supports | 支援當前事務,如果沒有當前事務,就以非事務方法執行。 |
3 | propagation_mandatory | 使用當前事務,如果沒有當前事務,就丟擲異常。 |
4 | propagation_required_new | 新建事務,如果當前存在事務,把當前事務掛起。 |
5 | propagation_not_supported | 以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。 |
6 | propagation_never | 以非事務方式執行操作,如果當前事務存在則丟擲異常。 |
7 | propagation_nested | 如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務, 則執行與propagation_required類似的操作。 |
事務隔離級別
No | 隔離級別 | 說明 |
---|---|---|
1 | read uncommited | 是最低的事務隔離級別,它允許另外一個事務可以看到這個事務未提交的資料。 |
2 | read commited | 保證一個事物提交後才能被另外一個事務讀取。 另外一個事務不能讀取該事物未提交的資料。 |
3 | repeatable read | 這種事務隔離級別可以防止髒讀,不可重複讀。但是可能會出現幻象讀。 它除了保證一個事務不能被另外一個事務讀取未提交的資料之外還避免 了以下情況產生(不可重複讀)。 |
4 | serializable | 是花費最高代價但最可靠的事務隔離級別。 事務被處理為順序執行。除了防止髒讀,不可重複讀之外,還避免了幻象讀 |
髒讀、不可重複讀、幻象讀概念說明
- 髒讀:一個事務讀取了另一個事務改寫但還未提交的資料,如果這些資料被回滾,則讀到的資料是無效資料。
- 不可重複讀:指在一個事務內,多次讀同一資料。在這個事務還沒有執行結束,另外一個事務也訪問該同一資料,那麼在第一個事務中的兩次讀取資料之間,由於第二個事務的修改第一個事務兩次讀到的資料可能是不一樣的,這樣就發生了在一個事物內兩次連續讀到的資料是不一樣的,這種情況被稱為是不可重複讀。
- 幻象讀:一個事務先後讀取一個範圍的記錄,但兩次讀取的紀錄數不同,我們稱之為幻象讀(兩次執行同一條 select 語句會出現不同的結果,第二次讀會增加一資料行,並沒有說這兩次執行是在同一個事務中)
幻讀:一個事務讀取了幾行記錄後,另一個事務插入一些記錄幻讀就發生了。在後來的查詢中,第一個事務就會發現有些原來沒有記錄。
Spring 事務常見介面/類介紹
PlatformTransactionManager 平臺事務管理器
spring 為不同的持久化框架提供了不同PlatformTransactionManager介面實現
事務 | 說明 |
---|
TransactionDefinition 事務定義資訊(隔離、傳播、超時、只讀)
TransactionDefinition介面控制著事務的屬性
Spring在TransactionDefinition介面中規定了7種類型的事務傳播行為及4中隔離級別
TransactionStatus 事務具體執行狀態
TransactionStatus 介面為事務程式碼提供了一個簡單的方法來控制事務的執行和查詢事務狀態。
Spring 事務實現方式
- 引入所需JAR
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
- 程式設計式事務管理
在實際應用中很少使用
通過TransactionTemplate 手動管理事務 - 宣告式事務管理
開發中推薦使用(程式碼侵入性最小)
Spring 的宣告事務是通過AOP實現的
程式設計式事務實現步驟
- 配置事務管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
- 配置事務模板
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
- 在需要使用事務的業務邏輯類中編寫程式碼實現事務
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
//業務程式碼
}
});
範例
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc_url}" />
<property name="username" value="${jdbc_username}" />
<property name="password" value="${jdbc_password}" />
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
<bean id="accountService" class="com.javaee.spring.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
<property name="transactionTemplate" ref="transactionTemplate" />
</bean>
<bean id="accountDao" class="com.javaee.spring.dao.AccountDao">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
private TransactionTemplate transactionTemplate;
@Override
public void transfer(int in, int out, double money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
accountDao.outMoney(out, money);
int i = 1 / 0;
accountDao.inMoney(in, money);
}
});
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
}
Spring 宣告式事務管理
1. 基於TransactionProxyFactoryBean的方式 實現宣告事務管理
實現步驟
- 引入Aop相關資源(JAR,名稱空間)
- 配置事務管理器
<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
- 配置業務代理類
配置目標物件
注入事務管理器
配置事務屬性
<!-- 配置業務代理類 -->
<bean id="accountProxyFactoryBean" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置目標物件 -->
<property name="target" ref="accountService" />
<!-- 注入事務管理器 -->
<property name="transactionManager" ref="transactionManager" />
<!-- 事務屬性(隔離級別,傳播行為) -->
<property name="transactionAttributes">
<props>
<!--
prop 的格式
PROPAGATION 事務的傳播行為
ISOLATION 事務的隔離級別
readOnly 只讀(不可進行修改,插入,刪除操作)
-Exception 發生哪些異常事務回滾
+Exception 發生哪些異常事務不回滾
-->
<!-- <prop key="transfer">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop> -->
<prop key="transfer">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
prop格式介紹
屬性介紹 | 屬性說明 |
---|---|
PROPAGATION | 事務的傳播行為 |
ISOLATION | 事務的隔離級別 |
readOnly | 只讀(不可進行修改,插入,刪除操作) |
-Exception | 發生哪些異常事務回滾 |
+Exception | 發生哪些異常事務不回滾 |
範例
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc_url}" />
<property name="username" value="${jdbc_username}" />
<property name="password" value="${jdbc_password}" />
</bean>
<bean id="accountService" class="com.javaee.spring.demo2.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<bean id="accountDao" class="com.javaee.spring.demo2.dao.AccountDao">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事務管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置業務代理類 -->
<bean id="accountProxyFactoryBean" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置目標物件 -->
<property name="target" ref="accountService" />
<!-- 注入事務管理器 -->
<property name="transactionManager" ref="transactionManager" />
<!-- 事務屬性(隔離級別,傳播行為) -->
<property name="transactionAttributes">
<props>
<!--
prop 的格式
PROPAGATION 事務的傳播行為
ISOLATION 事務的隔離級別
readOnly 只讀(不可進行修改,插入,刪除操作)
-Exception 發生哪些異常事務回滾
+Exception 發生哪些異常事務不回滾
-->
<prop key="transfer">PROPAGATION_REQUIRED</prop>
<!-- <prop key="transfer">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop> -->
</props>
</property>
</bean>
</beans>
Test 測試類
ApplicationContext context;
AccountService accountService;
@Before
public void init() {
context = new ClassPathXmlApplicationContext("spring-demo.xml");
accountService = (AccountService) context.getBean("accountProxyFactoryBean");
}
@Test
public void testTransfer() {
accountService.transfer(1, 2, 200);
}
Dao層
import org.springframework.jdbc.core.support.JdbcDaoSupport;
public class AccountDao extends JdbcDaoSupport {
public void inMoney(int in, double money) {
String sql = "update account set money = money + ? where id = ?";
super.getJdbcTemplate().update(sql, money, in);
}
public void outMoney(int out, double money) {
String sql = "update account set money = money - ? where id = ?";
super.getJdbcTemplate().update(sql, money, out);
}
}
業務邏輯層
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
@Override
public void transfer(int in, int out, double money) {
accountDao.outMoney(out, money);
int i = 1 / 0;
accountDao.inMoney(in, money);
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
}
2. 基於AspectJ的XML方式 實現宣告式事務
AspectJ 實現事務管理步驟
- 匯入相關JAR包等資原始檔
- 配置事務管理器
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
- 配置事務的通知(事務增強)
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
propagation 事務傳播行為
isolation 資料庫隔離級別