@Transactional 注解失效

先读一下下面这段代码:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MyService {

    @Autowired
    private UserRepository userRepository;  // 假设这是一个Spring Data JPA仓库

    @Transactional
    public void saveUserWithCatch() {
        try {
            User user = new User("John", "Doe");
            userRepository.save(user);  // 第一个数据库操作

            // 模拟一个异常
            if (true) {
                throw new RuntimeException("模拟异常");
            }

            User anotherUser = new User("Jane", "Doe");
            userRepository.save(anotherUser);  // 第二个数据库操作
        } catch (Exception e) {
            // 捕获异常,但不重新抛出
            System.out.println("捕获异常: " + e.getMessage());
        }
    }
}

saveUserWithCatch方法中,是否可以维持事务的原子性?即注解 @Transactional是否生效?答案是否定的。

让我们来看一下 @Transactional 注解的使用注意事项

1、@Transactional 注解使用注意事项


@Transactional 注解是 Spring 框架提供的声明式注解事务解决方案,我们在开发中使用事务保证方法对数据库操作的原子性,要么全部成功,要么全部失败,在使用 @Transactional 注解时,需要注意以下问题:

1、@Transactional 注解只能用在 public 方法上,如果用在 protected 或者 private 的方法上,不会报错,但是该注解不会生效。

2、@Transactional 注解只能回滚非检查型异常(什么是检查型异常,很好理解,在编译之前就可以被编译器识别的异常,即编译器提示代码错误),具体为 RuntimeException 及其子类和 Error 子类,可以从 Spring 源码的 DefaultTransactionAttribute 类里找到判断方法 rollbackOn。

	@Override
	public boolean rollbackOn(Throwable ex) {
		return (ex instanceof RuntimeException || ex instanceof Error);
	}

 3、检查事务传播行为(propagation behavior)。默认情况下,@Transactional使用 Propagation.REQUIRED,这应该适用于大多数情况,但在某些复杂场景下可能需要调整。

 4、@Transactional注解不能回滚被 try{} catch() 捕获的异常。

5、@Transactional注解只能对在被 Spring 容器扫描到的类下的方法生效。需要确保@Transactional注解的方法不是在同一类的其他方法中直接调用,如果在同一类中调用,事务挂历将不会生效。确保通过 Spring 代理调用此方法。

@Service
public class TemplateService {
    @Autowired
    private TemplateService templateService;
    
    public void outerMethod() {
        templateService.saveOrUpdateTemplate(...);  // 通过代理调用
    }
}

 6、确保底层数据库支持事务。例如,某些 NoSQL 数据库可能不支持事务,或者支持有限的事务。

7、多线程操作。确保所有数据库操作在同一个线程中执行,如果有多线程操作,事务管理可能会失效。

其实 Spring 事务的创建也是有一定的规则,对于一个方法里已经存在的事务,Spring 也提供了解决方案去进一步处理存在事务,通过设置 @Tranasctional 的 propagation 属性定义Spring 事务的传播规则。

2、Spring 事务的传播规则


Spring事务的传播行为一共有7种,定义在 spring-tx 模块的 Propagation 枚举类里,对应的常量值定义在 TransactionDefinition 接口里值为 int 类型的 0-6。

以下是 Spring 事务 7 种传播行为的特性:

传播规则

描述

例子

PROPAGATION_REQUIRED

支持当前事务,如果当前没有事务,则创建一个事务,这是最常用的传播行为,确保方法总是运行在事务中。

如果方法 A 调用方法 B,并且方法 A 已经在一个事务中,则方法 B 将加入这个事务。如果方法 A 没有事务,方法 B 将开始一个新事务。

PROPAGATION_SUPPORTS

支持当前事务,如果当前没有事务,就以非事务来执行

如果方法 A 调用方法 B,并且方法 A 在一个事务中,则方法 B 也将加入这个事务,如果方法 A 没有事务,方法 B 将以非事务方式加入。

PROPAGATION_MANDATORY

这种模式要求必须在一个现有事务中执行,如果没有事务存在,则抛出IllegalTransactionStateException

如果方法 A 调用方法 B,并且方法 A 没有事务,则方法 B 将抛出异常。

PROPAGATION_REQUIRES_NEW

这种模式确保方法总是运行在一个新的事务中,不管是否存在现有事务

如果方法 A 调用方法 B,并且方法 A 已经在一个事务中,则方法 A 的事务将被挂起,方法 B 将开始一个新事务。方法 B 完成后,方法 A 的事务将继续。

PROPAGATION_NOT_SUPPORTED

这种模式确保方法总是以非事务方式执行。

如果方法 A 调用方法 B,并且方法 A 已经在一个事务中,则方法 A 的事务将被挂起,方法 B 将以非事务方式执行

PROPAGATION_NEVER

这种模式要求方法不能在事务中执行,如果有现有事务,则抛出 IllegalTransactionStateException

如果方法 A 调用方法 B,并且方法 A 已经在一个事务中,则方法 B 将抛出异常

PROPAGATION_NESTED

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与 PROPAGATION_REQUIRED 类似的操作。嵌套事务是指一个事务的子事务,它依赖于父事务。

如果方法 A 调用方法 B,并且方法 A 在一个事务中,则方法 B 将在这个事务中创建一个嵌套事务。如果方法 A 没有事务,方法 B 将创建一个新事务

跟一下 TransactionDefinition 的源码(我的版本是:spring-tx:4.3.15.RELEASE)

跟一下,getPropagationBehavior(); 方法:

可以看到,默认事务就是 PROPAGATION_REQUIRED

可以进入到 AbstractPlatformTransactionManager类中,跟一下 getTransaction(@NullableTransactionDefinition definition) 方法,方法内部就是 spring 获取事务的逻辑:

如果事务存在,那么就交由 handleExistingTransaction方法处理:

感兴趣的小伙伴可以研究一下源码,但在实际开发中,我们只需要在 service 类上标记 @Transactional 注解,类下的方法都能根据指定的异常进行事务回滚,但切记要注意 @Transactional 的有效范围。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值