Transactional注解实现原理及失效场景

November 02, 2024 作者: southern 分类: 浏览: 4 评论: 0

Spring事务分为编程式事务和声明式事务

编程式事务可以使用 TransactionTemplate 和 PlatformTransactionManager 来实现,需要显式执行事务。允许在代码中直接控制事务的边界,通过编程方式明确指定事务的开始、提交和回滚。能够将事务做到代码块级别。

1.实现原理

声明式事务是建立在 AOP 之上的。其本质是通过 AOP 功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在目标方法执行完之后根据执行情况提交或者回滚事务。相比较编程式事务,优点是不需要在业务逻辑代码中掺杂事务管理的代码,最常用的是通过 @Transactional 注解的方式来实现声明式事务管理,也可以使用xml配置文件实现。不足的地方是,声明式事务管理只能作用到方法级别。

Spring 的声明式事务是通过 AOP和代理机制实现的。如果目标类实现了接口,Spring 采用 JDK 动态代理,代理对象是接口的实现类。如果目标类没有实现接口,Spring 采用 CGLIB 生成目标类的子类代理。

2.失效场景

2.1.Transactional注解作用在非public方法上

如果 Transactional 注解应用在非 public 修饰的方法上,Transactional 将会失效。

在 Spring AOP 代理时,TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource 的computeTransactionAttribute方法,获取 Transactional 注解的事务配置信息。此方法会检查目标方法的修饰符是否为 public,不是 public 则不会获取 @Transactional 的属性配置信息。

2.2.调用在同一个类中的方法

Spring 事务是通过代理对象实现的,类内部的方法调用不会经过代理,导致事务失效。

@Service
public class UserService {
​
    @Transactional
    public void updateUser() {
        updateData(); // 直接调用,不走代理,事务失效
    }
​
    public void updateData() {
        // 数据库操作
    }
}

这个问题在写点评的时候遇到过,特地讲了这里的解决方法

这里通过Spring上下文拿到代理对象,然后通过代理对象调用

@Autowired
private ApplicationContext context;
​
public void updateUser() {
    UserService self = context.getBean(UserService.class);
    self.updateData(); // 通过代理对象调用
}

2.3.事务传播机制设置错误

@Transactional(propagation = Propagation.NOT_SUPPORTED) 表示不支持事务,方法执行时事务会被挂起。

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void updateData() {
    // 事务不会生效
}
​

使用合适的事务传播机制@Transactional(propagation = Propagation.REQUIRED)

2.4.@Transactional 注解属性 rollbackFor 设置错误

rollbackFor 用来指定能够触发事务回滚的异常类型。Spring 默认抛出未检查 unchecked 异常(继承自 RuntimeException 的异常)或者 Error 才回滚事务,其他异常不会触发回滚事务。若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。

// 希望自定义的异常可以进行回滚
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class)

2.5.异常被catch

默认spring事务只在发生未被捕获的RuntimeExcetpion或Error时才回滚

@Transactional(rollbackFor=Exception.class)  //表示此方法有异常时触发Spring事务
public void updateUser() {
    try {
        // 数据库操作
        throw new RuntimeException("出现异常");
    } catch (Exception e) {
        // 异常被捕获,事务不会回滚
        //解决方法:
        //throw new Exception(e);  重新抛出一个 Exception,
        // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 手动回滚
    }
}

可以在catch后手动抛出一个异常,或者手动回滚


#2024(13)

评论