You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

移除关联实体后Merge多对多关系实体失败的JPA/Hibernate问题

多对多关系中移除关联后merge操作不生效的问题及解决方法

问题描述

A和B为多对多关联的实体类,执行以下操作后数据库关联表b_a未删除对应记录:

  1. 通过getAByIdFromRepository获取ID为2的A实例,随后关闭EntityManager;
  2. 从A的B列表中筛选出ID为1的B实例并移除;
  3. 调用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 省略

}

问题原因

  1. 关系维护端限制:A类的@ManyToMany标注了mappedBy = "a",说明B是多对多关系的维护端,A是被维护端。JPA仅会识别维护端的关联变更,被维护端的修改不会同步到数据库。
  2. 游离态对象问题:从getAByIdFromRepository获取的A实例是游离状态(EntityManager已关闭),即使双向移除了关联,merge时JPA不会基于被维护端的游离对象更新关联表。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 16:05:04