Spring Boot测试中findById与findBy<Field>的行为差异排查
你的问题核心在于Hibernate的一级缓存(Session缓存)机制以及事务内的flush时机,结合你使用的LAZY加载策略,具体原因如下:
一级缓存的ID匹配特性
一级缓存是Hibernate Session级别的缓存,仅通过实体ID管理缓存实例。当你在checkToken方法中调用findById获取UserAccount实例后,该实例会存入当前Session的缓存中。后续测试代码调用findById(user.getId())时,会直接从缓存取出已修改enabled字段的实例,因此返回更新后状态。非ID查询的数据库访问逻辑
findByUsername通过用户名而非ID查询,Hibernate无法通过一级缓存直接匹配对应实例,会执行SQL查询数据库。而你的测试方法被@Transactional标记,事务在测试结束前不会提交——此时checkToken中user.setEnabled(true)的修改,Hibernate还未执行flush操作(默认flush时机为事务提交前,部分查询会触发flush,但非ID查询不一定触发),数据库中enabled字段仍为旧值,因此查询返回未更新状态。LAZY加载的间接影响
LAZY抓取策略本身不会直接导致该现象,但需注意:如果findByUsername的JPQL查询未显式加载基本字段(基本字段默认EAGER)才可能有影响,但你这里仅操作enabled字段,所以核心问题仍在缓存与flush时机。
验证方法
在测试代码的perform调用后,手动触发Session的flush,再执行查询:
// 注入EntityManager @Autowired private EntityManager entityManager; @Test @Transactional public void isVerifyingToken() throws Exception { UserAccount user= new UserAccount("testName2", "PlainPassword", "test2@mail.com"); mvc.perform(MockMvcRequestBuilders.get(.......)); // 手动flush,将缓存变更同步到数据库 entityManager.flush(); assertTrue(service.findByUsername(user.getUsername()).get(0).isEnabled()); assertTrue(service.findById(user.getId()).isEnabled()); }
此时findByUsername会查询到更新后的值。
优化建议
- 对于通过
findById等方式获取的持久态实体,修改字段后无需手动调用save,Hibernate会自动跟踪变更并在flush时同步到数据库,可删除checkToken中的this.save(user)代码。 - 若希望
findByUsername利用一级缓存,可开启Hibernate查询缓存(需额外配置),或在查询前触发flush。
内容的提问来源于stack exchange,提问作者Discipulos

