Spring Data Repository使用@OneToMany时无法删除关联Entity的原因
问题:通过Entity2 Repository删除实体无效的原因解析
场景
- 基于Spring Boot Starter Data JPA构建的应用
- 使用H2内存关系型数据库
- Entity1与Entity2为双向关联:
- Entity1通过
@OneToMany关联多个Entity2 - Entity2通过
@ManyToOne关联单个Entity1
- Entity1通过
实体代码
Entity1
@Entity @Table(name="entity_1") public class Entity1 { @Id @Column(name="id", columnDefinition="char(36)") private String id; @OneToMany( mappedBy="entity1", cascade = CascadeType.ALL, orphanRemoval = true ) private List<Entity2> entity2 = new ArrayList<>(); public String getId() { return id; } public Entity1 setId(String id) { this.id = id; return this; } public List<Entity2> getEntity2() { return entity2; } public Entity1 setEntity2(List<Entity2> entity2) { this.entity2 = entity2; return this; } }
Entity2
@Entity @Table(name="entity_2") public class Entity2 { @Id @Column(columnDefinition = "char(36)") private String id; @ManyToOne(optional = false) @JoinColumn(name="entity1", referencedColumnName = "id") private Entity1 entity1; public String getId() { return id; } public Entity2 setId(String id) { this.id = id; return this; } public Entity1 getEntity1() { return entity1; } public Entity2 setEntity1(Entity1 entity1) { this.entity1 = entity1; return this; } }
关联初始化代码
Entity1 e1 = new Entity1().setId("1_1"); Entity2 e2_1 = new Entity2().setId("2_1"); Entity2 e2_2 = new Entity2().setId("2_2"); e1.getEntity2().add(e2_1); e1.getEntity2().add(e2_2); e2_1.setEntity1(e1); e2_2.setEntity1(e1); entity1Repo.save(e1);
问题重现
尝试通过Entity2的Repository直接删除Entity2时,操作无效果且无报错:
public void deleteEntity2UsingRepo2() { Entity1 e1 = entity1Repo.findAll().get(0); Entity2 e2 = e1.getEntity2().get(0); entity2Repo.delete(e2); }
关键日志
2022-08-24 13:35:26.577 TRACE 4956 --- [ main] o.h.e.i.DefaultDeleteEventListener : Deleting a persistent instance 2022-08-24 13:35:26.578 TRACE 4956 --- [ main] o.h.e.i.DefaultDeleteEventListener : Deleting [de.peravs.database.sample.entity.Entity2#2_1 ] 2022-08-24 13:35:26.579 DEBUG 4956 --- [ main] o.h.e.t.internal.TransactionImpl : committing
提交后实体仍存在:
2022-08-24 13:35:26.581 DEBUG 4956 --- [ main] o.hibernate.internal.util.EntityPrinter : Listing entities: 2022-08-24 13:35:26.581 DEBUG 4956 --- [ main] o.hibernate.internal.util.EntityPrinter : de.peravs.database.sample.entity.Entity2{entity1=de.peravs.database.sample.entity.Entity1#1_1 , id=2_2 } 2022-08-24 13:35:26.581 DEBUG 4956 --- [ main] o.hibernate.internal.util.EntityPrinter : de.peravs.database.sample.entity.Entity2{entity1=de.peravs.database.sample.entity.Entity1#1_1 , id=2_1 } 2022-08-24 13:35:26.581 DEBUG 4956 --- [ main] o.hibernate.internal.util.EntityPrinter : de.peravs.database.sample.entity.Entity1{entity2=[de.peravs.database.sample.entity.Entity2#2_1 , de.peravs.database.sample.entity.Entity2#2_2 ], id=1_1 }
Repository定义
public interface Entity1Repo extends Repository<Entity1, String> { List<Entity1> findAll(); void save(Entity1 entity1); } public interface Entity2Repo extends Repository<Entity2, String> { void delete(Entity2 entity2); }
原因分析
核心问题是双向关联的一致性与持久化上下文同步:
- 因为
@OneToMany(mappedBy="entity1"),说明Entity2是关联的拥有方(负责维护数据库中的外键字段),Entity1是关联的非拥有方(不维护外键)。 - 直接通过Entity2Repo删除Entity2时,Hibernate虽执行了删除逻辑,但此时持久化上下文中的Entity1实例仍保留着被删除的Entity2引用(entity2列表里还存在该实例)。
orphanRemoval=true的作用是当父实体(Entity1)移除子实体(Entity2)的引用时触发删除,但直接删除子实体时,父实体的内存引用未更新,Hibernate在flush阶段检测到双向关联不一致,最终回滚删除操作,保证内存实体状态与数据库一致。
解决方案
方案1:同步双向关联后删除
删除Entity2时,手动从Entity1的关联列表中移除该实例:
public void deleteEntity2UsingRepo2Fixed() { Entity1 e1 = entity1Repo.findAll().get(0); Entity2 e2 = e1.getEntity2().get(0); // 同步双向关联 e1.getEntity2().remove(e2); entity2Repo.delete(e2); }
方案2:通过父实体触发删除(已验证有效)
利用orphanRemoval=true的特性,通过父实体移除子实体引用后保存:
public void deleteEntity2UsingRepo1() { Entity1 e1 = entity1Repo.findAll().get(0); e1.getEntity2().remove(0); entity1Repo.save(e1); }
这种方式会因为orphanRemoval=true自动触发子实体的删除,同时保证双向关联的一致性。
内容的提问来源于stack exchange,提问作者PeRaVs
相关产品推荐
相关产品推荐

