You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot测试中findById与findBy<Field>的行为差异排查

问题原因分析

你的问题核心在于Hibernate的一级缓存(Session缓存)机制以及事务内的flush时机,结合你使用的LAZY加载策略,具体原因如下:

  1. 一级缓存的ID匹配特性
    一级缓存是Hibernate Session级别的缓存,仅通过实体ID管理缓存实例。当你在checkToken方法中调用findById获取UserAccount实例后,该实例会存入当前Session的缓存中。后续测试代码调用findById(user.getId())时,会直接从缓存取出已修改enabled字段的实例,因此返回更新后状态。

  2. 非ID查询的数据库访问逻辑
    findByUsername通过用户名而非ID查询,Hibernate无法通过一级缓存直接匹配对应实例,会执行SQL查询数据库。而你的测试方法被@Transactional标记,事务在测试结束前不会提交——此时checkToken中user.setEnabled(true)的修改,Hibernate还未执行flush操作(默认flush时机为事务提交前,部分查询会触发flush,但非ID查询不一定触发),数据库中enabled字段仍为旧值,因此查询返回未更新状态。

  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 16:15:40