Spring5.0原始碼學習系列之事務管理概述(十一),在學習事務管理的原始碼之前,需要對事務的基本理論比較熟悉,所以本章節會對事務管理的基本理論進行描述
1、什麼是事務?
事務就是一組原子性的SQL操作,或者說一個獨立的工作單元。在計算機術語中是指訪問並可能更新資料庫中各種資料項的一個程式執行單元(unit)
注意:Spring的事務支援是基於資料庫事務的,在MySQL資料庫中目前只有InnoDB或者NDB叢集引擎才支援,MySQL5.0之前的預設MyISAM儲存引擎是不支援事務的
2、事務的ACID特性
ACID其實是事務特性的英文首字母縮寫,具體含義是:原子性(atomicity)、一致性(consistency)、隔離性(isolation)、永續性(durability)
- 原子性(atomicity):事務是一個原子操作,由一系列動作組成。整個事務中的所有操作要麼全部提交成功,要麼全部失敗回滾;
- 一致性(consistency):資料庫總是從一個一致性的狀態轉換到另外一個一致性的狀態,執行事務前後,資料保持一致;
- 隔離性(isolation): 因為有多個事務處理同個資料的情況,因此每個事務都應該與其他事務隔離開來,防止資料髒讀、不可重複讀等等情況;
- 永續性(durability):一旦事務提交,則其所做的修改就會永久儲存到資料庫中。此時即使系統崩潰,修改的資料也不會丟;
3、什麼是髒讀、不可重複讀、幻讀?
- 髒讀
在A事務修改資料,提交事務之前,另外一個B事務讀取了A事務未提交事務之前的資料,這種情況稱之為髒讀(Dirty Read) - 不可重複讀
一個A事務在讀取某些資料,第1次讀取出來的資料結果和第2次讀取出來的不一致,因為在兩次資料讀取期間,另外的事務對資料進行了更改 - 幻讀
幻讀和不可重複讀是很類似的,不同的地方在於幻讀側重於事務對資料的刪除或者新增,都是因為在兩次資料讀取期間,因為另外事務對資料的刪除還是新增,導致第2次讀取的資料和第1次不一致
4、Spring事務管理核心介面
5、事務隔離級別
定義:事務的隔離級別定義了一個事務可能受其他併發事務影響的程度。隔離級別可以不同程度的解決髒讀、不可重複讀、幻讀。
隔離級別 | 描述 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|---|
ISOLATION_DEFAULT | 使用後端資料庫預設的隔離級別,預設的為Repeatable read (可重複讀) | 否 | 否 | 是 |
ISOLATION_READ_UNCOMMITTED | 不可提交讀,允許讀取尚未提交事務的資料,可能會導致髒讀、不可重複讀、幻讀 | 是 | 是 | 是 |
ISOLATION_READ_COMMITTED | 提交讀,讀取併發事務已經提交的資料,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生 | 否 | 是 | 是 |
ISOLATION_REPEATABLE_READ | 可重複讀,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生 | 否 | 否 | 是 |
ISOLATION_SERIALIZABLE | 序列化,這種級別是最高階別,服從ACID的隔離級別,確保阻止髒讀、不可重複讀以及幻讀 | 否 | 否 | 否 |
6、事務的傳播行為
事務傳播行為 | 描述 |
---|---|
PROPAGATION_REQUIRED | 必須,預設值。如果A有事務,B將使用該事務;如果A沒有事務,B將建立一個新的事務 |
PROPAGATION_SUPPORTS | 支援。如果A有事務,B將使用該事務;如果A沒有事務,B將以非事務執行 |
PROPAGATION_MANDATORY | 強制。A如果有事務,B將使用該事務;如果A沒有事務,B將拋異常 |
PROPAGATION_REQUIRES_NEW | 必須新的。如果A有事務,將A的事務掛起,B建立一個新的事務;如果A沒有事務,B建立一個新的事務。 |
PROPAGATION_NOT_SUPPORTED | 不支援。如果A有事務,將A的事務掛起,B將以非事務執行;如果A沒有事務,B將以非事務執行。 |
PROPAGATION_NEVER | 從不。如果A有事務,B將拋異常;如果A沒有事務,B將以非事務執行 |
PROPAGATION_NESTED | 巢狀。A和B底層採用儲存點機制,形成巢狀事務。 |
7、事務管理其它屬性
前面介紹了事務管理的隔離級別和傳播行為這兩個重要的屬性,接著介紹一下事務的其它屬性
- 事務超時屬性
事務超時,屬性值是timeout,指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。以 int 的值來表示超時時間,其單位是秒,預設值為-1。 - 事務只讀屬性
屬性值readOnly,對於只有讀取資料查詢的事務,可以指定事務型別為 readonly,即只讀事務。只讀事務不涉及資料的修改,資料庫會提供一些優化手段,所以對於業務很明確的介面,可以適當加上只讀屬性 - 事務回滾規則
屬性值rollbackFor,預設情況下,事務只有遇到執行期異常(RuntimeException 的子類)時才會回滾,Error 也會導致事務回滾
屬性名 | 說明 |
---|---|
propagation | 事務的傳播行為,預設值為 REQUIRED |
isolation | 事務的隔離級別,預設值採用 DEFAULT |
timeout | 事務的超時時間,預設值-1,表示不會超時,如果設定其它值,超過該時間限制但事務還沒有完成,則自動回滾事務 |
readOnly | 指定事務為只讀事務,預設值false |
rollbackFor | 指定能夠觸發事務回滾的異常型別,並且可以指定多個異常型別。 |
8、Spring事務實現方式
Spring事務程式碼實現方式有兩種,一種是程式設計式事務,一種是宣告式事務。所謂程式設計式事務,是指通過Spring框架提供的TransactionTemplate或者直接使用底層的PlatformTransactionManager。宣告式事務,依賴Spring AOP,配置檔案中做相關的事務規則宣告或者直接使用@Transactional註解
下面給出一個典型的轉賬匯款例子,先不用事務的方式實現,接著使用程式設計式事務和宣告式事務進行事務管理
package com.example.springframework.dao.impl;
import com.example.springframework.dao.AccountDao;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
/**
* <pre>
* AccountDaoImpl
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改後版本: 修改人: 修改日期: 2021/03/25 15:51 修改內容:
* </pre>
*/
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public void out(String outer, int money) {
super.getJdbcTemplate().update("update account set money = money - ? where usercode=?",money,outer);
}
@Override
public void in(String inner, int money) {
super.getJdbcTemplate().update("update account set money = money + ? where usercode = ?",money , inner);
}
}
package com.example.springframework.service.impl;
import com.example.springframework.dao.AccountDao;
import com.example.springframework.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.stereotype.Service;
/**
* <pre>
* AccountServiceImpl
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改後版本: 修改人: 修改日期: 2021/03/25 15:55 修改內容:
* </pre>
*/
@Service
public class AccountServiceImpl extends JdbcDaoSupport implements AccountService {
AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(final String outer,final String inner,final int money){
accountDao.out(outer , money);
// exception
// int i = 1 / 0;
accountDao.in(inner , money);
}
}
程式碼例子看起來是挺正常的,不過假如在accountDao.out(outer , money);
, accountDao.in(inner , money);
兩個事務執行期間,發生異常,這時會怎麼樣?效果如圖:Jack的賬號已經轉賬成功,轉了1000,不過Tom並沒有收到匯款,這種情況在實際生活中肯定是不允許的,所以需要使用事務進行管理
9、Spring程式設計式事務
Spring程式設計式事務實現,通過Spring框架提供的TransactionTemplate
或者直接使用底層的PlatformTransactionManager
- 使用
TransactionTemplate
的方式
private AccountDao accountDao;
private TransactionTemplate transactionTemplate;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
@Override
public void transfer(final String outer,final String inner,final int money){
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
accountDao.out(outer , money);
// exception
int i = 1 / 0;
accountDao.in(inner , money);
}
});
}
- 使用
PlatformTransactionManager
的方式
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transferTrans(String outer, String inner, int money) {
DataSourceTransactionManager dataSourceTransactionManager =
new DataSourceTransactionManager();
// 設定資料來源
dataSourceTransactionManager.setDataSource(super.getJdbcTemplate().getDataSource());
DefaultTransactionDefinition transDef = new DefaultTransactionDefinition();
// 設定傳播行為屬性
transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef);
try {
accountDao.out(outer , money);
// exception
int i = 1 / 0;
accountDao.in(inner , money);
//commit
dataSourceTransactionManager.commit(status);
} catch (Exception e) {
// rollback
dataSourceTransactionManager.rollback(status);
}
}
10、Spring宣告式事務
Spring宣告式事務依賴於Spring AOP,通過配置檔案中做相關的事務規則宣告或者直接使用@Transactional註解
- AOP規則宣告方式
這種方式在 applicationContext.xml 檔案中配置 aop 自動生成代理,進行事務管理
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test"></property>
<property name="username" value="root"></property>
<property name="password" value="minstone"></property>
</bean>
<bean id="accountDao" class="com.example.springframework.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="accountService" class="com.example.springframework.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事務管理器配置 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- xml配置事務 propagation 傳播行為isolation 隔離級別-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/>
</tx:attributes>
</tx:advice>
<!-- 配置所有的Service方法都支援事務-->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.example.springframework.service..*.*(..))"/>
</aop:config>
</beans>
- 使用AOP註解方式
在applicationContext.xml 配置事務管理器,將並事務管理器交予spring,在目標類或目標方法新增註解即可 @Transactional, proxy-target-class設定為 true : 底層強制使用cglib 代理
注意點:@Transactional只能用於public方法,不管是加上類上還是方法上
<!-- 事務管理器配置 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置 Annotation 驅動,掃描@Transactional註解的類定義事務 -->
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
通過上述管理之後,一旦發生異常,兩邊都會進行事務回滾,沒有異常,正常提交事務
本文例子程式碼可以在github找到下載連結