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 修饰的方法上

  1. 场景重现:当调用的方法不是 public 修饰时,不会回滚,事务失效
  2. 原因分析:在 spring aop 代理时,事务拦截器会在目标方法的执行前后进行拦截,其中的方法会去获取 Transactional 注解的一些信息,而其中的方法会去检查目标方法是否被 public 修饰,不是的话获取不到注解信息
  3. 解决方案:将调用的方法访问修饰符改为 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的代理类,默认只有外部调用事务才会生效。

解决办法

  1. 新加一个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();
    }
 
 }

  1. 只需要新加一个 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(非运行时异常),它不会回滚。

  • 16
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值