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