我有两个实体:UserAccount
和Notification
。它们之间的关系如下所示。
public class UserAccount {
@Id
@Column(name = "USER_NAME")
private String emailId;
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinTable(name = "USERS_NOTIFICATIONS", joinColumns = { @JoinColumn(name = "USER_NAME") }, inverseJoinColumns = { @JoinColumn(name = "NOTIFICATION_ID") })
private List<Notification> notifications;
//setters, getter, equals and hashcode
}
equals()
和hashcode()
都被覆盖(由具有业务密钥/主键的IDE生成)。
给定一个UserAccount
,当我添加第一个Notification
时,它会产生一个INSERT语句。但在对同一UserAccount
进行进一步添加时,它首先删除然后插入:
Hibernate: delete from USERS_NOTIFICATIONS where USER_NAME=?
Hibernate: insert into USERS_NOTIFICATIONS (USER_NAME, NOTIFICATION_ID) values (?, ?)
Hibernate: insert into USERS_NOTIFICATIONS (USER_NAME, NOTIFICATION_ID) values (?, ?)
Hibernate: insert into USERS_NOTIFICATIONS (USER_NAME, NOTIFICATION_ID) values (?, ?)
//as many inserts as the notifications the user has
每一个UserAccount
也是如此。如果我用Set
替换List
,则会发生正常的INSERT。我在阅读了这些文档和博客后发现了原因。
来自文档的观察
- 在单向
@OneToMany
关联中,Set
是优选的
应该清楚的是,索引
Collections
和Sets
允许在添加、删除和更新元素方面进行最有效的操作。
- 在双向
@OneToMany
关系(@ManyToOne
管理)中,List
和Bags
是有效的
Bags
和Lists
是最有效的逆Collections
。
话虽如此,哪个更可取:
单向
@OneToMany
映射中List
上的Set
?或者,我是否必须通过添加双向关系来调整我的域模型以使用
List
,尤其是当存在重复时?
不久前我遇到了这个问题。。。
我发现了这篇文章:一对多的性能反模式Hibernate中的关联https://fedcsis.org/proceedings/2013/pliks/322.pdf
简而言之:
- 袋子语义->
List
/Collection
+@OneToMany
->添加一个元素:1个删除,N个插入,删除一个元素 - 列表语义->
List
+@OneToMany
+@IndexColumn
/@OrderColumn
->添加一个元素:1个插入,M个更新,删除一个元素 - 设置语义->
Set
+@OneToMany
->添加一个元素:1个插入,删除一个元素,1个删除
对我来说:是的,这意味着你必须将你的List
更改为Set
以获得单向@OneToMany
。因此,我更改了我的模型以符合Hibernate的期望,这导致了很多问题,因为应用程序的视图部分主要依赖于List
。。。
一方面,Set
对我来说是一个合乎逻辑的选择,因为没有重复,另一方面List
更容易处理。
所以JPA/Hibernate迫使我更改模型对象,这已经不是第一次了,当你使用@EmbededId
时,你会做一些没有JPA/Hibernate可能不会做的事情。当你必须意识到HibernateProxy
在所有应用程序中,尤其是在equals方法中。。。CCD_ 43。。。,您注意到JPA/Hibernate持久层在其他层中有点侵入性。
但是当我直接使用JDBC时,我也会使用更改模型或构建方法来促进持久性。。。分层隔离可能是一个梦想,还是成本太高,无法100%完成?
如果它们是SortedSet
,就像带有注释@OrderBy
的TreeSet
一样,您可以订购Set
当某些代码依赖于List
并且无法更改时(例如JSF/PrimeFaces<dataTable>
或<repeat>
组件),这会带来问题因此,您必须将Set
更改为List
,然后返回到Set
,但如果您执行setNotifications(new HashSet<>(notificationList))
,则会有额外的查询,因为该集合是由Hibernate管理的org.hibernate.collection.PersistentSet
。。。所以我使用了addAll()
和removeAll()
而不是setter:
protected <E> void updateCollection(@NonNull Collection<E> oldCollection, @NonNull Collection<E> newCollection) {
Collection<E> toAdd = new ArrayList<>(newCollection) ;
toAdd.removeAll(oldCollection) ;
Collection<E> toRemove = new ArrayList<>(oldCollection) ;
toRemove.removeAll(newCollection) ;
oldCollection.removeAll(toRemove) ;
oldCollection.addAll(toAdd) ;
}
注意@Entity
的equals()
和hashCode()
方法。。。
另一个问题是,如果你想将JPA与Hibernate一起使用作为实现,你需要掌握JPA和Hibernate,因为Set/List/Bag语义来自Hibernate而不是JPA(如果我错了,请纠正我)
制定规范是为了抽象实现,使其不依赖于某个特定的供应商。尽管大多数JavaEE规范都成功了,但JPA对我来说失败了,我放弃了独立于Hibernate
列表:允许其中有重复元素。
Set:所有元素都应该是唯一的。
现在,删除可能是因为您重写了列表中的元素,所以当您修改UserAccount类型的持久化实体时,它将删除以前在列表中的实体。
当我不需要排序时,我会使用Set。这也启用了设置操作。另一方面,如果需要订购,我会使用列表。
在我看来,容器的语义是决定使用哪个容器的相关方面。