JPA @Query更新方法生产正常但@DataJpaTest测试失败如何解决
问题原因
这是Hibernate持久化上下文(一级缓存)的一致性问题,和@Modifying注解的默认配置、@DataJpaTest的运行机制有关:
- 执行
save(user)后,对应的User实例会被存入当前线程的持久化上下文缓存中。 - 你调用的
enableUser是JPQL更新语句,这类语句会直接操作数据库,但默认不会更新持久化上下文里已缓存的实体属性,也不会清空缓存。 - 后续执行
getByEmail查询时,Hibernate会优先匹配持久化上下文里的缓存实例,直接返回属性未更新的旧对象,哪怕此时数据库里的enabled字段已经被修改为TRUE(你看到的SELECT语句实际没有返回最新值,就是因为缓存命中跳过了数据库结果映射)。
生产环境运行正常的原因是:生产环境中调用enableUser的业务方法结束后事务会直接提交,对应的持久化上下文会被销毁,下一次查询是在新的上下文里执行,自然会读取数据库的最新值。
解决方案
提供3种可选方案,你可以根据实际场景选择:
方案1:修改@Modifying注解配置(推荐,适配所有场景)
给Repository的enableUser方法的@Modifying注解加clearAutomatically = true属性,更新执行后会自动清空持久化上下文,后续查询会强制拉取数据库最新数据:
@Transactional @Modifying(clearAutomatically = true) @Query("UPDATE User a " + "SET a.enabled = TRUE " + "WHERE a.email = ?1") void enableUser(String email);
方案2:测试中手动清理缓存(不修改生产代码)
在测试类中注入EntityManager,调用完enableUser后手动清空持久化上下文:
@DataJpaTest class UserRepositoryTest { @Autowired private UserRepository componentUnderTest; @Autowired private EntityManager entityManager; @Test @DisplayName("Ensure the query for enabling a user works.") void enableUser() { User user = new User("full name", "test@mail.com", "passwd", UserRole.USER); componentUnderTest.save(user); componentUnderTest.enableUser("test@mail.com"); // 新增两行代码清空缓存 entityManager.flush(); entityManager.clear(); User enabledUser = componentUnderTest.getByEmail("test@mail.com"); assertThat(enabledUser.isEnabled()).isTrue(); } }
方案3:刷新查询到的实体实例
拿到查询结果后调用refresh方法强制同步数据库最新属性:
componentUnderTest.enableUser("test@mail.com"); User enabledUser = componentUnderTest.getByEmail("test@mail.com"); entityManager.refresh(enabledUser); // 强制刷新实体属性 assertThat(enabledUser.isEnabled()).isTrue();
内容的提问来源于stack exchange,提问作者S. Kostadinov
相关产品推荐
相关产品推荐

