JPA/Hibernate 级联删除不起作用

Posted

技术标签:

【中文标题】JPA/Hibernate 级联删除不起作用【英文标题】:JPA/Hibernate cascade remove not working 【发布时间】:2016-09-28 09:32:34 【问题描述】:

我有这些实体:

@Entity
public class Item extends Unit

    // @Id is in superclass

    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<ItemRelation> lowerItemRelations = new LinkedHashSet<>();

    @OneToMany(mappedBy = "child", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<ItemRelation> higherItemRelations = new LinkedHashSet<>();

    // this too is in superclass
    @OneToMany(mappedBy = "unit", cascade = CascadeType.REMOVE, orphanRemoval = true)
    @OrderBy("date")
    protected Set<UnitRegistration> registrations = new LinkedHashSet<>();

    ...


@Entity
@Table(name = "ITEM_RELATION",
    indexes = @Index(columnList = "PARENT_ID, CHILD_ID", unique = true))
public class ItemRelation extends AbstractEntity

    // @Id is in superclass

    @ManyToOne(optional = false)
    @JoinColumn(name = "PARENT_ID")
    private Item parent;

    @ManyToOne(optional = false)
    @JoinColumn(name = "CHILD_ID")
    private Item child;

    @NotNull
    @Min(0)
    @Column(nullable = false, columnDefinition = "INT DEFAULT 1 NOT NULL")
    private int quantity = 1;

    ...

现在我只想执行一个简单的em.remove(item),但是Hibernate 不会为lowerItemRelations/higherItemRelations 发出相关的DELETE 语句。

相反,对于所有其他带有 @OneToMany(mappedBy = "...", cascade = CascadeType.ALL/REMOVE, orphanRemoval=true) 注释的字段,它会发出语句。

这里有一点 mysql 日志 sn-p:

2016-09-28T08:47:52.090453Z   13 Query  update UNIT set CODE='CE13000003167', ... where ID=132241 and version=1
2016-09-28T08:47:52.094971Z   13 Query  delete from UNIT_ACTION where PARENT_ID=132241
2016-09-28T08:47:52.134999Z   13 Query  update AUTHORIZATION set UNIT_ID=null where UNIT_ID=132241
2016-09-28T08:47:52.158014Z   13 Query  delete from UNIT_DOCUMENT where PARENT_ID=132241
2016-09-28T08:47:52.248074Z   13 Query  delete from UNIT_PRODUCT where UNIT_ID=132241
2016-09-28T08:47:52.315641Z   13 Query  delete from UNIT_PROJECT where UNIT_ID=132241
2016-09-28T08:47:52.586008Z   13 Query  delete from ITEM_ALTERNATIVE where ITEM_ID=132241
2016-09-28T08:47:52.853350Z   13 Query  delete from AUTHORIZATION where ID=714491
2016-09-28T08:47:52.910835Z   13 Query  delete from UNIT_REGISTRATION where ID=173505
2016-09-28T08:47:52.980887Z   13 Query  delete from UNIT where ID=132241 and version=1
2016-09-28T08:47:53.133290Z   13 Query  rollback

如您所见,没有可以从 ITEM_RELATION 中删除的行,我期待的是:

0000-00-00T00:00:00.000000Z   13 Query  delete from ITEM_RELATION where PARENT_ID=132241
0000-00-00T00:00:00.000000Z   13 Query  delete from ITEM_RELATION where CHILD_ID=132241

显然事务被回滚了,因为:

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`edea2`.`item_relation`, CONSTRAINT `FK_ITEM_RELATION_CHILD_ID` FOREIGN KEY (`CHILD_ID`) REFERENCES `unit` (`ID`)) 

另一个奇怪的事情是 Hibernate 执行(不必要的?)UPDATE 作为第一条语句。

然而,

这种不同的行为是否与 lowerItemRelations/higherItemRelations 引用相同的实体类型(尽管在不同的字段/列和不同的行上)这一事实有关?

这是一个错误还是有这种行为的原因?

我尝试了什么:

初始化集合 初始化并清除集合(以触发 orphanRemoval) em.remove()em.remove(item) 之前的每个集合元素

