JPA OneToMany: список против набора

Скажем, у меня есть два объекта USER и 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
 }

У меня есть переопределенные равные и хэш-коды (IDE, сгенерированные с помощью моего бизнес-ключа/первичного ключа). Скажем, у меня есть пользователь A. Когда я добавляю первое уведомление, я получаю нормальную инструкцию insert. Но при дальнейшем добавлении для того же пользователя он удаляет и затем вставляет. Это журнал:

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

То же самое происходит с каждым пользователем. Теперь, если я заменю List with Set, произойдет обычная вставка. И я нашел причину после прочтения doucmentation и blog.

Наблюдения из документов

  • В однонаправленной ассоциации OneToMany предпочтительнее Set.

должно быть ясно, что индексированные коллекции и наборы позволяют наиболее эффективную операцию с точки зрения добавления, удаления и обновления элементов.

  • В двунаправленных отношениях OneToMany (управление ManyToOne) список и сумки эффективны.

Суммы и списки являются наиболее эффективными обратными коллекциями

Несколько вопросов

  • Означает ли это, что я должен предпочесть набор над списком в однонаправленном сопоставлении OneToMany (довольно очевидно)? Но есть ли работа?
  • Или мне нужно настроить мой домен, чтобы сделать его двунаправленным отношением для использования списка, например, когда у меня есть дубликаты.

Ответы

Ответ 1

Я столкнулся с этой проблемой не так давно...

Я нашел эту статью: Производительность Antipatterns от одного до многих Ассоциация в 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 удалить

Для меня: да, это означает, что вам нужно изменить List на Set для однонаправленного @OneToMany. Поэтому я изменил свою модель, чтобы соответствовать ожиданиям Hibernate, и это вызывает множество проблем, потому что часть представления приложения полагалась на List в основном...

В одной руке Set является логическим выбором для меня, потому что нет дубликатов, с другой стороны List было легче справиться.

Итак, JPA/Hibernate заставил меня изменить объект модели, и это было не в первый раз, когда вы используете @EmbededId, вы делаете то, что вы, вероятно, не будете делать одинаково без JPA/Hibernate. И когда вам нужно знать HibernateProxy во всем приложении, особенно в методах equals... else if(object instanceof HibernateProxy) {..., вы заметили, что уровень persacency JPA/Hibernate немного навязчив в других слоях.

Но когда я использую напрямую JDBC, я также использую, чтобы изменить модель или методы бизнес-анализа, чтобы облегчить сохранение... Изоляция слоев может быть мечтой или стоить слишком много, чтобы сделать это на 100%?

И вы можете заказать Set, если они SortedSet как TreeSet с аннотацией @OrderBy

Это создает проблему, когда какой-то код полагается на List и не может быть изменен (например, компоненты JSF/PrimeFaces <dataTable> или <repeat>) Поэтому вам нужно изменить свой Set на List и вернуться к Set, но если вы сделаете setNotifications(new HashSet<>(notificationList)), у вас появятся дополнительные запросы, потому что набор - это org.hibernate.collection.PersistentSet, управляемый Hibernate... Поэтому я использовал addAll() и 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) ;
}

Обратите внимание на методы equals() и hashCode() вашего @Entity...

Еще одна проблема заключается в том, что вам нужно освоить JPA и Hibernate, если вы хотите использовать JPA с Hibernate в качестве реализации, потому что семантика Set/List/Bag от Hibernate не от JPA (исправьте меня, если я ошибаюсь)

Спецификация предназначена для абстрагирования реализации, чтобы она не зависела от одного конкретного поставщика. Хотя большинство спецификаций JavaEE удалось, JPA не удалось для меня, и я отказался от независимости от Hibernate

Ответ 2

List: Позволяет дублировать элементы.

Set: Все элементы должны быть уникальными.

Теперь удаление может происходить из-за того, что вы переписываете элемент в списке, поэтому, когда вы изменяете постоянный объект типа UserAccount, он удаляет сущность, которая находится в списке ранее.