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

集成测试中JPA用旧值查询返回已修改实体的异常问题

问题原因及解决办法

核心原因

你的问题源于事务共用与JPA一级缓存的交互:

  • 测试方法标注了@Transactional,导致整个测试流程(包括mockMvc触发的过滤器代码)运行在同一个数据库事务中。
  • JPA的一级缓存(EntityManager缓存)会在事务生命周期内保留已加载的实体引用。你修改user的uniqueName并调用saveAndFlush后,缓存中的实体已更新为newUniqueName,且变更被刷到数据库,但事务未提交。
  • 过滤器中执行findUserByUniqueName(oldUniqueName)时,JPA会先执行SQL查询,但由于当前事务内数据库记录已被更新为newUniqueName,SQL本应返回空。但EntityManager缓存中存在该ID的实体,JPA会直接返回缓存中已修改的实体,导致你看到“用旧uniqueName查到新值实体”的矛盾结果。

解决办法

1. 让过滤器使用独立事务

修改过滤器的@Transactional注解,设置传播行为为REQUIRES_NEW,强制开启新事务,使其看不到测试事务中未提交的变更:

@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
protected void doFilterInternal(
      HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {
    // 原有逻辑不变
}

注意:这种方式下,测试事务需要先提交变更,过滤器才能看到数据库中的新值。如果测试需要保留事务回滚,需结合手动事务管理。

2. 测试中手动管理事务,提交变更后再调用接口

去掉测试方法的@Transactional,手动控制事务提交,确保变更生效后再执行接口调用:

@Autowired
private PlatformTransactionManager transactionManager;

@Test
void test() {
    // 开启事务执行修改操作
    TransactionStatus updateStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
    try {
        User user = userRepository.findUserByUniqueName("oldUniqueName").orElse(null);
        assertThat(user).isNotNull();

        user.setUniqueName("newUniqueName");
        userRepository.saveAndFlush(user);
        
        // 提交事务,让变更写入数据库
        transactionManager.commit(updateStatus);
    } catch (Exception e) {
        transactionManager.rollback(updateStatus);
        throw e;
    }

    // 开启新事务执行测试验证(避免污染测试数据)
    TransactionStatus testStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
    try {
        // 构造请求头
        HttpHeaders headers = new HttpHeaders();
        headers.add("UniqueName", "oldUniqueName");

        mockMvc
            .perform(get(endpointUrl).headers(headers))
            .andExpect(status().isUnauthorized());
    } finally {
        transactionManager.rollback(testStatus);
    }
}

3. 清除EntityManager缓存(保留测试事务)

如果要保留测试方法的@Transactional,可在saveAndFlush后清除一级缓存,强制后续查询从数据库获取最新数据:

@Autowired
private EntityManager entityManager;

@Test
@Transactional
void test() {
    User user = userRepository.findUserByUniqueName("oldUniqueName").orElse(null);
    assertThat(user).isNotNull();

    user.setUniqueName("newUniqueName");
    userRepository.saveAndFlush(user);
    
    // 清除EntityManager缓存,强制后续查询走数据库
    entityManager.clear();

    // 构造请求头
    HttpHeaders headers = new HttpHeaders();
    headers.add("UniqueName", "oldUniqueName");

    mockMvc
        .perform(get(endpointUrl).headers(headers))
        .andExpect(status().isUnauthorized());
}

内容的提问来源于stack exchange,提问作者MehdiB

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 22:05:12