Spring Boot JPA单测试用例中变更无法持久化问题排查
这种情况我之前也碰到过好几次,结合你用的Spring Boot 2.0.1和Spring Data JPA,大概率是下面几个原因之一,咱们一个个拆解来看:
1. JPA一级缓存导致查询未读到最新数据
JPA的EntityManager会维护一个一级缓存,同一个事务内的查询会优先从缓存取数据。你的deleteOneByName执行后,缓存里的旧数据还没失效,后续的selectAll就直接读了缓存,导致看起来删除没生效。
解决办法:
如果你的删除方法是用JPQL自定义的,一定要加上@Modifying(clearAutomatically = true),让执行完删除后自动清空缓存:
@Modifying(clearAutomatically = true) @Query("DELETE FROM Customer c WHERE c.name = :name") void deleteOneByName(@Param("name") String name);
要是你自己实现了Repository的逻辑,就在删除后手动调用entityManager.flush()和entityManager.clear(),强制刷新并清空缓存。
2. 测试类的事务自动回滚坑
虽然你说事务已提交,但还是要确认下测试类的注解:如果你的测试类加了@Transactional,Spring Boot默认会在测试方法执行完自动回滚事务——哪怕你手动提交了,也可能被框架的回滚逻辑覆盖。
解决办法:
- 要是不需要测试后回滚,直接在测试方法上加
@Rollback(false); - 或者检查测试类有没有不必要的
@Transactional,如果业务逻辑本身已经处理了事务,测试类可以去掉这个注解。
3. 删除方法的逻辑根本没生效
有可能你的deleteOneByName方法本身就没正确执行删除:比如参数传错了?JPQL语句写错了?或者数据库里根本没有叫"SOME NAME"的记录?
排查小技巧:
- 在删除方法里加个日志,打印要删除的名称,确认和数据库里的记录完全匹配(注意大小写、空格这些细节);
- 开启JPA的SQL日志,看看实际执行的SQL是什么:
在application.properties里加这两行:
控制台会输出格式化后的SQL,你可以直接看删除语句的影响行数,确认有没有真的删掉数据。spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true
4. 数据库隔离级别导致重复读
如果你的数据库用的是默认的REPEATABLE READ隔离级别(比如MySQL InnoDB),同一个事务内的查询会重复读取事务开始时的数据快照,哪怕中间执行了删除,后续查询还是会看到旧数据。
解决办法:
把查询操作放到新事务里执行,比如给selectAll方法加@Transactional(propagation = Propagation.REQUIRES_NEW),强制开启新事务读取最新的数据库状态。
内容的提问来源于stack exchange,提问作者lapkritinis

