JPA多线程场景下调用Table_B_Repository.saveAll为何覆盖Table_A修改
问题解答
核心原因
你遇到的问题本质是线程2加载的旧版Table_A托管实例被误触发了更新,和Table_B的saveAll操作本身没有直接关联,而是saveAll触发了Hibernate的自动flush机制导致了连带更新,两个线程的执行时序刚好符合以下流程:
- 线程1读取Table_A实例,修改字段后还未提交事务
- 线程2读取同一个Table_A实例,此时拿到的是线程1修改前的旧值,且该实例在当前线程的持久化上下文中处于*托管(Managed)*状态
- 线程2调用
Table_B_Repository.saveAll()时,Hibernate默认会先执行flush操作,同步持久化上下文中所有变更的实体到数据库 - 某种原因导致Hibernate判定线程2持有的旧版Table_A实例发生了变更,将旧值写入数据库,刚好在线程1提交后执行,覆盖了线程1的修改
常见的Table_A被判定为变更的触发场景
- 你为
Table_A的lastModifiedDate等字段加了@LastModifiedDate等JPA审计注解,且开启了审计功能:只要实体处于托管状态,flush时审计功能会自动更新审计字段,触发全量更新 - 实体的属性访问策略为PROPERTY(基于getter/setter访问),某个getter方法存在额外逻辑,返回值和数据库存储的字段值不一致,Hibernate脏检查时误判为实体已修改
- 代码中存在隐式修改Table_A字段的逻辑,未被你察觉到
- 懒加载属性初始化时的特殊逻辑,误将实体标记为脏数据
- 开启了Open Session in View(OSIV,Spring Boot默认开启),整个请求周期内持久化上下文保持打开,加载的Table_A实例长期处于托管状态,大幅提高误更新概率
解决方案
- 方案1:查询Table_A时标记为只读,或查询后主动脱离持久化上下文
读操作不需要修改Table_A时,在Repository的查询方法上加只读提示:
或者查询完成后调用@QueryHints(value = @QueryHint(name = org.hibernate.jpa.QueryHints.HINT_READONLY, value = "true")) Optional<Table_A> findById(Integer id);entityManager.detach(tableAInstance),将实例从持久化上下文移除,就算被判定为变更也不会同步到数据库 - 方案2:为Table_A添加乐观锁
给实体加@Version字段做并发控制:
并发修改时会抛出@Version private Integer version;OptimisticLockingFailureException,避免静默覆盖,你可以捕获异常后重试操作 - 方案3:调整Flush模式
不需要实时同步变更的场景,将当前事务的FlushMode调整为COMMIT,只有事务提交时才执行flush,避免saveAll等操作触发提前flush - 方案4:排查审计逻辑和配置
确认审计注解的使用是否符合预期,检查所有getter方法是否存在修改返回值的额外逻辑,避免脏检查误判;如果不需要OSIV可以在配置中关闭spring.jpa.open-in-view=false
内容的提问来源于stack exchange,提问作者Xavi
相关产品推荐
相关产品推荐

