spring@Transactional不生效
数据库引擎不支持事务
以MySQL为例,其MyISAM引擎是不支持事务操作的,InnoDB才是支持事务的引擎。
从MySQL 5.5.5开始默认的存储引擎是:InnoDB,之前默认都是MyISAM。
所以值得注意,底层引擎不支持事务再怎么搞都是白搭。
数据源没有配置事务管理器
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
并且需要在Springboot启动类加上 @EnableTransactionManagement
注解
方法用final修饰或者static修饰
spring 事务底层使用了 aop,也就是通过 jdk 动态代理或者 cglib,帮我们生成了代理类,在代理类中实现的事务功能。
但如果某个方法用 final 修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能。
方法不是public修饰的
注意:如果某个方法是 static 的,同样无法重写该方法,所以无法通过动态代理,变成事务方法。
@Transactional 应用在非 public 修饰的方法上
- 场景重现:当调用的方法不是 public 修饰时,不会回滚,事务失效
- 原因分析:在 spring aop 代理时,事务拦截器会在目标方法的执行前后进行拦截,其中的方法会去获取 Transactional 注解的一些信息,而其中的方法会去检查目标方法是否被 public 修饰,不是的话获取不到注解信息
- 解决方案:将调用的方法访问修饰符改为 public
该类自身方法内部调用 不会生效
种场景很常见,方法A调用方法B,其中方法A未使用事务,而方法B使用了事务,此时方法B的事务是不生效的,例子如下,因为updateOrder方法拥有事务的能力是因为 spring aop 生成代理了对象,但是这种方法直接调用了 this 对象的方法,所以 updateStatus 方法不会生成事务。
先来两个例子:
例子一:update方法上面没有加 @Transactional 注解,调用有 @Transactional 注解的 updateOrder 方法
@Service
public class OrderServiceImpl implements OrderService {
public void update(Order order) {
updateOrder(order);
}
@Transactional
public void updateOrder(Order order) {
// doSomeThing()
}
}
例子二:update和updateOrder方法上加了 @Transactional,updateOrder 加了 REQUIRES_NEW 新开启一个事务
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void update(Order order) {
updateOrder(order);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateOrder(Order order) {
// update order
}
}
以上两个例子事务不起效,自身调用没有经过Spring的代理类,默认只有外部调用事务才会生效。
解决办法
- 新加一个service方法
只需要新加一个 Service 方法,把 @Transactional 注解加到新 Service 方法上,把需要事务执行的代码移到新方法中。具体代码如下:
@Servcie
public class ServiceA {
@Autowired
prvate ServiceB serviceB;
public void save(User user) {
queryData1();
queryData2();
serviceB.doSave(user);
}
}
@Servcie
public class ServiceB {
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}
- 只需要新加一个 Service 方法,把 @Transactional 注解加到新 Service 方法上,把需要事务执行的代码移到新方法中。具体代码如下:
@Servcie
public class ServiceA {
public void save(User user) {
queryData1();
queryData2();
((ServiceA)AopContext.currentProxy()).doSave(user);
}
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}
抛出非运行时异常
spring 事务,默认情况下只会回滚RuntimeException(运行时异常)和Error(错误),对于普通的
Exception(非运行时异常),它不会回滚。
如果事务注解使用的是@Transactional(),即使异常抛出了,但是抛出的是非RuntimeException类型异常,同样也不会回滚。
如果事务注解使用的是@Transactional(rollbackFor = Exception.class),那么抛出的是非RuntimeException类型异常是可以回滚的。
注: 也就是说,如果抛的异常不正确,spring 事务也不会回滚。
比如,方法上的事务是:@Transactional(rollbackFor = BusinessException.class),而实际执行业务逻辑时,程序报错,抛了 SqlException、DuplicateKeyException 等异常。而 BusinessException 是我们自定义的异常,报错的异常不属于 BusinessException,所以事务也不会回滚。
所以,建议一般情况下,将该参数设置成:Exception 或 Throwable。
自己吞了异常
事物不会回滚,最常见的问题是:开发者在代码中手动try…catch了异常。比如:
@slf4j
@Service
public class UserService{
@Transactional
public void add(UserModel userModel){
try{
saveData(userModel);
updateData(userModel);
}catch(Exception e){
log.error(e.getMessage,e);
}
}
}
这种情况下spring事务当然不会回滚,因为开发者自己捕获了异常,又没有手动抛出,换句话说就是把异常吞掉了。
如果想要spring事务能够正常回滚,必须抛出它能够处理的异常,如果没有抛出异常,则spring认为程序是正常的。
手动抛出了别的异常
即使开发者没有手动捕获异常,但是抛出的异常不正确,spring事务也不会回滚。
@slf4j
@Service
public class UserService{
@Transactional
public void add(UserModel userModel) throws Exception{
try{
saveData(userModel);
updateData(userModel);
}catch(Exception e){
log.error(e.getMessage,e);
throw new Exception(e);
}
}
}
上面的情况,开发人员自己捕获了异常:Exception,事务同样不会回滚。
因为spring事务,默认情况下只会回滚RuntimeException(运行时异常)和Error(错误),对于普通的Exception(非运行时异常),它不会回滚。