28

我有两个实体: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 生成)。

给定 a 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. 如果我将 替换为ListSet则会发生正常的 INSERT。在阅读了这个文档博客后,我找到了原因。

来自文档的观察

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

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

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

BagsLists是最有效的逆Collections


话虽如此,哪个更可取:

  1. 单向映射中的aSet过a ?List@OneToMany

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

4

3 回答 3

20

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

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

简而言之:

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

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

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

所以 JPA/Hibernate 迫使我更改模型对象,这不是第一次,当您使用时@EmbededId,您可能会在没有 JPA/Hibernate 的情况下以同样的方式做一些事情。当您必须HibernateProxy在所有应用程序中特别是在 equals 方法中注意时else if(object instanceof HibernateProxy) {……,您会注意到 JPA/Hibernate 持久层在其他层中有点侵入。

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

Set如果它们与注释SortedSet一样,您可以订购TreeSet@OrderBy

当某些代码依赖List且无法更改(例如 JSF/PrimeFaces<dataTable><repeat>组件)时会带来问题,因此您必须更改SetList返回,Set但如果这样做setNotifications(new HashSet<>(notificationList)),您将有额外的查询,因为该集合是org.hibernate.collection.PersistentSet由 Hibernate 管理的...所以我使用addAll()andremoveAll()而不是二传手:

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) ;
}

注意你的方法equals()hashCode()方法@Entity...

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

制定了一个规范来抽象实现,使其不依赖于一个特定的供应商。尽管大多数 JavaEE 规范都成功了,但 JPA 对我来说失败了,我放弃了独立于 Hibernate

于 2015-04-10T13:26:20.373 回答
1

列表:允许其中有重复的元素。

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

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

于 2012-11-14T09:25:38.573 回答
0

当我不需要订购时,我使用 Set。这也启用了设置操作。另一方面,如果需要订购,我会使用列表。

在我看来,容器的语义是决定使用哪一个的相关方面。

于 2022-02-09T07:37:41.333 回答