调用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时,第一个事务可能已完成提交,因此能查询到第一条数据。
具体解决办法
让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); }调整查询方法的事务上下文
若getStored方法被@Transactional修饰,设置其事务传播特性为REQUIRES_NEW,强制开启新事务读取数据库最新状态:@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) protected synchronized Optional<CommandEvent> getStored(Long id) { return commandEventRepository.findById(id); }检查数据库隔离级别
若数据库使用默认的REPEATABLE READ隔离级别(如MySQL InnoDB),同一事务内的多次查询会读取一致的快照。确保查询操作不在未提交的事务上下文内执行,避免读取旧快照。
内容的提问来源于stack exchange,提问作者daniel sp
相关产品推荐
相关产品推荐

