SpringBoot中的事务处理以及问题分享

一、什么是事务

数据库事务是指作为单个逻辑工作单元执行的一系列操作,这些操作要么一起成功,要么一起失败,是一个不可分割的工作单元。

二、Spring Boot中的事务管理

三大基础组件:PlatformTransactionManager事务管理器接口、TransactionDefinition、TransactionStatus

- PlatformTransactionManager : 事务管理器
- TransactionDefinition : 事务的一些基础属性信息,如超时时间、隔离级别、传播属性、回滚规则等。
- TransactionStatus: 事务的一些状态信息,如是否是一个新的事务、是否已被标记为回滚。

事务的传播性:在不同service层调事务。

在 Spring Boot 中操作事务有两种方式:编程式事务或声明式事务。

1、编程式事务

把事务代码嵌入到业务逻辑中。代码耦合度高。

在 Spring Boot 中实现编程式事务有两种方法:对于编程式事务管理,spring推荐使用TransactionTemplate。

1、使用 TransactionTemplate 对象实现编程式事务;

        @Autowired
        private TransactionTemplate transactionTemplate;
    
        @Autowired
        private PlatformTransactionManager transactionManager;
    
        @Autowired
        private FileDao fileDao;
    
        @Test
        public void test() {
            // 1、定义事务的默认属性
            DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
            // 2、获取TransactionStatus
            TransactionStatus status = transactionManager.getTransaction(definition);
            try {
                FilePo filePo = new FilePo();
                filePo.setId(UUID.randomUUID().toString());
                fileDao.insert(filePo);
                // 提交事务
                transactionManager.commit(status);
            } catch (DataAccessException e) {
                // 回滚事务
                transactionManager.rollback(status);
            }
        }

2、使用更加底层的TransactionManager对象实现编程式事务。

2、声明式事务

原理:基于AOP的思想,将事务和业务代码解耦,无入侵。

一般通过在方法上或类上添加 @Transactional 注解实现声明式事务。当标注在类上时,表示该类的所有public方法都将支持事务。当标注在方法上时,表示该方法将在一个事务内执行。

在方法执行前,自动开启事务;在方法成功执行完,自动提交事务;如果方法在执行期间,出现了异常,那么它会自动回滚事务。

1、@Transactional事务的属性:

参数    意义
isolation    事务隔离级别,默认为DEFAULT
propagation    事务传播机制,默认为REQUIRED
readOnly    事务读写性,默认为false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
noRollbackFor    一组异常类,遇到时不回滚,默认为{}
noRollbackForClassName    一组异常类名,遇到时不回滚,默认为{}
rollbackFor    一组异常类,遇到时回滚,默认为{}
rollbackForClassName    一组异常类名,遇到时回滚,默认为{}
timeout    超时时间,以秒为单位,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
value    可选的限定描述符,指定使用的事务管理器,默认为“”

2、什么时候需要添加@Transactional注解:

3、@Transactional失效场景:

1、同一个类中方法调用,导致失效:由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。

EmployeeServiceImpl类:    
    @Transactional
    public void calledFunc() {
        LambdaUpdateWrapper<Employee> wrapper = new LambdaUpdateWrapper<>();
        wrapper.set(Employee::getName, "修改后的姓名").eq(Employee::getId, "1");
        employeeMapper.update(null, wrapper);
        throw new RuntimeException("异常");
    }

    @Override
    public void test() {
        calledFunc();
    }
Test类:
    @Test
    void testTransaction() {
        employeeService.test();
    }

此时虽然方法抛出了异常,但是事务控制失效,异常事务并没有回滚,数据更新成功了。

必须通过代理过的类从外部调用目标方法才能生效。

2、如果一个方法添加了@Transactional注解声明事务,而方法内又使用了try catch 捕捉异常,则方法内的异常捕捉会覆盖事务对异常的判断,从而异致事务失效而不回滚。

EmployeeServiceImpl类:
    @Transactional
    public void test2() {
        try {
            LambdaUpdateWrapper<Employee> wrapper = new LambdaUpdateWrapper<>();
            wrapper.set(Employee::getName, "修改后的姓名").eq(Employee::getId, "1");
            employeeMapper.update(null, wrapper);
            throw new RuntimeException("异常");
        } catch (RuntimeException e) {
            System.out.println("捕获到了异常");
        }
    }

如何解决:

 第一个方法:给@Transactional注解增加:rollbackFor属性并手动抛出指定的异常。

 第二个方法:在捕捉到异常后手动rollback。

3、@Transactional 属性 rollbackFor 设置错误,导致异常不满足回滚条件

