Transactional注解实现原理及失效场景
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后手动抛出一个异常,或者手动回滚
评论