什麼是事務
事務是資料庫操作的最基本單元,是邏輯上的一組操作,要麼都成功,要麼都失敗。是一個不可分割的工作單元。
事務的使用
事務具有 4 個特性:原子性、一緻性、隔離性】持久性,簡稱為 ACID 特性。
- 原子性(Atomicity):一個事務是一個不可分割的工作機關,一個事務中包括的操作要麼都成功要麼都失敗。
- 一緻性(Consistency):事務必須保證資料庫從一個一緻性狀态變到另一個一緻性狀态。比如轉賬的總金額,不能轉着轉着總金額少了或者多了。大部分一緻性的需求需要程式員寫業務代碼保證。
- 隔離性(Isolation):一個事務的執行不能被其它事務幹擾,即一個事務内部的操作及使用的資料對并發的其它事務是隔離的,并發執行的各個事務之間不能互相打擾。
- 持久性(Durability):持久性也稱為永久性,指一個事務一旦送出,它對資料庫中資料的改變就是永久性的,後面的其它操作和故障都不應該對其有任何影響。
為什麼要用事務
舉例:銀行轉賬。小明給小紅轉 100 元。小明需要減少餘額 100,小紅需要增加餘額 100。這是兩個操作,需要一起成功。如果在小明轉賬成功之後發生了異常,就會出現小明 減 100 餘額,但是小紅并沒有加 100 餘額。就會造成錢丢失的情況。這是絕對不允許的。僞代碼如下:
public void accountMoney(){
int money = 100;
//小明 少 100
userDao.reduceMoney(money);
// 其他業務 發生異常
int i = 1/0;
//小紅 多 100
userDao.addMoney(money);
}
事務管理方式
Spring 支援 2 種事務管理方式。
- 程式設計式事務管理 程式設計式事務管理是通過編寫代碼實作的事務管理。可以根據需求規定事務從哪裡開始,到哪裡結束,擁有很高的靈活性。但是這種方式,會使業務代碼與事務規則高度耦合,難以維護,是以我們很少使用這種方式對事務進行管理。是以,本文給大家介紹的是如何使用聲明式事務管理。
- 聲明式 聲明式事務管理可以通過 2 種方式實作,分别是 XML和注解方式。Spring 在進行聲明式事務管理時,底層使用了 AOP 。
事務管理器
Spring 并不會直接管理事務,而是通過事務管理器對事務進行管理的。
PlatformTransactionManager
Spring 提供了一個 PlatformTransactionManager 接口,這個接口被稱為 Spring 的事務管理器,其源碼如下:
public interface PlatformTransactionManager {
// 根據傳入的 TransactionDefinition 對象擷取一個事務狀态對象
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
// 送出事務
void commit(TransactionStatus var1) throws TransactionException;
// 復原事務
void rollback(TransactionStatus var1) throws TransactionException;
}
該接口的源碼很簡單。這個接口針對不同的架構提供了不同的實作類,如下:
實作類 | 說明 |
org.springframework.jdbc.datasource.DataSourceTransactionManager | 提供給 Spring JDBC 、MBatis 的事務管理器 |
org.springframework.orm.hibernate5.HibernateTransactionManager | 提供給 Hibernate 的事務管理器 |
org.springframework.orm.jpa.JpaTransactionManager | 提供給 JPA 的事務管理器 |
org.springframework.jdo.JdoTransactionManager | 提供給 Jdo 的事務管理器 |
org.springframework.transaction.jta.JtaTransactionManager | 提供給 JTA 的事務管理器 |
注意:這些實作類,需要導入對應的依賴才能看到。 該接口中還有兩個對象,分别是 TransactionDefinition 和 TransactionStatus。
TransactionDefinition
- TransactionDefinition:事務定義,定義了事務的名稱,傳播屬性,事務隔離級别,是否隻讀,逾時時間。
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;
default int getPropagationBehavior(){
return 0;
}
default int getIsolationLevel(){
return -1;
}
default int getTimeout(){
return -1;
}
default boolean isReadOnly(){
return false;
}
@Nullable
default String getName(){
return null;
}
static TransactionDefinition withDefaults(){
return StaticTransactionDefinition.INSTANCE;
}
}
- PROPAGATION_** 0 ~ 7 代表的是事務傳播行為
- ISOLATION_** -1 ~ 8 代表的是事務的隔離級别
- TIMEOUT_DEFAULT 預設的逾時時間,-1,代表使用資料庫的逾時時間
- getPropagationBehavior:擷取事務的傳播行為,預設為
PROPAGATION_REQUIRED
- getIsolationLevel:擷取事務的隔離級别,預設為所使用資料庫的隔離級别
- getTimeout:擷取事務的逾時時間
- isReadOnly:事務是否隻讀
- getName:擷取事務的名稱
TransactionStatus
- TransactionStatus:事務狀态,儲存了事務執行過程中的狀态。
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
boolean hasSavepoint();
@Override
void flush();
}
方法說明如下:
名稱 | 說明 |
hasSavepoint | 事務内部是否帶有儲存點 |
flush | 重新整理事務 |
- TransactionExecution
public interface TransactionExecution {
boolean isNewTransaction();
void setRollbackOnly();
boolean isRollbackOnly();
boolean isCompleted();
}
方法說明如下:
名稱 | 說明 |
isNewTransaction | 目前事務是否是新的 |
setRollbackOnly | 設定事務復原 |
isRollbackOnly | 事務是否已被标記為復原 |
isCompleted | 事務是否完成,即是否已經送出或復原 |
- SavepointManager
public interface SavepointManager {
Object createSavepoint() throws TransactionException;
void rollbackToSavepoint(Object savepoint) throws TransactionException;
void releaseSavepoint(Object savepoint) throws TransactionException;
}
方法說明如下:
名稱 | 說明 |
createSavepoint | 建立儲存點 |
rollbackToSavepoint | 復原到給定的儲存點 |
releaseSavepoint | 釋放給定的儲存點 |
有一個預設的抽象實作
AbstractTransactionStatus
,對 TransactionExecution、savepoint、SavepointManager 有具體的實作邏輯,代碼有點多,就不貼了,但是非常好了解。 對 TransactionExecution、savepoint、SavepointManager 有具體的實作邏輯,代碼有點多,就不貼了,但是非常好了解。 DefaultTransactionStatus 又繼承了 AbstractTransactionStatus,繼續進行擴充。
public class DefaultTransactionStatus extends AbstractTransactionStatus {
// 具體事務對象
@Nullable
private final Object transaction;
//是否是新開啟的事務(=true時才會送出事務)
private final boolean newTransaction;
// 是否是建立的同步器(=true時才會執行回調事件)
private final boolean newSynchronization;
// 是否隻讀
private final boolean readOnly;
// 是否已調試
private final boolean debug;
// 挂起的資源資訊(事務傳播行為要求挂起目前事務時,挂起的事務暫存資訊,執行完後用于恢複)
@Nullable
private final Object suspendedResources;
//省略。。。。
}
事務傳播行為
事務傳播行為指的是,多事務方法之間進行調用時,這個過程中事務應該如何進行管理。例如,事務方法 A 在調用事務方法 B 時,B 方法是在調用者 A 方法的事務中運作呢,還是為自己開啟一個新事務運作,這就是由事務方法 B 的事務傳播行為決定的。
事務方法:能讓資料庫表資料發生改變的方法,例如新增、删除、修改資料的方法。
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
}
行為 | 說明 |
REQUIRED | 如果有事務在運作,目前的方法就在這個事務内運作,否則,就啟動一個新的事務,并在自己的事務内運作 |
SUPPORTS | 如果有事務在運作,目前的方法就在這個事務内運作;如果目前沒有事務,則以非事務的方式運作。 |
MANDATORY | 如果目前存在事務,則加入該事務;如果目前沒有事務,則抛出異常。 |
REQUIRES_NEW | 目前的方法必須啟動新事務,并在它自己的事務内運作,如果有事務正在運作,應該将它挂起 |
NOT_SUPPORTED | 以非事務方式運作,如果目前存在事務,則把目前事務挂起。 |
NEVER | 以非事務方式運作,如果目前存在事務,則抛出異常。 |
NESTED | 如果目前存在事務,則建立一個新事務作為目前事務的嵌套事務來運作;如果目前沒有事務,則該取值等價于 REQUIRED。 |
根據上面的描述,我們可以将行為分為三大類。
- 不要事務:NEVER、NOT_SUPPORTED。
- 如果有則用:SUPPORTS
- 必須使用事務:REQUIRED、REQUIRES_NEW、NESTED、MANDATORY
隔離級别
事務有一個特性為隔離性,多事務操作之間不會産生影響。但如果不考慮隔離性,則會産生三個讀問題:髒讀、不可重複讀、虛(幻)讀。
- 髒讀:一個未送出事務讀取到另一個未送出事務的資料
- 不可重複讀:一個未送出事務讀取到另一個送出事務修改的資料
- 虛(幻)讀:一個未送出事務讀取到另一送出事務添加的資料 那如何解決呢?可以通過設定事務隔離級别,解決讀問題!Spring 中提供了以下隔離級别。
級别 | 說明 |
DEFAULT | 使用所用的資料庫的隔離級别 |
READ_UNCOMMITTED(讀未送出) | 可以讀取到尚未送出的更改,可能導緻髒讀、幻讀和不可重複讀 |
READ_COMMITTED(讀已送出) | Oracle 的預設級别,可以讀取到已送出的更改的資料,防止髒讀,可能出現幻讀和不可重複讀 |
REPEATABLE_READ(可重複讀) | MySQL 的預設級别,同一條SQL多次執行,可以讀取到已送出的新增的資料,防止髒讀和不可重複讀,可能出現幻讀 |
SERIALIZABLE | 可串行化,什麼讀問題都不會産生 |
加入依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
xml方式
我們先來看看不使用事務會發生什麼情況。建立名為
aopxml
的包。
提供資料庫腳本
CREATE TABLE `tx_test` (
`id` int(11) NOT NULL,
`name` varchar(64) DEFAULT NULL,
`money` decimal(10,0) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `test`.`tx_test`(`id`, `name`, `money`) VALUES (1, '張三', 1000);
INSERT INTO `test`.`tx_test`(`id`, `name`, `money`) VALUES (2, '李四', 1000);
開發代碼
建立 dao 包
在類中提供兩個方法,一個張三增加金額,一個李四減金額。
@Repository
public class TXDao {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 給張三增加金額
*/
public void add(){
String sql = "update `tx_test` set money = money + 100 where id = 1;";
jdbcTemplate.update(sql);
}
/**
* 給李四減金額
*/
public void reduce(){
String sql = "update `tx_test` set money = money - 100 where id = 2;";
jdbcTemplate.update(sql);
}
}
建立 entity
public class TxTest {
private Integer id;
private String name;
private BigDecimal money;
public Integer getId(){
return id;
}
public void setId(Integer id){
this.id = id;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public BigDecimal getMoney(){
return money;
}
public void setMoney(BigDecimal money){
this.money = money;
}
}
建立 service 包
@Service
public class TXServiceImpl {
@Autowired
private TXDao tx;
public void transfer(){
tx.add();
int i = 1/0;
tx.reduce();
}
}
項目結構如下:
測試
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("tx.xml");
TXServiceImpl bean = context.getBean(TXServiceImpl.class);
bean.transfer();
}
控制台出現異常
Exception in thread "main" java.lang.ArithmeticException: / by zero
at cn.cxyxj.txannon.service.TestServiceImpl.transfer(TestServiceImpl.java:20)
at cn.cxyxj.txannon.AppMain.main(AppMain.java:20)
再來檢視資料庫資料,可以發現張三的金額增加了,但是李四的金額沒有減。銀行哭死!!! 是以我們需要引入 Spring 事務,解決上述出現的問題。
引入 tx 命名空間
<?xml versinotallow="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://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">
</beans>
注意: 上面說過 Spring 提供的聲明式事務管理是依賴于 Spring AOP 實作的,是以還需要添加 aop 命名空間配置。當然我還額外引入了 spring-context 命名空間。
配置事務管理器以及 JdbcTemplate
<!--引入 jdbc.properties 中的配置-->
<context:property-placeholder location="classpath:jdbc.properties">
</context:property-placeholder>
<!--配置資料源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--資料庫連接配接位址-->
<property name="url" value="${jdbc.url}"/>
<!--資料庫的使用者名-->
<property name="username" value="${jdbc.username}"/>
<!--資料庫的密碼-->
<property name="password" value="${jdbc.password}"/>
<!--資料庫驅動-->
<property name="driverClassName" value="${jdbc.driver}"/>
</bean>
<!--定義 JdbcTemplate Bean-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--将資料源的 Bean 注入到 JdbcTemplate 中-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事務管理器,以 JDBC 為例-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
配置的事務管理器實作為 DataSourceTransactionManager,是 JDBC 和 MBatis 的PlatformTransactionManager 接口實作。
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.50.172/tx_test?useSSL=false
jdbc.username=Yj19980402
jdbc.password=root
配置事務通知
配置事務通知,指定所需要使用的事務管理器以及指定事務作用的方法和該事務屬性。
<!--配置通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--配置事務參數-->
<tx:attributes>
<!--指定哪個方法上面添加事務-->
<tx:method name="transfer*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" timeout="10"/>
<!--可以配置多個方法 <tx:method name="account*"/>-->
</tx:attributes>
</tx:advice>
transaction-manage
參數的預設值就是 transactionManager,如果事務管理器 id 與其一緻,則可以不用指定。
<tx:method>
元素包含多個屬性參數,可以為某個或某些方法(name 屬性指定的方法)定義事務屬性,如下表所示:
事務屬性 | 說明 |
propagation | 指定事務的傳播行為,預設為 REQUIRED |
isolation | 指定事務的隔離級别,預設為所使用資料庫的隔離級别 |
read-only | 指定是否為隻讀事務,預設為 false |
timeout | 表示逾時時間,機關為“秒”。事務在指定的逾時時間後,自動復原。避免事務長時間不送出導緻資料庫資源占用。預設為 -1,代表不逾時 |
rollback-for | 指定出現哪些異常進行事務復原 |
no-rollback-for | 指定出現哪些異常不進行事務復原 |
配置切入點和切面
<aop:config>
<!--配置切入點-->
<aop:pointcut id="pt" expression="execution(*
com.cxyxj.aopxml.service.TXServiceImpl.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>
如上寫法就對 transfer 方法進行了事務管理。就不會出現小明減少餘額,而小紅沒有增加餘額的情況,發生了異常就進行復原。
注解方式
使用注解方式就不會有上面如此瑣碎的配置了。再重新建立名為
txannon
包,将 xml 方式使用到的 entity、dao、service 相關代碼 copy 過來。
開啟事務
使用
EnableTransactionManagement
注解開啟事務。
@ComponentScan(basePackages = "com.cxyxj.txannon")
@EnableTransactionManagement //開啟事務
public class AppMain {
}
相當于 tx:annotation-driven 标簽。
建立配置類
@Configuration
@PropertySource("jdbc.properties")
public class TxConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Value("${jdbc.driver}")
private String driverClassName;
//建立資料庫連接配接池
@Bean
public DriverManagerDataSource getDruidDataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
//建立 JdbcTemplate 對象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
//到 ioc 容器中根據類型找到 dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入 dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//建立事務管理器
@Bean
public DataSourceTransactionManager
getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new
DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
可以不需要在配置切入點和切面了。
添加事務注解
在需要添加事務的方法上添加
@Transactional
注解,表明該方法需要進行事務管理。
@Service
public class TXServiceImpl {
@Autowired
private TXDao tx;
@Transactional
public void transfer(){
tx.add();
int i = 1/0;
tx.reduce();
}
}
@Transactional
這個注解可以添加到類上面,也可以添加方法上面。如果把這個注解添加到類上面,這個類裡面所有的方法都添加事務,如果把這個注解添加方法上面,則是為這個方法添加事務。
@Transactional
Transactional
這個注解裡面可以配置很多事務相關參數。
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
事務屬性 | 說明 |
value | 指定不同的事務管理器。 |
transactionManager | 跟 value 一緻。 |
propagation | 指定事務的傳播行為,預設為 REQUIRED |
isolation | 指定事務的隔離級别,預設為所使用資料庫的隔離級别 |
read-only | 指定是否為隻讀事務,預設為 false |
timeout | 表示逾時時間,機關為“秒”。事務在指定的逾時時間後,自動復原。避免事務長時間不送出導緻資料庫資源占用。預設為 -1,代表不逾時 |
rollbackFor | 指定出現哪些異常進行事務復原 |
rollbackForClassName | 指定異常類名稱,進行事務復原 |
noRollbackFor | 指定出現哪些異常不進行事務復原 |
noRollbackForClassName | 指定出現哪些異常類名稱不進行事務復原 |
基本用法會了,現在就來看看事務的傳播行為,這是Spring事務中難以了解的一塊,因為它的場景很多。
事務傳播行為詳解
REQUIRED
如果有事務在運作,目前的方法就在這個事務内運作,否則,就啟動一個新的事務,并在自己的事務内運作
- 如果 transfer 方法沒有事務,則 reduce 方法會建立一個事務。
- 兩個方法的事務的傳播行為都為
。是以Propagation.REQUIRED
方法會先開啟一個事務,而transfer
會加入到reduce
方法的事務中,這兩個方法用的是同一個事務,是以不論是在哪個方法中抛出異常,所有操作都會復原。transfer
REQUIRES_NEW
目前方法必須啟動新事務,并在它自己的事務内運作。如果有事務正在運作,應該将它挂起。
reduce 方法行為修改為
Propagation.REQUIRES_NEW
。transfer 方法建立新事務,然後調用 reduce 方法,reduce 方法會将 transfer 方法的事務挂起,并建立屬于 reduce 方法的事務。是以在該例子中會建立兩個事務。由于有兩個事務,那事務的復原就出現了幾種情況。
- 場景一
transfer 方法進行的操作會復原,reduce 方法的操作不會復原。
- 場景二
兩個方法的操作都會復原。這是由于 reduce 方法的異常會向 transfer 方法傳遞。
- 場景三
transfer 方法進行的操作不會復原,reduce 方法的操作會復原。
- 如果 transfer 方法沒有事務,則 reduce 方法會建立一個事務。
- 如果 transfer 方法有事務,則 reduce 方法會将 transfer 方法的事務挂起,并建立屬于 reduce 方法的事務。如果此時 transfer 方法發生了異常,則 transfer 方法操作會復原,但不會導緻 reduce 方法復原。如果 reduce 方法發生了異常,則 reduce 方法操作會復原,如果 transfer 方法沒有捕獲 reduce 方法的異常,那 transfer 方法也會復原。
NESTED
如果目前存在事務(主事務),則建立一個新事務作為目前事務的嵌套事務(子事務)來運作;如果目前沒有事務,則該取值等價于 REQUIRED。
- 如果 transfer 方法沒有事務,則 reduce 方法會建立一個事務。
- 如果 transfer 方法有事務,則 reduce 方法會建立一個新事務,作為 transfer 方法事務的嵌套事務來運作。那會有什麼場景呢?
- 場景一
transfer 方法發生異常并復原,會導緻 reduce 方法 同時復原。
- 場景二
transfer 方法進行的操作不會復原,reduce 方法的操作會復原。注意:transfer 方法需要進行 catch,不然 transfer 方法也會復原。
主事務方法異常復原時,會同時復原子事務。而子事務可以單獨異常復原,可以不影響主事務和其他子事務(前提是需要處理掉子事務的異常)
MANDATORY
如果目前存在事務,則加入該事務;如果目前沒有事務,則抛出異常。
由于 transfer 方法沒有事務,在啟動時就會抛出異常,如下:
No existing transaction found for transaction marked with propagation 'mandatory'
SUPPORTS
如果有事務在運作,目前的方法就在這個事務内運作;如果目前沒有事務,則以非事務的方式運作。
由于 transfer 方法沒有事務,是以 reduce 方法也不會建立事務,發生了異常也不會進行復原。
NOT_SUPPORTED
以非事務方式運作,如果目前存在事務,則把目前事務挂起。
transfer 方法有事務,但 reduce 方法傳播行為是 NOT_SUPPORTED,是以會将 transfer 方法事務挂起,reduce 方法以非事務的方式運作。
是以圖檔例子會出現 transfer 方法進行的操作會復原,reduce 方法的操作不會復原。
NEVER
以非事務方式運作,如果目前存在事務,則抛出異常。
由于 transfer 方法有事務,在啟動時就會抛出異常,如下:
Existing transaction found for transaction marked with propagation 'never'
復原規則
上面一直在說遇到異常就復原,那是遇到所有異常都會復原嗎?不是的,預設情況下,Spring 事務隻有遇到 RuntimeException 以及 Error 時才會復原,在遇到檢查型異常時是不會復原的,比如 IOException、TimeoutException。
那如果想在發生檢查型異常時也進行復原呢,可以使用 rollbackFor 屬性進行如下配置: