引言
又到了喜闻乐见的 @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 数据库,并且数据不用持久化。
最后
看到单元测试爆红,拳头就硬了起来 👊