java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring事务失效的问题

Spring事务失效的问题及解决方案

作者:张 先生

本文详细分析了Spring事务失效的原因,并提供了一个解决方案,在代码中,事务传播属性设置为REQUIRES_NEW解决了事务失效的问题,确保了日志记录的操作不会影响主事务的回滚,同时,文中还总结了各种事务传播属性的含义和使用场景

Spring事务失效问题

开场

Spring事务管理,在Service实现类的方法上添加@Transactional注解就能进行事务控制,但是最近遇到一个事务失效的问题,浅浅分析一下

问题

在开发过程中发现这样一个bug,有一段逻辑代码:商品入库、并且生成一个入库单的操作;首先肯定的是需要操作两张表,一是 入库加库存,二是 新增一个入库单类似于入库记录。但是测试时候遇到一个问题,有一个商品入库成功了,库存也加进去了,但是没有生成入库单,我立马想到的是难道是事务没有控制好?翻看接口代码发现还真是。。。

Spring事务失效的原因

我们都知道@Transactional失效的原因有多种,列举一下,后面有时间再仔细分析研究

1.方法访问修饰符:

2.类内部方法调用:

3.没有启用事务管理:

<tx:annotation-driven/>

4.事务传播属性不当:

5.异常处理不当:

@Transactional(rollbackFor = Exception.class)

6.多线程环境:

7.数据源和事务管理器配置不一致:

8.Spring代理模式限制:

9.异步方法:

排查bug

言归正传,我在这段逻辑代码中发现有两个问题,首先代码块被 try catch 捕获后没有重新抛出异常,而且这段代码在catch 里面有日志记录,插入到一张日志表;业务逻辑太多此处就不贴真实代码了,但是我写了一个demo是可以复刻问题的。

代码如下:

@Transactional(rollbackFor = Exception.class)
public User update(User userDto) {
    try {
        //查询用户
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(StrUtil.isNotBlank(userDto.getId()), User::getId, userDto.getId());
        User user = userDao.selectOne(queryWrapper);
        //修改数据:扣钱
        user.setMoney(user.getMoney().subtract(userDto.getMoney()));
        LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(StrUtil.isNotBlank(userDto.getId()), User::getId, userDto.getId());
        userDao.update(user);
        //模拟异常
        int i = 1/0;
    } catch (Exception e) {
        log.error(e.getMessage(),e);
        //日志记录:将错误信息插入到日志表
        insertLog(userDto, e);
    }
    return null;
}

    /*
    * 日志记录的方法
    */
    public void recordLog(Exception e) {
        LogHistory logHistory = new LogHistory();
        logHistory.setCode("1");
        logHistory.setTime(new Date());
        logHistory.setErrorMsg(e.getMessage());
        logHistoryMapper.insert(logHistory);
    }

上面这段代码,大家还有发现问题其他吗,细心的小伙伴一定还能发现其他问题,那就是这个 catch 捕获异常后并没有抛出,所以这段代码并不会回滚,但是问题来了,如果我们在 catch 中最后抛出异常,那么日志记录的操作也将回滚,这里就会有点冲突,如果代码有异常报错,我们的目的就是要回滚,但是还不能把日志记录给回滚。

解决方案

发现了问题,该如何去解决呢?

在这里我用到了一个事务的传播行为:先说结论,将事务的传播属性设置为REQUIRES_NEW,就是在当前事务中新建了一个事务,用新的事务去控制我日志记录的操作,这样的话两个事务互不影响,A事务如果抛出异常,不影响B事务,B事务正常插入数据,完美解决!当然不止这一种解决方案,还有异步调用,mq发消息等方式。

但是需要注意,我们需要在单独的Service实现类中去编写日志记录的方法,不能在A事务中去编写了,代码如下:

    @Transactional(rollbackFor = Exception.class)
    public User update(User userDto) {
        try {
            //查询用户
            LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(StrUtil.isNotBlank(userDto.getId()), User::getId, userDto.getId());
            User user = userDao.selectOne(queryWrapper);
            //修改数据:扣钱
            user.setMoney(user.getMoney().subtract(userDto.getMoney()));
            LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(StrUtil.isNotBlank(userDto.getId()), User::getId, userDto.getId());
            userDao.update(user);
            //模拟异常
            int i = 1/0;
        } catch (Exception e) {
            log.error(e.getMessage(),e);
            //日志记录:需要在另外的service中编写
            logHistoryService.insertLog(userDto, e);
            //抛出异常
            throw e;
        }
        return null;
    }
    /*
    * 日志记录
    * 此处我们需要设置事务的传播属性为 REQUIRES_NEW
    */
	@Transactional(propagation = Propagation.REQUIRES_NEW)
    public LogHistory insertLog(User userDto, Exception e) {
        LogHistory logHistory = new LogHistory();
        logHistory.setCode("111");
        logHistory.setTime(new Date());
        logHistory.setErrorMsg(e.getMessage());
        logHistoryMapper.insert(logHistory);
        return logHistory;
    }

事务的传播属性

在 Spring 中,事务的传播属性(Propagation)决定了事务的行为在方法间调用时的传播方式。传播属性通过 @Transactional 注解的 propagation 属性来设置,Spring 提供了以下几种传播属性:

1.REQUIRED(默认值):

2.REQUIRES_NEW

3.SUPPORTS

4.NOT_SUPPORTED

5.MANDATORY

6.NEVER

7.NESTED

8.NEVER

9.NESTED

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文