digital-garden/dev/java/hibernate/Дублирование значений при использовании @ElementCollection и @OneToMany.md
Struchkov Mark bd6b7c1492
All checks were successful
continuous-integration/drone/push Build is passing
Дочерние заметки
2024-09-14 23:38:42 +03:00

3.3 KiB
Raw Blame History

aliases tags date zero-link parents linked
maturity/🌱
2024-09-06
../../../meta/zero/00 Hibernate

Столкнулся с неочевидным поведением @ElementCollection в связке с @OneToMany. Может случиться так, что в @OneToMany будет дублирование значений из-за @ElementCollection. Проще объяснить на примере.

Допустим у нас есть три таблицы: useruser_nicknameaddress. Есть сущность User:

@Entity
public class User {

    // ... ... ... ... ...

    @OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
    private List<Address> stages = new ArrayList<>();

    @ElementCollection(fetch = FetchType.EAGER)
    @CollectionTable(
            name = "user_nickname",
            joinColumns = @JoinColumn(name = "user_id")
    )
    @Column(name = "nickname")
    private Set<String> nicknames = new HashSet<>();

    // ... ... ... ... ...

}

Допустим у нас одна запись про пользователя, у которого есть три адреса и один никнейм. В этом случае все хорошо, все придет корректно.

==Добавим этому пользователю еще один никнейм. Теперь мы получаем шесть адресов и два никнейма. Откуда взялись еще три лишних адреса, в базе данных их все еще три.== Это дубликаты. При этом, если вы воспользуетесь пагинацией JPA, то дублей уже не будет. It is magic 💫

Магии в программировании, как вы понимаете, нет. Они создаются при использовании FetchType.EAGER у @OneToMany в совокупности с @ElementCollection(fetch = FetchType.EAGER). Hibernate генерирует запрос с двумя-тремя полными соединениями, отсюда и берутся дубли. При этом в пагинации Hibernate не генерирует джойны, а использует кучу селектов, отсюда и отсутствие дублей при пагинации.

Эта проблема решается несколькими способами:

  • Переделайте List в Set у @OneToMany.
  • Уберите FetchType.EAGER у @OneToMany.
  • Добавьте @Fetch(FetchMode.SUBSELECT) у @OneToMany. это аннотация Hibernate, которая вместо JOIN использует подзапрос. О подзапросах я писал в отдельной статье.

Мета информация

Область:: ../../../meta/zero/00 Hibernate Родитель:: Источник:: Автор:: Создана:: 2024-09-06

Дополнительные материалы

Дочерние заметки