Spring/JPA中关联实体更新的Hibernate行为疑问
Hibernate关联实体复杂更新逻辑疑问解答
背景
我希望了解Hibernate如何处理关联实体的复杂更新操作,验证我的理解是否正确。已知部分关联设计不够理想,但重点是理解Hibernate的处理逻辑。
实体类定义
class User { @OneToMany(mappedBy = "user") private List<Document> documents; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER) @Fetch(FetchMode.SELECT) private List<ContactInfo> contactInfos; }
DAO层实现
class DocumentService { private final UserRepository userRepository; private final DocumentRepository documentRepository; public User updateUser(User user) { if (!userRepository.existsById(user.getId())) { throw new NoSuchElementException(); } // 我的理解:此操作应将User的变更传播至ContactInfo return userRepository.save(user); } public User findUserById(String userId) { return userRepository.findById(userId).orElseThrow(() -> new NoSuchElementException()); } }
场景一:从Document更新User的两种实现差异
疑问
useCase1和useCase2是否存在差异?实验显示useCase1可能抛出PessimisticLockingFailureException,useCase2可能抛出OptimisticLockingFailureException,该观察是否正确?
解答
该观察完全正确,核心差异源于两种实现采用的锁机制不同:
- useCase1大概率是通过
findById时指定了悲观锁(比如添加@Lock(LockModeType.PESSIMISTIC_WRITE)注解),Hibernate会立即对数据库中目标User行添加排他锁,当其他事务尝试获取同一User的锁时,就会触发PessimisticLockingFailureException。 - useCase2应该是基于乐观锁实现(比如User实体类中添加了
@Version版本字段),Hibernate不会提前锁定数据库行,而是在事务提交时对比实体版本号,若发现版本不匹配(说明其他事务已修改),则抛出OptimisticLockingFailureException。
两种锁策略的触发时机和逻辑完全不同,对应抛出的异常类型自然有明确区别。
场景二:从Document更新ContactInfo的两种实现差异
疑问
useCase3和useCase4是否存在差异?实验显示useCase4会获取过时的contactInfos列表,useCase3能获取更新后的列表,这是否与Hibernate缓存相关?
解答
确实和Hibernate的**一级缓存(Session缓存)**直接相关,具体逻辑如下:
- useCase3是在同一个Session内完成更新ContactInfo和获取列表的操作:Hibernate的一级缓存会维护当前Session中所有实体的最新状态,更新操作后,缓存内的ContactInfo数据会同步更新,因此获取列表时直接从缓存读取,能拿到最新数据。
- useCase4大概率是更新ContactInfo后切换了Session,或者使用了脱离Session管理的脱管状态User实体:新Session的一级缓存中没有更新后的ContactInfo数据,若未主动触发缓存刷新或重新查询,就会读取缓存内的旧数据(若开启二级缓存,还可能读取二级缓存中的过时数据)。
另外,ContactInfo设置的FetchType.EAGER仅在首次加载User时触发数据库查询,后续Session未刷新的情况下,缓存内的旧数据不会自动更新,这也是导致useCase4拿到过时数据的辅助原因。
内容的提问来源于stack exchange,提问作者Rodrigo Novaes
相关产品推荐
相关产品推荐

