Spring JPA/Hibernate实体关联更新问题:列表无法实时刷新
问题场景
A实体与B实体为双向关联:
- A通过
@ManyToOne关联B - B通过
@OneToMany(mappedBy = "A")关联A集合
在createA方法中,保存A实体后,立即打印B的A集合大小未实时更新,仅在下一次调用方法时才显示正确数值。目前通过entityManager.refresh(b)可以解决,但希望了解其他实现方式,比如重新查询B实体。
实体代码
A实体
@Entity public class A { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne private B b; }
B实体
@Entity public class B { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToMany(mappedBy = "A", cascade = CascadeType.REMOVE) private List<A> A; }
原服务方法
@Transactional @Override public ADTO createA (String username) { B b = findEntityByUsername(username); log.info("ACTUAL NUMBER OF A = {}", b.getA().size()); A a = AMapper.buildA(b); ARepository.save(a); log.info("ACTUAL NUMBER OF A = {}", b.getA().size()); return AMapper.buildADTO(a); }
解决方案
1. 手动维护双向关联关系(推荐)
双向关联需要同时维护双方对象的引用,这是JPA双向关联的最佳实践,不需要依赖数据库刷新就能实时更新内存中的集合。
修改映射逻辑或手动将A添加到B的集合中:
@Transactional @Override public ADTO createA (String username) { B b = findEntityByUsername(username); log.info("ACTUAL NUMBER OF A = {}", b.getA().size()); A a = AMapper.buildA(b); // 手动维护双向关联:将新创建的A添加到B的集合中 b.getA().add(a); ARepository.save(a); log.info("ACTUAL NUMBER OF A = {}", b.getA().size()); // 此时数值已更新 return AMapper.buildADTO(a); }
注意:如果B的
A集合为null,需先在B的构造方法中初始化(如this.A = new ArrayList<>()),避免空指针异常。
2. 重新查询B实体(绕过一级缓存)
直接再次调用findEntityByUsername(username)会拿到EntityManager一级缓存中的旧B实例,无法获取最新数据,需通过以下方式绕过缓存:
方式1:清除缓存后重新查询
@Transactional @Override public ADTO createA (String username) { B b = findEntityByUsername(username); log.info("ACTUAL NUMBER OF A = {}", b.getA().size()); A a = AMapper.buildA(b); ARepository.save(a); // 清除一级缓存,再重新查询 entityManager.clear(); B refreshedB = findEntityByUsername(username); log.info("ACTUAL NUMBER OF A = {}", refreshedB.getA().size()); return AMapper.buildADTO(a); }
方式2:添加查询提示强制绕过缓存
在Spring Data JPA的BRepository方法上添加查询提示,强制从数据库获取最新数据:
// 在BRepository中定义方法 @QueryHints({@QueryHint(name = org.hibernate.jpa.QueryHints.HINT_CACHEABLE, value = "false")}) Optional<B> findByUsername(String username);
之后在服务方法中直接重新查询即可:
@Transactional @Override public ADTO createA (String username) { B b = findEntityByUsername(username); log.info("ACTUAL NUMBER OF A = {}", b.getA().size()); A a = AMapper.buildA(b); ARepository.save(a); B refreshedB = findEntityByUsername(username); log.info("ACTUAL NUMBER OF A = {}", refreshedB.getA().size()); return AMapper.buildADTO(a); }
3. 优化现有刷新方案
你当前使用entityManager.refresh(a)是刷新A实体,正确做法应直接刷新B实体,逻辑更清晰:
@Transactional @Override public ADTO createA (String username) { B b = findEntityByUsername(username); log.info("ACTUAL NUMBER OF A = {}", b.getA().size()); A a = AMapper.buildA(b); ARepository.save(a); entityManager.refresh(b); // 刷新B实体,获取最新集合 log.info("ACTUAL NUMBER OF A = {}", b.getA().size()); return AMapper.buildADTO(a); }
内容的提问来源于stack exchange,提问作者huga721
相关产品推荐
相关产品推荐

