Spring在 TransactionDefinition
接口中规定了7种类型的事务传播行为。 事务传播行为是Spring框架独有的事务增强特性,不属于事务实际提供方即数据库的行为。
事务传播行为类型
说明
REQUIRED
如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。
SUPPORTS
支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY
使用当前的事务,如果当前没有事务,就抛出异常。
REQUIRES_NEW
新建事务,如果当前存在事务,把当前事务挂起。
NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER
以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则创建一个事务。
准备
POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>fun.roran</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- lombok start -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- lombok end -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
复制代码
application.yml
server:
port: 80
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
username: root
password: 123456
复制代码
sql
CREATE TABLE `tx` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`value` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
复制代码
DAO
TxDAO
package fun.roran.demo.dao;
public interface TxDAO {
void a();
void b();
}
复制代码
TxDAOImpl
package fun.roran.demo.dao.impl;
import fun.roran.demo.dao.TxDAO;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
@Repository
public class TxDAOImpl implements TxDAO {
@Resource
JdbcTemplate jdbcTemplate;
@Override
public void a() {
jdbcTemplate.execute("INSERT INTO tx (value) VALUES ('a')");
}
@Override
public void b() {
jdbcTemplate.execute("INSERT INTO tx (value) VALUES ('b')");
}
}
复制代码
Service
TxServiceA
package fun.roran.demo.service;
public interface TxServiceA {
void a();
}
复制代码
TxServiceAImpl
package fun.roran.demo.service.impl;
import fun.roran.demo.dao.TxDAO;
import fun.roran.demo.service.TxServiceA;
import fun.roran.demo.service.TxServiceB;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class TxServiceAImpl implements TxServiceA {
@Resource
private TxDAO txDAO;
@Resource
private TxServiceB txServiceB;
@Override
public void a() {
txDAO.a();
txServiceB.b();
}
}
复制代码
TxServiceB
package fun.roran.demo.service;
public interface TxServiceB {
void b();
}
复制代码
TxServiceBImpl
package fun.roran.demo.service.impl;
import fun.roran.demo.dao.TxDAO;
import fun.roran.demo.service.TxServiceB;
import javax.annotation.Resource;
public class TxServiceBImpl implements TxServiceB {
@Resource
private TxDAO txDAO;
@Override
public void b() {
txDAO.b();
}
}
复制代码
REQUIRED
如果当前没有事务,就新建一个事务。 如果已经存在一个事务中,加入到这个事务中。
默认为该级别。
情况1: a()
开启事务
以上文代码为例,在 a()
开启事务的情况下, a()
、 b()
相当于处于同一个事务。它们要么全都提交,要么全都不提交。
只要 b()
抛出异常, a()
、 b()
会一起回滚。 注意,即使 b()
的异常被 a()
捕获,仍是要一起回滚的。
a()
调用完 b()
后抛出异常回滚, b()
也会回滚。
情况2: a()
不开启事务
若 a()
不开启事务, b()
开启事务。
若 a()
调用完 b()
后抛出异常, b()
自然不会回滚。 同样若 b()
抛出异常回滚,也不会影响 a()
已经执行过的代码。
SUPPORTS
支持当前事务,如果当前没有事务,就以非事务方式执行。
情况1: a()
开启事务
在 a()
开启事务的情况下, b()
会加入 a()
的事务。二者属于同一事务。
只要 b()
抛出异常, a()
、 b()
会一起回滚。 注意,即使 b()
的异常被 a()
捕获,仍是要一起回滚的。
若 a()
调用 b()
后抛出异常,则 b()
也会回滚。
情况2: a()
不开启事务
若 a()
不开启事务,则 a()
、 b()
都处在非事务环境下。
MANDATORY
情况1: a()
开启事务
在 a()
开启事务的情况下, b()
会加入 a()
的事务。二者属于同一事务。
只要 b()
抛出异常, a()
、 b()
会一起回滚。 注意,即使 b()
的异常被 a()
捕获,仍是要一起回滚的。
若 a()
调用 b()
后抛出异常,则 b()
也会回滚。
情况2: a()
不开启事务
异常
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
复制代码
REQUIRES_NEW
情况1: a()
开启事务
a()
开启事务,执行 b()
时,会将 a()
的事务挂起, b()
创建一个自己的事务。
b()
抛出异常回滚,若 a()
没有对异常进行补获,则也要回滚。 若 a()
捕获了异常,则 a()
不用回滚。
a()
调用完 b()
后抛出异常, a()
回滚, b()
不用回滚(因为 b()
的事务已经提交了)。
情况2: a()
不开启事务
即 b()
会开启一个自身的事务。 b()
若发生异常回滚不会影响到 a()
已执行操作, a()
调用完 b()
后抛出异常自然不会影响到 b()
。
NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
情况1: a()
开启事务
a()
开启事务,但在执行 b()
的过程中, a()
的事务被挂起且 b()
是无事务方式执行的。
若 a()
调用 b()
后抛出异常,则 b()
不会被回滚(除了其他 b()
的其它操作可以回滚)。
若 b()
执行中抛出异常,则 b()
已经执行的操作也不会回滚。 同时若 a()
没有捕获 b()
抛出的异常, a()
也会被回滚。若 a()
捕获了,则不会被回滚。
总而言之,不管 a()
、 b()
哪个操作抛出异常且未捕获, a()
一定会被回滚, b()
一定不会回滚。
情况2: a()
不开启事务
NEVER
情况1: a()
开启事务
异常
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
复制代码
情况2: a()
不开启事务
NESTED
如果当前存在事务,则在嵌套事务内执行。 如果当前没有事务,则创建一个事务。
情况1: a()
开启事务
调用 b()
时,开启一个事务,这个事务是 a()
的子事务。
当 b()
抛出异常并回滚时。该异常若被 a()
捕获,则 a()
不会回滚,否则 a()
回滚。
a()
调用 b()
后抛出异常,则 a()
、 b()
一起回滚。
情况2: a()
不开启事务
总结
事务传播失效
同一个类中事务传播失效
如果将上文的 a()
、 b()
方法写在同一个类中,那么事务传播行为将失效。
因为事务传播行为的实现是通过代理对象实现的。而原来的对象是没有事务传播行为功能的。
代码示例
package fun.roran.demo.service.impl;
import fun.roran.demo.dao.TxDAO;
import fun.roran.demo.service.TxServiceA;
import fun.roran.demo.service.TxServiceB;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
@Service
public class TxServiceAImpl implements TxServiceA {
@Resource
private TxDAO txDAO;
@Transactional(rollbackFor = Exception.class)
@Override
public void a() {
txDAO.a();
b();
}
@Transactional(propagation = Propagation.NEVER, rollbackFor = Exception.class)
@Override
public void b() {
txDAO.b();
}
}
复制代码
调用 a()
方法,发现能够正常运行,不抛出异常。
原因:根据 TxServiceAImpl
对象生产代理对象后,代理对象底层还是调用 TxServiceAImpl
对象的 b()
方法,而这个方法是不支持事务传播功能的。
解决方法
不通过 this
调用方法,而是通过注入代理对象类调用。
代码示例
package fun.roran.demo.service.impl;
import fun.roran.demo.dao.TxDAO;
import fun.roran.demo.service.TxServiceA;
import fun.roran.demo.service.TxServiceB;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
@Service
public class TxServiceAImpl implements TxServiceA {
@Resource
private TxDAO txDAO;
@Resource
private TxServiceA txServiceA;
@Transactional(rollbackFor = Exception.class)
@Override
public void a() {
txDAO.a();
txServiceA.b();
}
@Transactional(propagation = Propagation.NEVER, rollbackFor = Exception.class)
@Override
public void b() {
txDAO.b();
}
}
复制代码
https://juejin.im/post/5e88103ce51d4546e8575a4d