一. 代码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
// transactionManager() 的别名,用于指定事务管理器的名称。可以通过指定事务管理器的名称来指定要使用的特定事务管理器。
@AliasFor("transactionManager")
String value() default "";
// 用于指定事务管理器的名称。可以通过指定事务管理器的名称来指定要使用的特定事务管理器。
@AliasFor("value")
String transactionManager() default "";
// 指定事务的传播行为。它定义了事务方法被嵌套调用时的事务传播规则。
Propagation propagation() default Propagation.REQUIRED;
// 指定事务的隔离级别。它定义了事务方法对数据的访问和锁定的级别。
Isolation isolation() default Isolation.DEFAULT;
// 指定事务的超时时间(以秒为单位)。如果事务在超过指定时间时仍未提交,则将自动回滚事务。
int timeout() default -1;
// 指定事务是否为只读事务。如果设置为 true,则表示事务只读,不会修改数据库的内容。
boolean readOnly() default false;
// 指定在遇到指定的异常时需要回滚事务。可以指定一个或多个异常类型。
Class<? extends Throwable>[] rollbackFor() default {};
// 与 rollbackFor() 类似,但以字符串数组的形式指定异常类的全限定名。
String[] rollbackForClassName() default {};
// 指定在遇到指定的异常时不需要回滚事务。可以指定一个或多个异常类型。
Class<? extends Throwable>[] noRollbackFor() default {};
// 与 noRollbackFor() 类似,但以字符串数组的形式指定异常类的全限定名。
String[] noRollbackForClassName() default {};
}
二. 事务的传播行为
Spring框架提供了事务管理功能,其中事务的传播机制定义了在方法调用链中嵌套事务的行为。事务传播机制用于控制事务的边界和行为,以确保数据的一致性和完整性。
声明式事务的传播行为可以通过 @Transactional 注解中的 propagation 属性来定义
1.REQUIRED
如果调用方存在事务,则被调用方加入到该事务中;如果调用方没有事务,则被调用方创建一个新的事务。
1.1 方法 A B 均有注解, A 调用 B 时, B 中抛异常
@Override
@Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
testB(record);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void testB(TestTable record){
record.setValue("小红");
testTableMapper.insert(record);
if (true){
throw new RuntimeException("报错了");
}
}
当方法B抛出异常后, 方法A和B的事务均回滚
1.2 方法 A 有注解, 方法 B 无注解, A 调用 B 时, B 中抛异常
@Override
@Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
testB(record);
}
@Override
// @Transactional(rollbackFor = Exception.class)
public void testB(TestTable record){
record.setValue("小红");
testTableMapper.insert(record);
if (true){
throw new RuntimeException("报错了");
}
}
当方法 B 抛出异常后, 方法 A 和 B 的事务均回滚
1.3 方法 A 无注解, 方法 B 有注解, A 调用 B 时, B 中抛异常
1.3.1 自调用方式
@Override
// @Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
testB(record);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void testB(TestTable record){
record.setValue("小红");
testTableMapper.insert(record);
if (true){
throw new RuntimeException("报错了");
}
record.setValue("小黄");
testTableMapper.insert(record);
}
方法 A 和 B 均没有回滚失效, 数据库中插入了 小明和小红, 没有插入小黄
1.3.2 Spring 代理对象调用
@Service
public class TestServiceImpl implements ITestService {
@Autowired
private ITestService testService;
@Autowired
TestTableMapper testTableMapper;
@Override
// @Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
testService.testB(record);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void testB(TestTable record){
record.setValue("小红");
testTableMapper.insert(record);
if (true){
throw new RuntimeException("报错了");
}
record.setValue("小黄");
testTableMapper.insert(record);
}
}
方法 A 没有回滚, 数据库中插入了小明, 方法 B 事务进行了回滚, 数据库中没有插入小红和小黄
1.4 方法 A B 均有注解, A 调用 B 时, A 中抛异常
@Override
@Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
testB(record);
if (true){
throw new RuntimeException("报错了");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void testB(TestTable record){
record.setValue("小红");
testTableMapper.insert(record);
}
方法 A 和 B 均回滚, 数据库中没有插入小明和小红
1.5 方法 A 有注解, 方法 B 无注解, A 调用 B 时, A 中抛异常
@Override
@Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
testB(record);
if (true){
throw new RuntimeException("报错了");
}
}
@Override
// @Transactional(rollbackFor = Exception.class)
public void testB(TestTable record){
record.setValue("小红");
testTableMapper.insert(record);
}
方法 A 和 B 均回滚, 数据库中没有插入小明和小红
1.6 方法 A 无注解, 方法 B 有注解, A 调用 B 时, A 中抛异常
1.6.1 自调用
@Override
// @Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
testB(record);
if (true){
throw new RuntimeException("报错了");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void testB(TestTable record){
record.setValue("小红");
testTableMapper.insert(record);
}
当 A 抛出异常时, 方法 A 和 B 均没回滚, 数据库插入了小明和小红
1.6.2 Spring 代理对象调用
@Service
public class TestServiceImpl implements ITestService {
@Autowired
private ITestService testService;
@Autowired
TestTableMapper testTableMapper;
@Override
// @Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
testService.testB(record);
if (true){
throw new RuntimeException("报错了");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void testB(TestTable record){
record.setValue("小红");
testTableMapper.insert(record);
}
}
当 A 抛出异常时, 方法 A 和 B 均没回滚, 数据库插入了小明和小红
1.7 方法 A B 都有注解, 在 Controller 中同时调用, 在 B 中抛异常
@GetMapping("/test")
public ResultMap insertTest() {
TestTable a = new TestTable();
testService.testA(a);
testService.testB(a);
return Result.success("ok");
}
@Override
@Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void testB(TestTable record){
record.setValue("小红");
testTableMapper.insert(record);
if (true){
throw new RuntimeException("我报错了");
}
}
方法 A 没有受到影响, 小明顺利插入, B 事务进行回滚, 小红没有被插入, 所以在 Controller 同时调用两个事务型方法时, 无法保证该接口整体回滚。
总结: 当方法进行调用时, 如果调用方存在事务, 则无论被调用方是否存在事务, 都会将被调用方加入到当前事务中; 如果调用方不存在事务, 被调用方存在事务, 则被调用方会自己开启事务, 调用方不受被调用方的事务影响。并且相互间调用时, 采用自调用的方式可能会使事务失效。
2.SUPPORTS
调用方存在事务, 则被调用方加入到该事务中, 调用方不存在事务, 被调用方也不开启新的事务。这种传播行为适用于希望在事务存在时加入事务,但在非事务环境中也可以执行的情况。
2.1 A 调用 B, B方法中事务设为 SUPPORTS 传播行为
@Override
@Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
testService.testB(record);
}
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
public void testB(TestTable record){
record.setValue("小红");
testTableMapper.insert(record);
if (true){
throw new RuntimeException("报错了");
}
}
方法 A 和 B 均发生了回滚, 数据库没有插入任何数据
2.2 关闭方法 A 的事务
@Override
// @Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
testService.testB(record);
}
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
public void testB(TestTable record){
record.setValue("小红");
testTableMapper.insert(record);
if (true){
throw new RuntimeException("报错了");
}
}
方法 A 和 B 都没有发生回滚, 数据库插入了小明和小红, 该示例可以与 1.3.2示例 做对比
3.MANDATORY
如果调用方存在事务,则被调用方加入该事务中;如果调用方没有事务,则抛出异常。
4.REQUIRES_NEW
如果调用方存在事务,则把该事务挂起。被调用方会开启自己的事务,且开启的事务与调用方的事务相互独立,互不干扰, 调用方发生异常后, 被调用方的的事务不受影响, 因为 REQUIRES_NEW 会暂停调用方的事务, 直到被调用方的事务提交或者回滚才恢复执行。
4.1 方法 B 开启 REQUIRES_NEW 传播行为
@Override
@Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
testService.testB(record);
if (true){
throw new RuntimeException("报错了");
}
}
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void testB(TestTable record){
record.setValue("小红");
testTableMapper.insert(record);
}
方法 A 报错后, B 方法正常执行, 数据库插入小红, 而 A 事务回滚, 数据库中没有插入小明
5.NOT_SUPPORTED
以非事务方式执行,如果调用方存在事务,则将该事务挂起。
@Override
@Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
testService.testB(record);
}
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
public void testB(TestTable record){
record.setValue("小红");
testTableMapper.insert(record);
if (true){
throw new RuntimeException("报错了");
}
}
方法 B 报错后, 数据库插入小红, 而 A 事务回滚, 数据库中没有插入小明
6.NEVER
以非事务方式运行,如果当前存在事务,则抛出异常。
7.NESTED
它表示当前方法被调用时,如果已经存在一个事务,那么当前方法就会在这个已存在的事务中创建一个嵌套事务;如果当前方法没有事务,则会创建一个新的事务。嵌套事务是相对于外层事务的,嵌套在外层事务内部。嵌套事务可以独立进行提交或回滚,并且嵌套事务的提交不会影响外层事务的提交,但如果外层事务回滚,则嵌套事务也会被回滚。
7.1 方法 A 中捕获 方法 B 的异常
@Override
@Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
try{
testService.testB(record);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
public void testB(TestTable record){
record.setValue("小红");
testTableMapper.insert(record);
if (true){
throw new RuntimeException("报错了");
}
}
方法 A 没有受到影响, 方法 B 回滚, 数据库中只插入了小明
三. 其它几种调用形式
1.在本方法中抛出异常并捕获
@Override
@Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
try{
if (true){
throw new RuntimeException("报错了");
}
}catch (Exception e){
e.printStackTrace();
}
}
事务失效, 数据库中插入小明
2.A 方法捕获 B方法的异常
2.1 自调用方式
@Override
@Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
try{
testB(record);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void testB(TestTable record){
record.setValue("小红");
testTableMapper.insert(record);
if (true){
throw new RuntimeException("报错了");
}
}
事务均失效, 数据库中插入了小明和小红
这是因为在 Spring 中,事务注解的生效需要满足以下两个条件:
1. 被调用的方法必须是 Spring 管理的 Bean,并且被代理才能使事务注解生效。
2. 事务注解必须是通过方法的公有接口调用,而不是通过内部方法调用,即所谓的“自调用”。
2.2 Spring 代理对象调用
@Override
@Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
try{
testService.testB(record);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void testB(TestTable record){
record.setValue("小红");
testTableMapper.insert(record);
if (true){
throw new RuntimeException("报错了");
}
}
报异常 Transaction rolled back because it has been marked as rollback-only, 这是因为在方法B 中标记了 rollback-only,但是后面的程序执行后又commit事务,所以抛出此异常。然后方法A B 均回滚。
3.多线程间的传递情况
3.1 两个方法都开启事物, B方法报错
@Override
@Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
Thread thread = new Thread(() ->
testService.testB(record));
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void testB(TestTable record){
record.setValue("小红");
testTableMapper.insert(record);
if (true){
throw new RuntimeException("报错了");
}
}
A方法没有回滚, B方法回滚了, 数据库插入了小明而没有小红
3.2 A 方法开启事物, B方法不开启
@Override
@Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
Thread thread = new Thread(() ->
testService.testB(record));
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
// @Transactional(rollbackFor = Exception.class)
public void testB(TestTable record){
record.setValue("小红");
testTableMapper.insert(record);
if (true){
throw new RuntimeException("报错了");
}
}
方法 AB 均没有回滚, 数据库中插入了 小明和小红
3.3 方法A开启事物, 方法B 不开启, A中报错
@Override
@Transactional(rollbackFor = Exception.class)
public void testA(TestTable record) {
record.setValue("小明");
testTableMapper.insert(record);
Thread thread = new Thread(() ->
testService.testB(record));
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (true){
throw new RuntimeException("报错了");
}
}
@Override
// @Transactional(rollbackFor = Exception.class)
public void testB(TestTable record){
record.setValue("小红");
testTableMapper.insert(record);
}
方法A 发生回滚, 方法B 没有, 数据库中只插入了小红
总结: 线程间没有事物的传递
四.事务失效的一些场景
1.事务方法未被Spring管理
如果事务方法所在的类没有注册到Spring IOC容器中,也就是说,事务方法所在类并没有被Spring管理,则Spring事务会失效。
2.方法使用final类型修饰
@Transactional 是使用 Spring AOP 实现的,而 Spring AOP 的底层是通过动态代理实现,在代理类中实现事务功能。如果某个方法用final修饰了,那么就无法在代理类中重写该方法,从而无法添加事务功能。
3.非public修饰的方法
@Transactional 在生成代理时会判断,如果方法为非 public 修饰的方法,则不生成代理对象。
4.同一个类中的方法相互调用
因为Spring的事务管理通常是基于AOP(面向切面编程)实现的,而AOP通过动态代理在运行时织入事务逻辑。当一个带有 @Transactional 注解的方法被调用时,Spring会创建一个事务上下文,并将其绑定到当前线程。如果该方法在执行过程中调用了同一个类中的另一个方法,调用会直接进入该方法,而不会经过AOP代理。由于AOP代理没有介入,事务上下文也就无法传播到被调用的方法,导致事务失效。
为了在同一个类中的方法相互调用时保持事务的有效性,可以使用self-invocation(自我调用)的方式,通过获取Spring容器的ApplicationContext并通过它来获取当前类的代理对象,然后通过代理对象调用方法,这样AOP代理会正确拦截方法调用并应用事务管理。
@Service
public class MyService {
@Autowired
private ApplicationContext context;
@Transactional
public void methodA() {
// 此处通过代理对象调用methodB(),事务将保持有效
MyService proxy = context.getBean(MyService.class);
proxy.methodB();
}
@Transactional
public void methodB() {
// 执行逻辑
}
}
5. 方法的事务传播类型不支持事务
比如 NOT_SUPPORTED、SUPPORTS、NEVER 都可能不会开启事务
6. 异常被catch,并且内部消化沒有抛出
7. 数据库不支持事务
8. 未配置开启事务
Springboot 有时候会帮我们进行自动配置事务管理, 但是如果没有的话, 需要手动进行配置。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
@Configuration
public class TransactionConfig {
@Autowired
private DataSource dataSource;
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource);
}
}
9. 多线程调用
事务不会在线程间传递
五. Isolation 事物隔离级别
1. DEFAULT(-1):表示使用默认的事务隔离级别。实际的默认隔离级别取决于数据库管理系统的配置。
2. READ_UNCOMMITTED(1):读未提交, 表示最低的事务隔离级别。在这个隔离级别下,一个事务对数据的修改即使未提交,对其他事务也是可见的。这样的隔离级别可能导致脏读、不可重复读和幻读问题。
3. READ_COMMITTED(2):读已提交, 表示在一个事务提交后,其他事务才能看到这个事务对数据的修改。这样的隔离级别可以避免脏读问题,但可能导致不可重复读和幻读问题。
4. REPEATABLE_READ(4):可重复读, 表示在一个事务执行期间,其他事务不能对它所读取的数据进行修改。这样的隔离级别可以避免脏读和不可重复读问题,但可能导致幻读问题。
5. SERIALIZABLE(8):串行化, 表示最高的事务隔离级别。在这个隔离级别下,一个事务在执行期间完全串行化执行,其他事务不能对它所读取的数据进行修改。这样的隔离级别可以避免脏读、不可重复读和幻读问题,但可能降低并发性能。