EmployeeServiceImpl类:   
   @Transactional
    public void calledFunc() throws Exception{
        LambdaUpdateWrapper<Employee> wrapper = new LambdaUpdateWrapper<>();
        wrapper.set(Employee::getName, "修改后的姓名").eq(Employee::getId, "1");
        employeeMapper.update(null, wrapper);
        throw new Exception("受检异常");
    }

    @Override
    @Transactional
    public void test() throws Exception {
        calledFunc();
    }
    @Test
    void testTransaction() {
        try {
            employeeService.test();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

解决方法:设置rollbackFor:@Transactional(rollbackFor = Exception.class)

4、@Transactional 注解传播属性 propagation 设置错误,父事务回滚子事务不会回滚:

EmployeeServiceImpl类:
    @Override
    @Transactional
    public void test() {
        LambdaUpdateWrapper<Employee> wrapper = new LambdaUpdateWrapper<>();
        wrapper.set(Employee::getName, "修改后的姓名").eq(Employee::getId, "2");
        employeeMapper.update(null, wrapper);
        detectApplicationDealer.calledFunc();
        throw new RuntimeException("受检异常");
    }
NameDetectApplicationDealer类:
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void calledFunc() {
        LambdaUpdateWrapper<Employee> wrapper = new LambdaUpdateWrapper<>();
        wrapper.set(Employee::getName, "修改后的姓名").eq(Employee::getId, "1");
        employeeMapper.update(null, wrapper);
    }

调用方法抛出异常,被调用方法使用Propagation.REQUIRES_NEW属性,会开启一个新的事务,外层事务不影响内部事务的提交/回滚;内部事务出现异常,会影响外部事务的提交/回滚。

5、@Transactional 应用在非 public 修饰的方法上。

在同一个类里,被调用的方法写@Transactional注解没有意义,因为它是通过this来调的而不是代理对象。

三、开发中遇到的问题分享

1、异常没注明属性。

2、事务未提交导致查询数据库查不到。

一、rollbackFor属性
如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚。
@Transactional(rollbackFor = Exception.class)

二、一个带@Transactional注解的方法调用其他方法,被调用的方法需要加@Transactional注解吗?
这要视情况而定。
如果你希望被调用的方法在其自己的事务中运行(即,如果该方法失败,只有该方法的结果会回滚,而不会影响到调用它的方法的事务),那么你应该在被调用的方法上添加@Transactional注解。
然而,如果你希望被调用的方法和调用它的方法共享同一个事务(即,如果被调用的方法失败,整个事务都会回滚),那么你不需要在被调用的方法上添加@Transactional注解1。在这种情况下,即使被调用的方法没有@Transactional注解,它仍然会在调用它的方法的事务上下文中运行。

三、什么时候需要对一个方法添加@Transactional注解
1、一个方法中的所有操作都需要在同一个事务中完成,那么这个方法就应该被注解为@Transactional;
2、涉及到多个数据库操作,这些操作需要在同一个事务中完成以确保数据的一致性;
3、调用其他方法的方法:如果一个方法调用了其他方法,并且这些方法需要在同一个事务中运行,那么调用方法应该被注解为@Transactional。这样,无论被调用的方法是否有@Transactional注解,它们都会在同一个事务上下文中运行;
4、需要自己的事务的方法:如果一个方法需要在其自己的事务中运行,那么这个方法应该被注解为@Transactional。这样,如果这个方法失败,只有这个方法的结果会回滚,而不会影响到调用它的方法的事务。

四、一个方法对数据库只有一次查询和一次插入操作,需要添加@Transactional注解吗?
如果一个方法只包含一次查询和一次插入操作,那么是否需要添加@Transactional注解取决于你的具体需求。
如果查询和插入操作是相互独立的,也就是说,插入操作的成功与否不依赖于查询操作的结果,那么你可能不需要使用@Transactional注解。在这种情况下,即使插入操作失败,查询操作也不会被回滚。
然而,如果查询和插入操作是相关联的,也就是说,插入操作的成功与否依赖于查询操作的结果,那么你应该使用@Transactional注解。这样,如果插入操作失败,那么整个事务(包括查询操作)都会被回滚,以保持数据的一致性。
总的来说,是否需要对一个方法添加@Transactional注解取决于你的业务逻辑和这个方法的需求。你应该根据你的具体情况来决定是否使用@Transactional注解。

在一个被调用的类里,如果入口处的方法没加事务,在它里面被它调用的方法加事务,这个事务是不会生效的!因为没有切面。
一个类调用另一个类的方法,被调用的方法是否需要再单独加事务呢?

五、SpringBoot事务不回滚的可能情况

面试突击86:SpringBoot 事务不回滚?怎么解决?_Java_王磊_InfoQ写作社区

Mysql隔离级别

事务的传播机制

六、参考:

详解Spring的事务管理PlatformTransactionManager-腾讯云开发者社区-腾讯云 (tencent.com)

Spring Boot中的事务是如何实现的 - 知乎 (zhihu.com)

【java】@Transactional事务注解_java transactional注解-CSDN博客

Spring Boot业务代码中使用@Transactional事务失效踩坑点总结 - 知乎

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值