一杈抢、前言
spring事務(wù)傳播機(jī)制對應(yīng)java web應(yīng)用來說是比較常見的一個方面了凹联,在項目開發(fā)或者面試中經(jīng)常會遇到,希望本篇文章對spring事務(wù)有一定基礎(chǔ)的同學(xué)能夠加深對spring事務(wù)傳播機(jī)制的理解。
spring事務(wù)是在數(shù)據(jù)庫事務(wù)的基礎(chǔ)上進(jìn)行封裝擴(kuò)展,其主要特性如下:
支持原有的數(shù)據(jù)事務(wù)的隔離級別
加入了事務(wù)傳播的概念丐谋,提供多個事務(wù)的合并和隔離的功能
提供聲明式事務(wù),讓業(yè)務(wù)代碼與事務(wù)分離煌珊,事務(wù)更易用
spring提供了三個接口用來使用事務(wù):
TransactionDefinition :事務(wù)定義(規(guī)定了7種類型的事務(wù)傳播行為)
PlatformTransactionManager :事務(wù)的管理(事務(wù)提交号俐,回滾)
TransactionStatus : 事務(wù)運行狀態(tài)(狀態(tài)判斷)
TransactionDefinition.java
package org.springframework.transaction;
public interface TransactionDefinition {
? ? int PROPAGATION_REQUIRED = 0;
? ? int PROPAGATION_SUPPORTS = 1;
? ? int PROPAGATION_MANDATORY = 2;
? ? int PROPAGATION_REQUIRES_NEW = 3;
? ? int PROPAGATION_NOT_SUPPORTED = 4;
? ? int PROPAGATION_NEVER = 5;
? ? int PROPAGATION_NESTED = 6;
? ? int ISOLATION_DEFAULT = -1;
? ? int ISOLATION_READ_UNCOMMITTED = 1;
? ? int ISOLATION_READ_COMMITTED = 2;
? ? int ISOLATION_REPEATABLE_READ = 4;
? ? int ISOLATION_SERIALIZABLE = 8;
? ? int TIMEOUT_DEFAULT = -1;
? ? int getPropagationBehavior();
? ? int getIsolationLevel();
? ? int getTimeout();
? ? boolean isReadOnly();
? ? @org.springframework.lang.Nullable
? ? java.lang.String getName();
}
PlatformTransactionManager.java
package org.springframework.transaction;
public interface PlatformTransactionManager {
? ? org.springframework.transaction.TransactionStatus getTransaction(@org.springframework.lang.Nullable org.springframework.transaction.TransactionDefinition transactionDefinition) throws org.springframework.transaction.TransactionException;
? ? void commit(org.springframework.transaction.TransactionStatus transactionStatus) throws org.springframework.transaction.TransactionException;
? ? void rollback(org.springframework.transaction.TransactionStatus transactionStatus) throws org.springframework.transaction.TransactionException;
}
TransactionStatus.java
package org.springframework.transaction;
public interface TransactionStatus extends org.springframework.transaction.SavepointManager, java.io.Flushable {
? ? boolean isNewTransaction();
? ? boolean hasSavepoint();
? ? void setRollbackOnly();
? ? boolean isRollbackOnly();
? ? void flush();
? ? boolean isCompleted();
}
二、基礎(chǔ)概念
什么是事務(wù)傳播行為定庵?
事務(wù)傳播行為就是用來描述由某一個事務(wù)傳播行為修飾的方法被嵌套進(jìn)另一個方法的時刻事務(wù)如何傳播吏饿。
下面用偽代碼說明:
public class Demo1 {
public void methodA(){
? ? //doSomething
? ? methodB();
}
}
public class Demo2 {
@Transaction(Propagation=XXX)
public void methodB(){
? ? //doSomething
}
}
其中methodA()方法嵌套調(diào)用了methodB()方法,methodB()的事務(wù)傳播行為由@Transaction(Propagation=XXX)設(shè)置而決定蔬浙。
三猪落、為什么會有事務(wù)傳播機(jī)制?
spring 對事務(wù)的控制畴博,是使用 aop 切面實現(xiàn)的笨忌,我們不用關(guān)心事務(wù)的開始,提交 俱病,回滾蜜唾,只需要在方法上加 @Transactional 注解,這時候就有問題了庶艾。
場景一:?methodA方法調(diào)用了?methodB 方法,但兩個方法都有事務(wù)擎勘,這個時候如果?methodB 方法異常咱揍,是讓?methodB 方法提交,還是兩個一起回滾棚饵。
場景二:methodA方法調(diào)用了?methodB 方法煤裙,但是只有?methodA 方法加了事務(wù)掩完,是否把?methodB 也加入?methodA 的事務(wù),如果?methodB 異常硼砰,是否回滾?methodA且蓬。場景三:methodA方法調(diào)用了?methodB 方法,兩者都有事務(wù)题翰,methodB 已經(jīng)正常執(zhí)行完恶阴,但?methodA異常,是否需要回滾 serviceB 的數(shù)據(jù)豹障。
四冯事、傳播機(jī)制的生效條件
因為 spring 是使用 aop 來代理事務(wù)控制 ,是針對于接口或類的血公,所以在同一個 service 類中兩個方法的調(diào)用昵仅,傳播機(jī)制是不生效的。
public class DemoClass {
public void methodA(){
? ? //doSomething
? ? methodB();
}
@Transaction(Propagation=XXX)
public void methodB(){
? ? //doSomething
}
}
1累魔、如果在methodA中加入:@Transactional摔笤,mehodB不加@Transactional,那么調(diào)用methodA垦写,methodA的事物會生效吕世,因為methodA默認(rèn)的propagation為PROPAGATION_REQUIRED,此時methodB會加入到methodA中
2梯澜、如果methodA與methodB都加上@Transactional寞冯,那么調(diào)用methodA,methodA的事物會生效晚伙,此時和 1 中情況一樣吮龄,事物會生效,也是由于methodB的事物會加入到methodA中咆疗,但是漓帚,其實methodA中通過this.methodB()的調(diào)用是沒有觸發(fā)spring 的事物,原因會統(tǒng)一講解午磁。
3尝抖、如果methodA配置@Transactional,methodB配置@Transactional( propagation = Propagation.REQUIRES_NEW )迅皇,那么調(diào)用methodA昧辽,如果methodA出現(xiàn)異常,根據(jù)spring的事物傳播行為登颓,其實methodB應(yīng)該是入庫才對搅荞,但是我測試發(fā)現(xiàn)根本沒有生效,一樣是全部回滾。
以上結(jié)果的原因解釋:
spring的事物管理通過AOP代理來實現(xiàn)咕痛, 根據(jù)aop的思想痢甘,不可能在具體類Demo上直接處理事物,而是通過代理類來處理茉贡,代理類在調(diào)用具體類的方法來實現(xiàn)塞栅,根據(jù)上面的情景methodA通過this調(diào)用methodB,那么此時相當(dāng)于調(diào)用methodB時是沒有經(jīng)過代理類的調(diào)用腔丧,因此spring無法對事物的傳播行為做處理放椰。
因此下面的類型都是針對于被調(diào)用方法來說的,理解起來要想象成兩個 不同的service 的方法的調(diào)用才可以悔据。
五庄敛、七種傳播機(jī)制
Propagation.java
package org.springframework.transaction.annotation;
public enum Propagation {
? ? REQUIRED, SUPPORTS, MANDATORY, REQUIRES_NEW, NOT_SUPPORTED, NEVER, NESTED;
? ? private final int value;
? ? private Propagation(int value) { /* compiled code */ }
? ? public int value() { /* compiled code */ }
}
類別事務(wù)傳播類型傳播行為說明
支持當(dāng)前事務(wù)REQUIRED(必須)如果當(dāng)前沒有事務(wù),就新建一個事務(wù)科汗,如果已經(jīng)存在一個事務(wù)中藻烤,加入到這個事務(wù)中合并成一個事務(wù)。這是最常見的選擇(默認(rèn))头滔。
支持當(dāng)前事務(wù)SUPPORTS(支持)支持當(dāng)前事務(wù)有則加入當(dāng)前事務(wù)怖亭,如果當(dāng)前沒有事務(wù),就以非事務(wù)方式執(zhí)行坤检。
支持當(dāng)前事務(wù)MANDATORY(強(qiáng)制)使用當(dāng)前的事務(wù)加入當(dāng)前事務(wù)兴猩,如果當(dāng)前無事務(wù),則拋出異常早歇,也即父級方法必須有事務(wù)倾芝。
不支持當(dāng)前事務(wù)REQUIRES_NEW(隔離)新建事務(wù),如果當(dāng)前存在事務(wù)箭跳,把當(dāng)前事務(wù)掛起晨另。這個方法會獨立提交事務(wù),不受調(diào)用者的事務(wù)影響谱姓,父級異常借尿,它也是正常提交。
不支持當(dāng)前事務(wù)NOT_SUPPORTED(不支持)以非事務(wù)方式執(zhí)行操作屉来,如果當(dāng)前存在事務(wù)路翻,就把當(dāng)前事務(wù)掛起。
不支持當(dāng)前事務(wù)NEVER(強(qiáng)制非事務(wù))以非事務(wù)方式運行茄靠,如果當(dāng)前存在事務(wù)茂契,則拋出異常,即父級方法必須無事務(wù)慨绳。
嵌套事務(wù)NESTED(嵌套事務(wù))如果當(dāng)前存在事務(wù)账嚎,它將會成為父級事務(wù)的一個子事務(wù)莫瞬,方法結(jié)束后并沒有提交,只有等父事務(wù)結(jié)束才提交郭蕉;
如果當(dāng)前沒有事務(wù),則新建事務(wù) 喂江;
如果它異常召锈,父級可以捕獲它的異常而不進(jìn)行回滾,父級正常提交获询,而它回滾事務(wù)不提交?涨岁;
但如果父級異常,它必然回滾吉嚣,這就是和?REQUIRES_NEW?的區(qū)別梢薪。
說明:一般用得比較多的是 PROPAGATION_REQUIRED , REQUIRES_NEW尝哆;REQUIRES_NEW 一般用在子方法需要單獨事務(wù)秉撇。
六、項目中如何使用
下面我們采用實際的項目案例秋泄,驗證以上spring的事務(wù)傳播機(jī)制
創(chuàng)建一個maven 項目spring-propagation
POM.xml
? ? <parent>
? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? <artifactId>spring-boot-starter-parent</artifactId>
? ? ? ? <version>2.0.5.RELEASE</version>
? ? ? ? <relativePath/>
? ? </parent>
? ? <groupId>com.veromca.transaction</groupId>
? ? <artifactId>spring-propagation</artifactId>
? ? <version>1.0-SNAPSHOT</version>
? ? <properties>
? ? ? ? <java.version>1.8</java.version>
? ? </properties>
? ? <dependencies>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>org.mybatis.spring.boot</groupId>
? ? ? ? ? ? <artifactId>mybatis-spring-boot-starter</artifactId>
? ? ? ? ? ? <version>2.1.0</version>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>org.projectlombok</groupId>
? ? ? ? ? ? <artifactId>lombok</artifactId>
? ? ? ? ? ? <optional>true</optional>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? ? ? <artifactId>spring-boot-starter-test</artifactId>
? ? ? ? ? ? <scope>test</scope>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>org.apache.commons</groupId>
? ? ? ? ? ? <artifactId>commons-lang3</artifactId>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>commons-io</groupId>
? ? ? ? ? ? <artifactId>commons-io</artifactId>
? ? ? ? ? ? <version>2.6</version>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>com.alibaba</groupId>
? ? ? ? ? ? <artifactId>fastjson</artifactId>
? ? ? ? ? ? <version>1.2.30</version>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>mysql</groupId>
? ? ? ? ? ? <artifactId>mysql-connector-java</artifactId>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>tk.mybatis</groupId>
? ? ? ? ? ? <artifactId>mapper-spring-boot-starter</artifactId>
? ? ? ? ? ? <version>2.1.5</version>
? ? ? ? </dependency>
? ? </dependencies>
創(chuàng)建兩張表
sys_user為主表琐馆,account為字表
CREATE TABLE `sys_user` (
? `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
? `user_name` varchar(30) NULL COMMENT '用戶名',
? `age` int(2) NULL COMMENT '年齡',
? PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用戶表';
CREATE TABLE `account` (
? `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
? `user_name` varchar(30) NULL COMMENT '用戶名',
? `account_no` varchar(30) NULL COMMENT '賬戶編號',
? PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='賬戶表';
創(chuàng)建對應(yīng)服務(wù)類
PropagationAService.java?
/**
? ? * 測試事務(wù)傳播機(jī)制
? ? * 可修改調(diào)用方法來查看不同的效果
? ? */
? ? @Transactional
? ? public void transactionAll(SysUserPO sysUser, List<AccountPO> accounts){
? ? ? ? sysUserMapper.insert(sysUser);
? ? ? ? //propagationBService.insertAccountsTransactionRequired(accounts);
? ? ? ? //propagationBService.insertAccountsTransactionRequiredNew(accounts);
? ? ? ? //propagationBService.insertAccountsTransactionNotSupport(accounts);
? ? ? ? //propagationBService.insertAccountsTransactionNever(accounts);
? ? ? ? //propagationBService.insertAccountsTransactionMandatory(accounts);
? ? ? ? //propagationBService.insertAccountsTransactionSupport(accounts);
? ? ? ? try {
? ? ? ? ? ? propagationBService.insertAccountsTransactionNested(accounts);
? ? ? ? }catch (Exception e){
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? ? ? if(true) throw new IllegalArgumentException("出異常了");
? ? }
PropagationBService.java
@Transactional(propagation = Propagation.REQUIRED)
? ? public void insertAccountsTransactionRequired(List<AccountPO> accounts) {
? ? ? ? for (AccountPO account : accounts) {
? ? ? ? ? ? accountMapper.insert(account);
? ? ? ? }
? ? }
@Transactional(propagation = Propagation.REQUIRES_NEW)
? ? public void insertAccountsTransactionRequiredNew(List<AccountPO> accounts) {
? ? ? ? for (AccountPO account : accounts) {
? ? ? ? ? ? accountMapper.insert(account);
? ? ? ? }
? ? }
@Transactional(propagation = Propagation.SUPPORTS)
? ? public void insertAccountsTransactionSupport(List<AccountPO> accounts) {
? ? ? ? for (AccountPO account : accounts) {
? ? ? ? ? ? accountMapper.insert(account);
? ? ? ? ? ? if (true) throw new IllegalArgumentException("出異常了");
? ? ? ? }
? ? }
@Transactional(propagation = Propagation.NOT_SUPPORTED)
? ? public void insertAccountsTransactionNotSupport(List<AccountPO> accounts) {
? ? ? ? for (AccountPO account : accounts) {
? ? ? ? ? ? accountMapper.insert(account);
? ? ? ? ? ? if (true) throw new IllegalArgumentException("出異常了");
? ? ? ? }
? ? }
@Transactional(propagation = Propagation.MANDATORY)
? ? public void insertAccountsTransactionMandatory(List<AccountPO> accounts) {
? ? ? ? for (AccountPO account : accounts) {
? ? ? ? ? ? accountMapper.insert(account);
? ? ? ? ? ? if (true) throw new IllegalArgumentException("出異常了");
? ? ? ? }
? ? }
@Transactional(propagation = Propagation.NEVER)
? ? public void insertAccountsTransactionNever(List<AccountPO> accounts) {
? ? ? ? for (AccountPO account : accounts) {
? ? ? ? ? ? accountMapper.insert(account);
? ? ? ? ? ? if (true) throw new IllegalArgumentException("出異常了");
? ? ? ? }
? ? }
@Transactional(propagation = Propagation.NESTED)
? ? public void insertAccountsTransactionNested(List<AccountPO> accounts) {
? ? ? ? for (AccountPO account : accounts) {
? ? ? ? ? ? accountMapper.insert(account);
? ? ? ? ? ? if (true) throw new IllegalArgumentException("出異常了");
? ? ? ? }
? ? }
測試結(jié)果:
具體測試過程大家可以通過下載本文對應(yīng)源碼親自測試,加深理解恒序。
源碼鏈接:https://gitee.com/veromca/spring-propagation.git
值得注意的是:
測試NEVER 時當(dāng)前存在事務(wù)拋出的異常信息如下(即父級方法必須無事務(wù)):
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
測試MANDATORY?時當(dāng)前不存在事務(wù)拋出的異常信息如下(即父級方法必須有事務(wù)):
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'