Hibernate中更新前实体对象自动同步为更新后值该如何解决?
问题根因
这个问题是Hibernate**一级缓存(持久化上下文)**的固有特性导致的:
- 你第一次调用
taskRepository.findById(taskId)查询得到的taskBeforeUpdate是Hibernate返回的托管状态实体,会被存入当前线程绑定的EntityManager的一级缓存中,缓存的key是实体类型+主键ID。 - 后续执行
assignUser方法时,内部再次调用entityManager.find(Task.class, taskId)查询同ID的Task实体时,Hibernate不会发起真实的数据库查询,而是直接从一级缓存中返回和taskBeforeUpdate指向同一内存地址的对象引用。 - 你在
assignUser中对该对象执行setUser(user)修改属性时,本质就是修改taskBeforeUpdate指向的对象,所以会出现提前查询的旧对象值自动变更的问题。
可行解决方案
你可以从以下方案中选一个适配你的业务场景:
- 方案1:查询到旧实体后直接做深拷贝保存,发Kafka时使用拷贝后的旧对象
可以用MapStruct、Apache BeanUtils、或者手动重写拷贝方法实现,注意如果Task类包含关联实体属性,要做深拷贝避免浅拷贝带来的关联对象同步修改问题。
示例代码:final Task taskBeforeUpdate = taskRepository.findById(taskId); // 深拷贝得到独立的旧对象快照 final Task taskSnapshot = deepCopy(taskBeforeUpdate); // 后续执行更新逻辑 final Task assignedTask = taskRepository.assignUser(taskBeforeUpdate.getId(), user.getId()); // 发Kafka用拷贝的快照对象 kafkaSenderTaskProducer.sendUpdatedEvent(assignedTask,taskSnapshot); - 方案2:查询到旧实体后将其从持久化上下文中脱管
调用entityManager.detach(taskBeforeUpdate)方法将旧实体从一级缓存中移除,后续更新操作查询同ID实体时会返回新的对象,不会修改已脱管的旧实体。 - 方案3:更新操作使用HQL/JPQL直接执行数据库更新,不操作实体对象
直接写更新语句修改数据库的关联关系,不会将实体加载到一级缓存,自然不会修改之前查询到的旧实体:@Modifying @Query("update Task t set t.user = :user where t.id = :taskId") int assignUser(@Param("taskId") Long taskId, @Param("user") User user); - 方案4:查询旧实体时直接封装为DTO而非实体类
用投影查询只取出你需要发送Kafka的字段,封装为普通JavaDTO对象,完全脱离Hibernate的缓存管理,后续实体更新不会影响DTO的值。
内容的提问来源于stack exchange,提问作者Олександр Житарюк
相关产品推荐
相关产品推荐

