单向@OnetoMany 映射删除所有关系并重新添加剩余关系而不是删除特定关系
Unidirectional @OnetoMany mapping deletes all relationships and re-adds remaining ones rather than removing the specific one
给定以下代码
public class Course {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<Review> reviews = new ArrayList<>();
}
public class Review {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false)
private String rating;
private String description;
}
保存的课程有 2 条评论。
如果我尝试从课程中删除一条评论。
course.getReviews().remove(0);
Hibernate 在查询后触发。
delete from course_reviews where course_id=?
binding parameter [1] as [BIGINT] - [1]
insert into course_reviews (course_id, reviews_id) values (?, ?)
binding parameter [1] as [BIGINT] - [1]
binding parameter [2] as [BIGINT] - [3]
请注意,它首先删除所有关系,然后插入剩余的关系。为什么会有这种行为?为什么不能更具体一点,只删除存储关系的那条记录。
Hibernate 这样做是因为它不知道实体是如何相关的。由于没有关于如何识别关系的信息,它使用它拥有的唯一信息——内存中的对象。因此它通过谓词清除 table 并从内存中保留实体。
您需要在子端使用 @JoinColumn
,在父端使用 @OneToMany
的 mappedBy
参数。
不确定这是由于包语义(因为您使用 List
而不是 Set
进行评论)还是仅仅因为 Hibernate 有时会进行所谓的“集合娱乐”。尝试使用 Set
.
首先,您在 documentation:
中描述了您看到的所有行为
The unidirectional associations are not very efficient when it comes to removing child entities. In the example above, upon flushing the persistence context, Hibernate deletes all database rows from the link table (e.g. Person_Phone) that are associated with the parent Person entity and reinserts the ones that are still found in the @OneToMany
collection.
On the other hand, a bidirectional @OneToMany
association is much more efficient because the child entity controls the association.
关于问题:
Why this behavior? Why couldn't it be more specific and delete just that one record storing the relationship.
答案并不那么简单,需要深入研究休眠源代码。
实体在hibernate中的集合处理的关键点是PersistentCollection接口。正如该界面的评论中所述:
Hibernate wraps a java collection in an instance of PersistentCollection
. This mechanism is designed to support tracking of changes to the collection's persistent state and lazy instantiation of collection elements. The downside is that only certain abstract collection types are supported and any extra semantics are lost.
我们讨论的重要地方有这个接口的如下方法:
/**
* 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 创建一个动作队列,用于在刷新时调度 creates/removes/updates(请参阅此队列中的 AbstractFlushingEventListener.flushCollections method). So, our collection belongs to one of the 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
returns true
(因为使用了 BasicCollectionPersister)。
- 您可以使用
@OrderColumn
annotation:
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@OrderColumn
private List<Review> reviews;
在这种情况下,集合将被视为 PersistentList
,您将避免重新创建集合。但是这也需要在Course_Review
table中附加顺序列(必须是整数类型)。当您尝试从列表的开头删除一个项目时,您也会有很多订单列更新。
- 您可以使用
Set
界面而不是 List
(正如 Christian Beikov 所注意到的):
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Review> reviews;
在这种情况下,集合将被视为 PersistentSet
,您也将避免重新创建集合。使用 Sets 时,它是 very important to supply proper equals/hashCode
implementations for child entities. A better equals/hashCode
实现,使用 natural-id 或 business-key。并且您将只能通过对象引用从该集合中删除一个项目,因为方法 remove(int index)
在 Set
接口中不存在。
给定以下代码
public class Course {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<Review> reviews = new ArrayList<>();
}
public class Review {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false)
private String rating;
private String description;
}
保存的课程有 2 条评论。
如果我尝试从课程中删除一条评论。
course.getReviews().remove(0);
Hibernate 在查询后触发。
delete from course_reviews where course_id=?
binding parameter [1] as [BIGINT] - [1]
insert into course_reviews (course_id, reviews_id) values (?, ?)
binding parameter [1] as [BIGINT] - [1]
binding parameter [2] as [BIGINT] - [3]
请注意,它首先删除所有关系,然后插入剩余的关系。为什么会有这种行为?为什么不能更具体一点,只删除存储关系的那条记录。
Hibernate 这样做是因为它不知道实体是如何相关的。由于没有关于如何识别关系的信息,它使用它拥有的唯一信息——内存中的对象。因此它通过谓词清除 table 并从内存中保留实体。
您需要在子端使用 @JoinColumn
,在父端使用 @OneToMany
的 mappedBy
参数。
不确定这是由于包语义(因为您使用 List
而不是 Set
进行评论)还是仅仅因为 Hibernate 有时会进行所谓的“集合娱乐”。尝试使用 Set
.
首先,您在 documentation:
中描述了您看到的所有行为The unidirectional associations are not very efficient when it comes to removing child entities. In the example above, upon flushing the persistence context, Hibernate deletes all database rows from the link table (e.g. Person_Phone) that are associated with the parent Person entity and reinserts the ones that are still found in the
@OneToMany
collection.On the other hand, a bidirectional
@OneToMany
association is much more efficient because the child entity controls the association.
关于问题:
Why this behavior? Why couldn't it be more specific and delete just that one record storing the relationship.
答案并不那么简单,需要深入研究休眠源代码。
实体在hibernate中的集合处理的关键点是PersistentCollection接口。正如该界面的评论中所述:
Hibernate wraps a java collection in an instance of
PersistentCollection
. This mechanism is designed to support tracking of changes to the collection's persistent state and lazy instantiation of collection elements. The downside is that only certain abstract collection types are supported and any extra semantics are lost.
我们讨论的重要地方有这个接口的如下方法:
/**
* 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 创建一个动作队列,用于在刷新时调度 creates/removes/updates(请参阅此队列中的 AbstractFlushingEventListener.flushCollections method). So, our collection belongs to one of the 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
returns true
(因为使用了 BasicCollectionPersister)。
- 您可以使用
@OrderColumn
annotation:
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@OrderColumn
private List<Review> reviews;
在这种情况下,集合将被视为 PersistentList
,您将避免重新创建集合。但是这也需要在Course_Review
table中附加顺序列(必须是整数类型)。当您尝试从列表的开头删除一个项目时,您也会有很多订单列更新。
- 您可以使用
Set
界面而不是List
(正如 Christian Beikov 所注意到的):
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Review> reviews;
在这种情况下,集合将被视为 PersistentSet
,您也将避免重新创建集合。使用 Sets 时,它是 very important to supply proper equals/hashCode
implementations for child entities. A better equals/hashCode
实现,使用 natural-id 或 business-key。并且您将只能通过对象引用从该集合中删除一个项目,因为方法 remove(int index)
在 Set
接口中不存在。