Java 单向@OnetoMany映射删除所有关系并重新添加其余关系,而不是删除特定关系

Java 单向@OnetoMany映射删除所有关系并重新添加其余关系,而不是删除特定关系,java,hibernate,jpa,one-to-many,hibernate-mapping,Java,Hibernate,Jpa,One To Many,Hibernate Mapping,给定以下代码 公共课{ @身份证 @生成值 私人长id; 私有字符串名称; @OneToMany(cascade=CascadeType.ALL,orphan=true) 私有列表评论=新建ArrayList(); } 公开课复习{ @身份证 @生成值 私人长id; @列(nullable=false) 私人字符串评级; 私有字符串描述; } 已保存课程,共2次复习 如果我试图从课程中删除一个复习 course.getReviews().remove(0); Hibernate激发以下查询

给定以下代码

公共课{
@身份证
@生成值
私人长id;
私有字符串名称;
@OneToMany(cascade=CascadeType.ALL,orphan=true)
私有列表评论=新建ArrayList();
}
公开课复习{
@身份证
@生成值
私人长id;
@列(nullable=false)
私人字符串评级;
私有字符串描述;
}
已保存课程,共2次复习

如果我试图从课程中删除一个复习

course.getReviews().remove(0);
Hibernate激发以下查询

从课程审查中删除,其中课程id=?
将参数[1]绑定为[BIGINT]-[1]
在课程复习(课程id,复习id)中插入值(?,)
将参数[1]绑定为[BIGINT]-[1]
将参数[2]绑定为[BIGINT]-[3]

请注意,它首先删除所有关系,然后插入其余的关系。为什么会有这种行为?为什么不能更具体一点,只删除存储关系的一条记录。

Hibernate这样做是因为它不知道实体之间是如何关联的。因为没有关于如何识别关系的信息,所以它只使用它拥有的信息—内存中的对象。因此,它通过谓词清除表,并从内存中持久化实体


您需要在子端使用
@JoinColumn
,在父端使用
@OneToMany
mappedBy
参数。

不确定这是否是由于包语义(因为您使用
列表而不是
设置
进行查看)或仅仅是因为Hibernate有时会进行所谓的“收集重现”。尝试使用
集合

首先,您看到的行为在:

在删除子实体时,单向关联不是很有效。在上面的示例中,在刷新持久性上下文时,Hibernate从链接表(例如Person_Phone)中删除与父Person实体关联的所有数据库行,并重新插入仍在
@OneToMany
集合中找到的数据库行

另一方面,双向
@OneToMany
关联效率更高,因为子实体控制关联

至于问题:

为什么会有这种行为?为什么不能更具体地删除存储关系的一条记录呢

答案并不简单,需要深入研究hibernate源代码

hibernate中实体集合处理的关键点是接口。如本接口评论中所述:

Hibernate将java集合包装在
PersistentCollection
的实例中。此机制旨在支持跟踪对集合的持久状态的更改和集合元素的延迟实例化。缺点是只支持某些抽象集合类型,并且会丢失任何额外的语义

我们讨论的重要地方有以下接口方法:

/**
*当此集合发生更改时,我们是否需要完全重新创建它?
*
*@param persister集合persister
*@return{@code true}如果更改需要重新创建。
*/
布尔需求创建(CollectionPersister persister);
Hibernate创建一个操作队列,用于安排在刷新时创建/删除/更新(请参见方法)。因此,我们的集合属于此队列中的一个操作

CollectionUpdateAction.execute()
方法实现中可以看到,hibernate根据
collection.needsRecreate(persister)
调用中的

PersistentCollection
接口具有以下实现层次结构:

PersistentCollection
   |
   |-- AbstractPersistentCollection
           |
           |-- PersistentArrayHolder
           |-- PersistentBag
           |-- PersistentIdentifierBag
           |-- PersistentList
           |-- PersistentMap
                  |
                  |-- PersistentSortedMap
           |
           |-- PersistentSet
                  |
                  |-- PersistentSortedSet
实际上,
需要create
方法,该方法仅在
AbstractPersistentCollection
中实现,并通过以下方式覆盖
PersistentBag

@覆盖
公共布尔需求创建(CollectionPersister persister){
return!persister.isOneToMany();
}
Hibernate在解析域模型时决定集合属于上述层次结构的类型

  • 当您使用问题映射中描述的选项时:
  • @OneToMany(cascade=CascadeType.ALL,orphan=true)
    私人名单审查;
    
    hibernate将其视为
    PersistentBag
    ,方法
    PersistentCollection.needsCreate
    返回
    true
    (因为使用了)

  • 您可以使用
    @OrderColumn
  • @OneToMany(cascade=CascadeType.ALL,orphan=true)
    @订单列
    私人名单审查;
    
    在这种情况下,集合将被视为
    PersistentList
    ,您将避免重新创建集合。但在
    课程回顾
    表中,还需要额外的订单列(必须为整型)。当您试图从列表的开头删除一个项目时,您也会有很多订单列更新

  • 您可以使用
    设置
    界面而不是
    列表
    (Christian Beikov注意到了这一点):
  • @OneToMany(cascade=CascadeType.ALL,orphan=true)
    私集审查;
    
    在这种情况下,集合将被视为
    PersistentSet
    ,您也将避免集合的重新创建。使用集合时,需要为子实体提供适当的
    equals/hashCode
    实现。
    等于/hashCode
    实现,使用自然id或业务密钥。您将能够仅通过对象引用从该集合中删除项,方法是
    remove(int index)
    just abse