Hibernate是否会在新事务中自动重新关联已加载实体?
我们通过TokenService借助JpaRepository加载Token实体,这个读取操作会自动生成事务块。之后调用标注了Spring @Transactional的PasswordService#resetPassword方法,把之前获取的Token作为参数传入,并修改它的validationTime属性。
控制器代码:
@Controller public class UserAccountController { @PostMapping("/reset") public String processPasswordReset(PasswordResetDto resetDto) { String tokenSequence = resetDto.getTokenSequence(); Token token = tokenService.findBySequence(tokenSequence); passwordService.resetPassword(token); return "redirect:/"; } }
服务类代码:
@Service public class PasswordService { @Transactional public void resetPassword(Token token) { token.setValidationTime(new Timestamp(System.currentTimeMillis())); } }
无需显式保存Token,事务块结束时Token就会被更新,由此能推测Hibernate执行了脏检查并自动更新Token。
我的疑问:未在事务内重新获取Token的情况下,Hibernate如何对其进行管理?重新获取会得到一级缓存版本,但此场景下Hibernate显然自动识别到了实体更新需求。Hibernate是否会在缓存实体传入事务块时自动重新关联?还是有其他机制在起作用?
这里的关键在于Hibernate持久化上下文的状态管理,以及Spring事务和Hibernate的协同逻辑:
首次加载Token的状态变化
调用tokenService.findBySequence时,因为JpaRepository的方法默认被@Transactional修饰,这个读取操作会在一个临时事务中执行。此时Token实体被加载到当前的持久化上下文(也就是一级缓存),处于持久化状态——Hibernate会跟踪它的所有属性变化。当这个临时事务结束后,持久化上下文关闭,Token实体变为游离状态:不再被Hibernate跟踪,但实体本身携带了数据库主键等唯一标识信息。新事务中的自动关联逻辑
当进入passwordService.resetPassword方法时,@Transactional注解会触发Spring开启新事务,同时创建对应的Hibernate持久化上下文。此时传入的Token是游离状态的实体,Hibernate会自动完成以下操作:- 检查实体的主键标识:只要实体有有效的主键值,且当前持久化上下文中没有该主键对应的实例,Hibernate会自动把这个游离实体**重新附着(reattach)**到当前持久化上下文,让它回到持久化状态,重新被Hibernate跟踪。
- 这个附着过程不需要你显式调用
save或重新查询,Spring与Hibernate的整合会自动处理。
脏检查与自动更新
当事务即将提交时,Hibernate会对持久化上下文中的所有实体执行脏检查:对比实体当前的属性值和加载(或最后一次刷新)时的快照值。一旦发现validationTime被修改,Hibernate会自动生成UPDATE语句,把变更同步到数据库。
总结来说:游离状态的实体只要携带有效主键,进入新事务后会被自动附着到当前持久化上下文,之后的属性变更会被脏检查捕获,最终在事务提交时自动更新到数据库。
内容的提问来源于stack exchange,提问作者Slevin

