JPA OneToMany:List vs Set
我有两个实体: 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 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
都会发生同样的情况。 如果我用List
替换List
,则会发生正常的INSERT。 我在阅读本文档和博客后找到了原因。
来自文档的观察
- 在单向
@OneToMany
关联中,Set
是首选。
应该清楚的是,索引的
Collections
和Sets
允许在添加,删除和更新元素方面进行最有效的操作。
- 在双向
@OneToMany
关系(@ManyToOne
管理)中,List
和Bags
是高效的。
Bags
和Lists
是最有效的逆Collections
。
话虽如此,更可取的是:
-
在单向
@OneToMany
映射中Set
List
? -
或者,我是否必须通过添加双向关系来调整我的域模型以使用
List
,尤其是当存在重复时?
不久前我遇到了这个问题……
我发现这篇文章:Hibernate中一对多关联的性能反模式https://fedcsis.org/proceedings/2013/pliks/322.pdf
简而言之:
- Bag语义 – >
List
/Collection
+@OneToMany
– >添加了一个元素:1个删除,N个插入,删除了一个元素:1个删除,N个插入 - 列表语义 – >
List
+@OneToMany
+@IndexColumn
/@OrderColumn
– >添加了一个元素:1个插入,M个更新,删除了一个元素:1个删除,M个更新 - 设置语义 – >
Set
+@OneToMany
– >添加一个元素:1个插入,删除一个元素:1删除
对我来说:是的,这意味着您必须将List
更改为单向@OneToMany
Set
。 所以我改变了我的模型以符合Hibernate的期望,这导致了很多问题,因为应用程序的视图部分主要依赖于List
…
一方面, Set
对我来说是一个合乎逻辑的选择,因为没有重复,另一方面List
更容易处理。
所以JPA / Hibernate强迫我改变模型对象,这不是第一次,当你使用@EmbededId
你做了一些你可能不会在没有JPA / Hibernate的情况下做同样的事情。 当你必须在所有应用程序中都知道HibernateProxy
,特别是在equals方法中… else if(object instanceof HibernateProxy) {
…,你会注意到JPA / Hibernate persitence层在其他层中有点干扰。
但是,当我使用directely JDBC时,我也会使用更改模型或商务方法来促进持久性…层次隔离可能是一个梦想或成本太高而不能100%完成?
如果它们像TreeSet
一样使用注释@OrderBy
进行SortedSet
则可以订购Set
当某些代码依赖于List
并且无法更改时(例如JSF / PrimeFaces
或
组件),这会带来问题所以您必须将Set
更改为List
并返回Set
但是如果您执行setNotifications(new HashSet<>(notificationList))
你将有额外的查询,因为该集合是由Hibernate管理的org.hibernate.collection.PersistentSet
…所以我使用addAll()
和removeAll()
而不是setter:
protected void updateCollection(@NonNull Collection oldCollection, @NonNull Collection newCollection) { Collection toAdd = new ArrayList<>(newCollection) ; toAdd.removeAll(oldCollection) ; Collection 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
列表:允许其中包含重复元素。
设置:所有元素都应该是唯一的。
现在,删除可能正在发生,因为您在列表中覆盖了元素,因此当您修改UserAccount类型的持久化实体时,它正在删除先前在列表中的实体。
- @Transactional的奇怪行为(propagation = Propagation.REQUIRES_NEW)
- 我可以在ConstraintValidator中为Method参数更改属性路径吗?
- Hibernate中的可选一对一映射
- java.lang.NoSuchMethodError:org.hibernate.SessionFactory.openSession()Lorg / hibernate / classic / Session
- Hibernate 4未设置’hibernate.dialect’时,对DialectResolutionInfo的访问不能为空
- 使用TIMESTAMPDIFF的JPA Hibernate公式中的SQL Literal
- Hibernate支持Postgresql UUID?
- EntityManager persist()不保存任何数据库
- Hibernate无法打开连接