Jakarta EclipseLink:如何用EntityManager维护双向一对多关联同步
实体类代码
类A
@Entity @Getter @Setter public class A { @Id private String aid; @OneToMany(mappedBy = "mya") private Collection<B> bees; public void addB(B b2add){ bees = bees != null ? bees : new HashSet<B>(); bees.add(b2add); } }
类B
@Entity @Getter @Setter public class B { @Id private String bid; @ManyToOne private A mya; }
问题场景
尝试持久化两个B实例和一个A实例并建立关联,但不同操作方式的结果都不满足双向关联同步的需求:
持久化方法1(add)
public Response add(){ B b1 = new B(); B b2 = new B(); b1.setBid("b1"); b2.setBid("b2"); A a = new A(); a.setAid("a1"); a.addB(b1); a.addB(b2); em.persist(b1); em.persist(b2); em.persist(a); return Response .ok("added") .build(); }
持久化方法2(add2)
public Response add2(){ B b1 = new B(); B b2 = new B(); b1.setBid("b1"); b2.setBid("b2"); A a = new A(); a.setAid("a1"); b1.setMya(a); b2.setMya(a); em.persist(b1); em.persist(b2); em.persist(a); return Response .ok("added inversed") .build(); }
查询方法
public Response read(){ var aent = em.find(A.class, "a1"); System.out.println(aent); return Response .ok(aent) .build(); } public Response read2(){ var bent = em.find(B.class, "b1"); System.out.println(bent); return Response .ok(bent) .build(); }
问题现象
调用add方法后,A实例a1的bees集合包含b1和b2,但两个B实例都没有指向a1的引用;调用add2方法后,b1和b2都关联了a1,但a1的bees集合为空。两种情况下数据库存储的数据完全一致(外键均不为空),但EntityManager加载的内存对象双向关联不同步。
需要解决的问题:无需手动先后调用add和add2,如何让EntityManager遍历的双向关联保持同步?
解决方案
核心是在维护关联的方法中同时更新双向关联的两端——JPA要求开发者手动保证内存中双向关联的一致性,数据库层面由拥有外键的B端(@ManyToOne标注的一方)控制。
1. 修改A类的addB方法
更新addB方法,在将B加入集合的同时,同步设置B的mya属性指向当前A实例:
public void addB(B b2add){ bees = bees != null ? bees : new HashSet<B>(); bees.add(b2add); // 同步维护B端的关联 b2add.setMya(this); }
2. 可选:添加移除关联的方法(避免内存泄漏)
如果需要移除关联,同样要同步更新两端:
public void removeB(B bToRemove){ if(bees != null){ bees.remove(bToRemove); bToRemove.setMya(null); } }
3. 简化持久化逻辑
现在只需调用A的addB方法,就能同时维护双向关联:
public Response add(){ B b1 = new B(); B b2 = new B(); b1.setBid("b1"); b2.setBid("b2"); A a = new A(); a.setAid("a1"); a.addB(b1); a.addB(b2); em.persist(a); // 若未配置级联,仍需手动持久化B实例 em.persist(b1); em.persist(b2); return Response.ok("added").build(); }
4. 可选:配置级联持久化
如果希望持久化A时自动持久化关联的B,可以在A的@OneToMany注解中添加cascade = CascadeType.PERSIST:
@OneToMany(mappedBy = "mya", cascade = CascadeType.PERSIST) private Collection<B> bees;
这样就不需要手动调用persist(b1)和persist(b2),仅需persist(a)即可完成所有实例的持久化。
原理说明
JPA中双向关联的拥有方是@ManyToOne标注的B端(外键存储在B的表中),数据库的关联关系由B端控制。但内存中的对象关联需要开发者手动维护两端,否则EntityManager加载对象时,未被设置的一端会显示为空(即使数据库中存在外键)。通过在关联维护方法中同步更新两端,就能保证内存中对象的双向关联始终一致,无需手动执行两种持久化操作。
内容的提问来源于stack exchange,提问作者zajer