没有成功。

到目前为止我发现的唯一工作方式是发出:

CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaDelete<ItemRelation> delete = builder.createCriteriaDelete(ItemRelation.class);
Root<ItemRelation> rel = delete.from(ItemRelation.class);
delete.where(builder.or(
    builder.equal(rel.get(ItemRelation_.parent), managedItem),
    builder.equal(rel.get(ItemRelation_.child), managedItem)));

em.flush();

em.remove(managedItem);

我在 Wildfly 10.1.0.Final 上使用 Hibernate 5.2.2.Final

谢谢


这里的要求是 em.remove() 被调用的地方:

@Stateless
@Local
public class PersistenceService implements Serializable
       
    @PersistenceContext
    private EntityManager em;

    ...

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public <T> void delete(T entity)
    
        T managed;

        if(!em.contains(entity))
        
            Class<T> entityClass = EntityUtils.getClass(entity);
            Object entityId = EntityUtils.getIdentifier(entity);

            managed = em.find(entityClass, entityId);
        
        else
         
            managed = entity;
        

        em.remove(managed);

        // em.flush(); // just for debugging
    

【问题讨论】:

你能在em.remove(item)失败的地方追加代码片段吗? 请查看更新。它在 tx 提交时失败(或未注释时 em.flush()) 【参考方案1】:

好的,这是一个错误。

我发现此行为与两个集合的延迟初始化有关,因此我提交了问题 HHH-11144 并生成了一个简单的测试用例(也可在 GitHub 上找到)。

简而言之,这种情况发生在

EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();

tx.begin();

Item item = em.createQuery("select x from Item x where x.code = 'first'", Item.class).getSingleResult();

Set<ItemRelation> lowerItemRelations = item.getLowerItemRelations();
Hibernate.initialize(lowerItemRelations);

// initializing 'higherItemRelations' prevents orphanRemoval to work on 'lowerItemRelations'
Set<ItemRelation> higherItemRelations = item.getHigherItemRelations();
Hibernate.initialize(higherItemRelations);

lowerItemRelations.clear();

tx.commit();
em.close();

【讨论】:

【参考方案2】:

我敢打赌,Unit 正在引用其他内容,并且不是您的根用户独有的 - 在这种情况下为 ItemRelation,因此无法删除。

您的映射表明Unit 可以有多个ItemRelations,所以这很可能是这里的问题。您必须首先通过删除双方的相互引用来“破坏”关系(或者至少在持有 FK 的实体上,或者如果使用连接表,则其中任何一个)

【讨论】:

我不明白你的回答...当然 Unit 正在引用其他实体(例如我举的 UnitRegistrations 示例),实际上目标是一次删除所有实体(级联)。 Item ItemRelation 之间的映射完全用sn-p 表示(@OneToMany/@ManyToOne 和@JoinColumn),你看没有@JoinTable。请问,你能详细说明吗? 我知道动作可以从上到下级联到它结束的地方,但是你想要的是级联过程从底部反弹到顶部并与 N 个其他相关实体重新开始。恕我直言cascade 不会处理这个,但我可能是错的。 不,你错了。我不想删除 related items,我只想删除 managedItem 及其所有 lowerItemRelationshigherItemRelations i> ItemRelation 实体,它将两个 FK 保存到 UNIT 表 - 如您所见, ItemRelation.parent 和 ItemRelation.child 没有声明级联属性,所以我不要求休眠删除整个图,只是目标实体和直接的 ItemRelation 邻居(声明了级联):)

以上是关于JPA/Hibernate 级联删除不起作用的主要内容,如果未能解决你的问题,请参考以下文章

spring.jpa.hibernate.ddl-auto=create 在带有 SpringBoot 2.0 的 Hibernate 5 中不起作用

Coredata,级联删除不起作用

Postgres - 级联删除不起作用

sql级联更新和级联删除不起作用

删除级联在这种情况下不起作用[重复]

使用 spring boot JPA hibernate 存储库检查用户名和密码的 Mysql 查询不起作用。我该怎么办?