JPA OneToMany:List vs Set

我有两个实体: UserAccountNotification 。 这些关系如下所示。

  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是首选。

应该清楚的是,索引的CollectionsSets允许在添加,删除和更新元素方面进行最有效的操作。

  • 在双向@OneToMany关系( @ManyToOne管理)中, ListBags是高效的。

BagsLists是最有效的逆Collections


话虽如此,更可取的是:

  1. 在单向@OneToMany映射中Set List

  2. 或者,我是否必须通过添加双向关系来调整我的域模型以使用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) ; } 

记住@Entityequals()hashCode()方法……

另一个问题是如果你想使用JPA和Hibernate作为实现你需要掌握JPA和Hibernate,因为Set / List / Bag语义来自Hibernate而不是来自JPA(如果我错了,请纠正我)

规范用于将实现抽象为不依赖于特定供应商。 尽管大多数JavaEE规范都成功了,但JPA对我来说失败了,而且我放弃了独立于Hibernate

列表:允许其中包含重复元素。

设置:所有元素都应该是唯一的。

现在,删除可能正在发生,因为您在列表中覆盖了元素,因此当您修改UserAccount类型的持久化实体时,它正在删除先前在列表中的实体。