@Transactional又双叒叕失效了?


引言

又到了喜闻乐见的 @Transactional 注解失效环节


正文

md,@Transactional注解又又又又“失效”了。

这次是在单元测试中失效了:单元测试的时候,为啥业务代码中的遇到异常没有回滚?明明业务代码中加了 @Transactional 注解的…

请注意我的描述,是业务代码的事务,不是单元测试的事务。在单元测试中我开启了事务,单元测试方法上也加了 @Rollback 注解,所以单元测试的回滚是没有问题的。

事故现场描述

业务代码简略如下:

@Override
@Transactional(rollbackFor = Exception.class)
public boolean faceDelete(List<Long> uidList) {
   	...
    
    // 删除recognition_user表中的数据
    Assert.isTrue(recognitionUserMapper.deleteBatchIds(uidList) == uidList.size(), "删除人脸信息数据失败");
    // 删除人脸特征数据(midList的获取我省略了)
    Assert.isTrue(faceRecognition.faceDelete(midList), "删除人脸特征向量数据失败");
    return true;
}

单元测试代码简略如下:

@Rollback
@Test
public void faceDeleteTest() {
    List<Long> uidList = new LinkedList<>();
    uidList.add(11L);

    ...

    // mock: 测试删除人脸特征数据(即业务代码中的[faceRecognition.faceDelete(midList)])失败时的情况
    when(faceRecognition.faceDelete(any())).thenReturn(false);
    Assertions.assertThrows(IllegalArgumentException.class, () -> faceService.faceDelete(uidList));
    // 测试事务回滚,此时数据库中应该存在id为11的数据才对
    Assertions.assertNotNull(recognitionUserMapper.selectById(11));
}

但是,很不幸的,上面的单元测试没有通过,Assertions.assertNotNull(recognitionUserMapper.selectById(11))断言失败了,由此可以看出,业务代码中的事务并没有回滚,因为 id 为 11 的那条数据被删掉了。请注意,这里我用了“还”🧐

解释

业务代码中的事务为什么并没有回滚,是因为单元测试方法 faceDeleteTest 里调用了业务方法 faceDelete,而 faceDelete 上的 @Transactional 注解会复用外部单元测试方法中的事务,所以,直到单元测试方法结束之后,业务代码中的事务才会回滚

解决方法

至于解决方法,可以通过在单元测试方法上添加 @Transactional(propagation=Propagation.NOT_SUPPORTED) 来解决这个问题

@Test
@Transactional(propagation=Propagation.NOT_SUPPORTED)
public void faceDeleteTest() { ... }

使用 Propagation.NOT_SUPPORTED 会取消外部事务, 这时业务方法只会使用自己的事务。但是单元测试方法里的数据库操作,例如添加一些测试数据是不会回滚,有可能会污染数据库。推荐使用单元测试时使用 H2 数据库,并且数据不用持久化。


最后

看到单元测试爆红,拳头就硬了起来 👊

@TransactionalSpring框架中用来管理事务的注解,它可以将一组操作作为一个事务来执行,如果其中任何一个操作失败,则整个事务将被回滚,以保证数据的一致性和完整性。 @Transactional的工作原理是在方法执行前创建一个事务,方法执行完毕后,如果没有发生异常,则提交事务,否则回滚事务。这个过程是由Spring框架通过AOP技术实现的。 然而,在一些场景下,@Transactional可能会失效,比如: 1. 事务注解被应用在了非public方法上。只有public方法才能被Spring框架所代理,因此,如果事务注解被应用在了非public方法上,那么它将不会生效。 2. 事务注解被应用在了static方法上。同样地,Spring框架只会代理非static方法,因此,如果事务注解被应用在了static方法上,那么它将不会生效。 3. 异常被catch住了。如果在事务中发生了异常,但是异常被catch住了,那么事务将不会回滚。 4. 方法中调用了同一个类中的其他方法。如果在同一个类中的方法中调用了其他方法,那么事务注解可能会失效。这是因为Spring通过代理来实现事务管理,而代理只能拦截方法调用,无法拦截同类中的方法调用。 5. 多个事务注解嵌套使用。如果在一个方法中使用了多个@Transactional注解,并且它们的传播级别不同,那么可能会导致事务失效。这是因为Spring不支持嵌套事务的回滚。 了解这些场景可以帮助我们更好地使用@Transactional注解,从而保证数据的一致性和完整性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值