JPA OneToMany:List vs Set

时间:2012-10-27 16:15:05

标签: java hibernate jpa

我有两个实体: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<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都会发生同样的情况。如果我用List替换Set,则会发生正常的INSERT。我在阅读documentationblog之后找到了原因。

文档观察

  • 在单向@OneToMany关联中,首选Set
  

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

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

BagsLists是效率最高的Collections


话虽如此,更为可取:

  1. 单向Set映射中的List上的@OneToMany

  2. 或者,我是否必须通过添加双向关系来调整我的域模型以使用List,尤其是在存在重复项时?

2 个答案:

答案 0 :(得分:11)

我不久前遇到了这个问题......

我发现这篇文章:一对多的性能反模式 Hibernate中的关联https://fedcsis.org/proceedings/2013/pliks/322.pdf

简而言之:

  • Bag语义 - &gt; List / Collection + @OneToMany - &gt;添加了一个元素:1个删除,N个插入,删除了一个元素:1个删除,N个插入
  • 列表语义 - &gt; List + @OneToMany + @IndexColumn / @OrderColumn - &gt;添加了一个元素:1个插入,M个更新,删除了一个元素:1个删除,M个更新
  • 设置语义 - &gt; Set + @OneToMany - &gt;添加了一个元素:1个插入,删除了一个元素:1删除

对我来说:是的,这意味着您必须将List更改为Set以获得单向@OneToMany。所以我改变了我的模型以符合Hibernate的期望,这导致了很多问题,因为应用程序的视图部分主要依赖于List ...

一方面Set对我来说是合乎逻辑的选择,因为没有重复,另一方面List更容易处理。

所以JPA / Hibernate强迫我改变模型对象,这不是第一次,当你使用@EmbededId时,你做的事情你可能在没有JPA的情况下以同样的方式做休眠。当你必须在所有应用程序中都知道HibernateProxy时,特别是在equals方法... else if(object instanceof HibernateProxy) { ...中,你会注意到JPA / Hibernate persitence层在其他层中有点干扰。

但是当我使用directely JDBC时,我也会使用更改模型或商务方法来促进持久性...... 层隔离可能是一个梦想,或者成本太高而不能100%完成?

如果Set SortedSetTreeSet注释@OrderBy

,则可以订购List

当某些代码依赖<dataTable>并且无法更改时(例如JSF / PrimeFaces <repeat>Set组件),这会带来问题 因此,您必须将List更改为Set并返回setNotifications(new HashSet<>(notificationList)),但如果您执行org.hibernate.collection.PersistentSet,则会有额外的查询,因为该集是addAll()由Hibernate管理...所以我使用了removeAll()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) ; } 而不是setter:

equals()

请注意hashCode() ...

@Entity和{{1}}方法

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

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

答案 1 :(得分:1)

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

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

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