移除关联实体后Merge多对多关系实体失败的JPA/Hibernate问题
多对多关系中移除关联后merge操作不生效的问题及解决方法
问题描述
A和B为多对多关联的实体类,执行以下操作后数据库关联表b_a未删除对应记录:
- 通过
getAByIdFromRepository获取ID为2的A实例,随后关闭EntityManager; - 从A的B列表中筛选出ID为1的B实例并移除;
- 调用
repositoryMerge执行merge,操作无效果。
相关代码
Main类
public class Main { public static void main(String[] args) { A a = getAByIdFromRepository(2); B b = a.getB().stream().filter(i -> i.getId() == 1).findFirst().orElse(null); a.remove(b); repositoryMerge(a); } public static A getAByIdFromRepository(int id) { EntityManagerFactory factory = Persistence.createEntityManagerFactory("persistence_unit"); EntityManager em = factory.createEntityManager(); A a = em.find(A.class, id); em.close(); factory.close(); return a; } public static void repositoryMerge(A a) { EntityManagerFactory factory = Persistence.createEntityManagerFactory("persistence_unit"); EntityManager entityManager = factory.createEntityManager(); entityManager.getTransaction().begin(); entityManager.merge(a); entityManager.getTransaction().commit(); entityManager.close(); factory.close(); } }
A实体类
@Entity @Table(name = "a") public class A implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "a_id") protected int id; @Column(name = "a_name") protected String name; @ManyToMany(mappedBy = "a", fetch = FetchType.EAGER) protected List<B> b; // getters setters 省略 public void remove(B b) { this.getB().remove(b); b.getA().remove(this); } }
B实体类
@Entity @Table(name = "b") public class B implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "b_id") protected int id; @Column(name = "b_name") protected String name; @JoinTable( name = "b_a", joinColumns = @JoinColumn(name = "b_id"), inverseJoinColumns = @JoinColumn(name = "a_id")) @ManyToMany(fetch = FetchType.EAGER) protected List<A> a; // getters setters 省略 }
问题原因
- 关系维护端限制:A类的
@ManyToMany标注了mappedBy = "a",说明B是多对多关系的维护端,A是被维护端。JPA仅会识别维护端的关联变更,被维护端的修改不会同步到数据库。 - 游离态对象问题:从
getAByIdFromRepository获取的A实例是游离状态(EntityManager已关闭),即使双向移除了关联,merge时JPA不会基于被维护端的游离对象更新关联表。 - EntityManager资源滥用:每次操作都新建EntityManagerFactory和EntityManager,不仅浪费资源,还会导致对象状态管理混乱。
解决方案
方案1:操作维护端B的关联集合
直接修改作为维护端的B的关联集合,触发数据库更新:
public static void main(String[] args) { EntityManagerFactory factory = Persistence.createEntityManagerFactory("persistence_unit"); EntityManager em = factory.createEntityManager(); em.getTransaction().begin(); // 在同一个持久化上下文中获取实例 A a = em.find(A.class, 2); B b = em.find(B.class, 1); // 维护双向关联 a.getB().remove(b); b.getA().remove(a); // 合并维护端B的状态 em.merge(b); em.getTransaction().commit(); em.close(); factory.close(); }
方案2:统一EntityManager生命周期
避免使用游离态对象,在同一个持久化上下文中完成查询、修改、提交操作:
public static void main(String[] args) { EntityManagerFactory factory = Persistence.createEntityManagerFactory("persistence_unit"); EntityManager em = factory.createEntityManager(); em.getTransaction().begin(); A a = em.find(A.class, 2); B b = a.getB().stream().filter(i -> i.getId() == 1).findFirst().orElse(null); a.remove(b); // 持久化上下文内的对象无需merge,直接提交事务即可同步到数据库 em.getTransaction().commit(); em.close(); factory.close(); }
方案3:调整关系维护端(可选)
如果希望通过A来维护多对多关系,修改实体类的注解配置:
// A类改为维护端 @Entity @Table(name = "a") public class A implements Serializable { // 其他字段不变 @JoinTable( name = "b_a", joinColumns = @JoinColumn(name = "a_id"), inverseJoinColumns = @JoinColumn(name = "b_id")) @ManyToMany(fetch = FetchType.EAGER) protected List<B> b; public void remove(B b) { this.getB().remove(b); b.getA().remove(this); } } // B类改为被维护端 @Entity @Table(name = "b") public class B implements Serializable { // 其他字段不变 @ManyToMany(mappedBy = "b", fetch = FetchType.EAGER) protected List<A> a; }
修改后,原代码中的merge操作即可生效,因为A成为了关系维护端。
内容的提问来源于stack exchange,提问作者randomuser333
相关产品推荐
相关产品推荐

