JPA/Hibernate分离实体为何仅部分合并?关联删除无数据库变更
问题描述
从EntityManager获取实体A后关闭会话,将A置为detached状态,删除A关联的B实体后调用merge方法,数据库未产生对应变更:重新打开EntityManager后,仅A的name字段变更被合并,B实体列表的变更完全不生效;无任何异常、错误或警告,Hibernate日志仅显示查询语句,没有针对b_a中间表的删除操作。
但如果在同一个EntityManager实例中完成查询、删除关联实体、合并操作的全流程,功能可正常生效。
代码示例
主测试类
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; // getter、setter等省略 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; // getter、setter等省略 }
正常生效的代码示例
EntityManagerFactory factory = Persistence.createEntityManagerFactory("persistence_unit"); EntityManager entityManager = factory.createEntityManager(); A a = entityManager.find(A.class, 2); B b = a.getB().stream().filter(i -> i.getId() == 1).findFirst().orElse(null); a.remove(b); entityManager.getTransaction().begin(); entityManager.merge(a); entityManager.getTransaction().commit(); entityManager.close(); factory.close();
问题原因
- 关联关系维护端定位错误:A的
@ManyToMany注解使用了mappedBy = "a",说明A是关联关系的被动端(inverse端),B才是主动维护端(owning端)——中间表b_a的增删改操作只能通过维护端B的集合变更触发。 - Detached实体的merge行为限制:当A处于detached状态时,调用
merge(a)只会同步A自身的基本属性(比如name),不会处理其关联集合的变更——Hibernate认为被动端的集合变更不应该触发中间表操作,且此时B也是detached状态,merge不会自动处理游离实体的集合修改。 - 同EntityManager实例的状态差异:在同一个EntityManager中,A和B都是managed状态,Hibernate会追踪所有实体的状态变更,即使修改了被动端A的集合,也会同步维护端B的状态,从而触发中间表的更新。
解决方案
方案1:操作维护端B并merge
修改合并逻辑,同时merge被修改的B实体,确保维护端的集合变更被Hibernate捕获:
public static void repositoryMerge(A a, B b) { EntityManagerFactory factory = Persistence.createEntityManagerFactory("persistence_unit"); EntityManager entityManager = factory.createEntityManager(); entityManager.getTransaction().begin(); // merge A A managedA = entityManager.merge(a); // merge B(此时B的集合已移除A,维护端变更会触发中间表操作) entityManager.merge(b); entityManager.getTransaction().commit(); entityManager.close(); factory.close(); }
方案2:重新获取Managed状态的实体进行修改
避免操作detached实体,在合并前重新从EntityManager中加载A和B的managed实例,再执行删除关联的操作:
public static void fixAndMerge(int aId, int bId) { EntityManagerFactory factory = Persistence.createEntityManagerFactory("persistence_unit"); EntityManager entityManager = factory.createEntityManager(); entityManager.getTransaction().begin(); // 获取managed状态的A和B A managedA = entityManager.find(A.class, aId); B managedB = entityManager.find(B.class, bId); // 删除关联 managedA.remove(managedB); entityManager.getTransaction().commit(); entityManager.close(); factory.close(); }
方案3:调整关联关系的维护端(可选)
如果业务场景允许,将A设为关联关系的维护端,这样修改A的集合就能直接触发中间表的操作:
// 修改实体A的注解,移除mappedBy,添加@JoinTable @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; } // 修改实体B的注解,添加mappedBy @Entity @Table(name = "b") public class B implements Serializable { // ...其他属性 @ManyToMany(mappedBy = "b", fetch = FetchType.EAGER) protected List<A> a; }
内容的提问来源于stack exchange,提问作者randomuser333
相关产品推荐
相关产品推荐

