首先,您看到的所有行为都在documentation 中进行了描述:
单向关联在删除子实体时效率不高。在上面的示例中,在刷新持久性上下文时,Hibernate 会从链接表(例如 Person_Phone)中删除与父 Person 实体关联的所有数据库行,并重新插入仍然在 @OneToMany 集合中找到的行。
另一方面,双向@OneToMany 关联效率更高,因为子实体控制关联。
至于问题:
为什么会有这种行为?为什么不能更具体一点,只删除存储关系的那条记录。
答案并不那么简单,需要深入研究 hibernate 源代码。
hibernate中实体集合处理的关键点是PersistentCollection接口。正如它在该接口的 cmets 中所述:
Hibernate 包装在PersistentCollection 实例中的java 集合。此机制旨在支持跟踪对集合的持久状态的更改和集合元素的延迟实例化。缺点是仅支持某些抽象集合类型,并且丢失了任何额外的语义。
我们讨论的重要地方有这个接口的如下方法:
/**
* Do we need to completely recreate this collection when it changes?
*
* @param persister The collection persister
* @return {@code true} if a change requires a recreate.
*/
boolean needsRecreate(CollectionPersister persister);
Hibernate 创建一个操作队列,用于在刷新时调度创建/删除/更新(请参阅AbstractFlushingEventListener.flushCollections 方法)。因此,我们的集合属于此队列中的 CollectionUpdateAction 操作之一。
从CollectionUpdateAction.execute() 方法实现中可以看出,hibernate 根据collection.needsRecreate(persister) 调用检查是否需要重新创建集合。
PersistentCollection 接口具有以下实现层次结构:
PersistentCollection
|
|-- AbstractPersistentCollection
|
|-- PersistentArrayHolder
|-- PersistentBag
|-- PersistentIdentifierBag
|-- PersistentList
|-- PersistentMap
|
|-- PersistentSortedMap
|
|-- PersistentSet
|
|-- PersistentSortedSet
实际上,needsRecreate 方法仅在AbstractPersistentCollection 中实现,并通过以下方式覆盖PersistentBag:
@Override
public boolean needsRecreate(CollectionPersister persister) {
return !persister.isOneToMany();
}
Hibernate 在解析域模型时决定集合属于上述层次结构中的哪种类型。
- 当您使用问题映射中的描述时:
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<Review> reviews;
hibernate 会将其视为PersistentBag 并且PersistentCollection.needsRecreate 方法返回true(因为使用了BasicCollectionPersister)。
- 您可以使用
@OrderColumnannotation:
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@OrderColumn
private List<Review> reviews;
在这种情况下,集合将被视为PersistentList,您将避免重新创建集合。但这也需要Course_Review 表中的附加订单列(必须是整数类型)。当您尝试从列表的开头删除一个项目时,您还会有很多订单列更新。
- 您可以使用
Set 接口代替List(正如Christian Beikov 所注意到的):
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Review> reviews;
在这种情况下,集合将被视为PersistentSet,您也将避免重新创建集合。使用 Set 时,very important 为子实体提供正确的 equals/hashCode 实现。 better equals/hashCode 实现,使用自然 ID 或业务密钥。并且您将能够仅通过对象引用作为 remove(int index) 方法在 Set 接口中不存在的方法从该集合中删除一个项目。