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

调用JpaRepository save()后跨线程findById()无法找到数据的问题

使用JpaRepository存储对象后,查询不到刚保存的数据的问题

我用JpaRepository存储对象,相关代码如下:

protected synchronized void saveAndPublish(CommandEvent inCommandEvent) {
  commandEventRepository.save(inCommandEvent);
  CommandEventDTO commandEventDTO = commandEventMapper.toDTO(inCommandEvent);
  publishCommandEvent(commandEventDTO);//publishes to a queue
}

protected synchronized Optional<CommandEvent> getStored(Long id) {
  return commandEventRepository.findById(id);
}

存储前对象ID为null,调用save()后ID已被赋值,且通过映射器正确复制到DTO中,这一点我已通过打印DTO验证。publish方法将DTO放入队列,由另一个线程读取并返回确认消息,整个过程通常耗时约50ms。但收到确认消息后调用getStored()查询初始CommandEvent时,返回的Optional为空。

我尝试了以下方案但均无效:

  • 使用saveAndFlush()替代save()
  • 因两个方法均为synchronized,尝试在publish后添加sleep
  • 显式设置数据库autocommit = true

调用getStored()时打印仓库中所有对象,目标数据不在其中。但有趣的是,后续发送第二个CommandEvent并检查仓库时,第一个CommandEvent却存在。请问我哪里操作有误?


问题原因与解决办法

核心原因

这是事务传播与提交时机导致的问题:

  • 若saveAndPublish方法被Spring的@Transactional注解修饰,save操作仅将实体放入当前事务的持久化上下文,事务未提交时,数据库中并未真正写入数据;其他线程(甚至当前线程的跨事务查询)都无法读取到这条未提交的数据。
  • 当你发送第二个CommandEvent时,第一个事务可能已完成提交,因此能查询到第一条数据。

具体解决办法

  1. 让save操作的事务提前独立提交
    将save逻辑拆分为单独的事务方法,强制开启新事务并提交,确保数据立即写入数据库:

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public CommandEvent saveCommandEvent(CommandEvent event) {
        return commandEventRepository.saveAndFlush(event);
    }
    
    protected synchronized void saveAndPublish(CommandEvent inCommandEvent) {
      CommandEvent savedEvent = saveCommandEvent(inCommandEvent);
      CommandEventDTO commandEventDTO = commandEventMapper.toDTO(savedEvent);
      publishCommandEvent(commandEventDTO);
    }
    

    或者使用TransactionTemplate手动控制事务提交:

    @Autowired
    private TransactionTemplate transactionTemplate;
    
    protected synchronized void saveAndPublish(CommandEvent inCommandEvent) {
      CommandEvent savedEvent = transactionTemplate.execute(status -> {
          return commandEventRepository.saveAndFlush(inCommandEvent);
      });
      CommandEventDTO commandEventDTO = commandEventMapper.toDTO(savedEvent);
      publishCommandEvent(commandEventDTO);
    }
    
  2. 调整查询方法的事务上下文
    若getStored方法被@Transactional修饰,设置其事务传播特性为REQUIRES_NEW,强制开启新事务读取数据库最新状态:

    @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
    protected synchronized Optional<CommandEvent> getStored(Long id) {
      return commandEventRepository.findById(id);
    }
    
  3. 检查数据库隔离级别
    若数据库使用默认的REPEATABLE READ隔离级别(如MySQL InnoDB),同一事务内的多次查询会读取一致的快照。确保查询操作不在未提交的事务上下文内执行,避免读取旧快照。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 20:03:26