Hibernate何时更新关联实体?Spring Data JPA关联场景疑问
兄弟,我太懂你这种踩坑的感觉了!明明在同一个事务里存了关联的Score,读Player的时候却看不到,简直让人挠头。咱们来拆解下问题原因,再给你几个靠谱的解决办法:
问题根源
你用的是FetchType.LAZY的@OneToMany关联,虽然加了@Transactional让操作在同一个持久化上下文里,但这里有个关键细节:JPA不会自动把新保存的Score同步到Player内存对象的scores集合里。
简单说,你先存了Player,这时候它的scores是个懒加载的代理集合(空的);之后你存了关联这个Player的Score,数据库里的关联是对的,但持久化上下文里的Player实例还是原来的状态,根本不知道有新的Score加进来了,所以直接读它的scores自然还是空的。
靠谱的解决办法
1. 手动维护双向关联(最推荐)
双向关联的核心就是要同时维护双方的关系,不能只给Score设置Player,还要把Score加到Player的集合里。代码大概是这样:
// 先保存Player Player savedPlayer = playerRepository.save(player); // 创建Score并关联Player Score score = new Score(); score.setPlayer(savedPlayer); // 关键一步:把Score加到Player的scores集合里 savedPlayer.getScores().add(score); // 保存Score scoreRepository.save(score); // 这时候再读savedPlayer的scores就能看到刚加的Score了 System.out.println(savedPlayer.getScores().size()); // 输出1
这样做之后,持久化上下文里的Player对象的集合就被更新了,后续读取自然能拿到数据,同时也保证了双向关联的一致性。
2. 刷新持久化上下文
如果不想手动维护集合,可以在保存Score之后,用EntityManager刷新Player的状态,强制从数据库重新加载:
@Autowired private EntityManager entityManager; // 保存Player和Score的步骤... scoreRepository.save(score); // 刷新Player,从数据库重新获取最新状态 entityManager.refresh(savedPlayer); // 这时候访问savedPlayer.getScores()会触发懒加载,从数据库查到新的Score
不过要注意,刷新之后原来的代理集合会被替换成实际的集合,后续访问会执行查询语句。
3. 重新查询Player
另一种方式是通过Repository重新查询一次Player,这样会从持久化上下文或者数据库获取最新的实例:
// 保存Score之后 Player refreshedPlayer = playerRepository.findById(savedPlayer.getId()).orElseThrow(); // 访问refreshedPlayer.getScores()就能拿到关联的Score了
这种方式比较直观,适合不想动EntityManager或者手动维护集合的场景。
4. 谨慎使用FetchType.EAGER(不推荐)
你也可以把@OneToMany的fetch模式改成EAGER,这样加载Player的时候会直接把所有Score都查出来。但我非常不推荐这种做法——除非你的Player关联的Score数量极少,否则每次加载Player都查一堆Score,会严重影响性能,属于饮鸩止渴的方案。
最后提醒
双向关联一定要记住双方都要维护,不然不仅会出现你遇到的内存对象不一致问题,极端情况下还可能导致数据库关联数据异常。养成维护双向关联的习惯,能避免很多类似的坑!
内容的提问来源于stack exchange,提问作者Dima Stoyanov

