четверг, 15 августа 2019 г.

Как в Hibernate создать отображение многие-ко-многим через внешнюю таблицу, имеющую составной первичный ключ

Hibernate: How to map ManyToMany association with external table and composite key?
Этот интересный вопрос возник передо мной, когда на новом проекте разработка началась по принципу DDD - т.е. сначала была создана база данных со всеми таблицами и сущностями, а потом всё это нужно было реализовать в java-коде.
Честно скажу, повозиться пришлось с этим кодом немало. Зато теперь всё работает как часы и опыта в копилку прибавилось.
Итак, имеем следующую базу данных:

Не мудрствуя лукаво, создал базу такую же, как здесь.
Связь многие-ко-многим между двумя сущностями через промежуточную таблицу может быть представлена в виде двух связей многие к одному с промежуточным классом.
Создаём классы-сущности:

Параметр orphanRemoval=true сообщает фреймворку Hibernate, что тот должен навсегда удалять объекты при удалении их из коллекции.
Параметр fetch = FetchType.LAZY говорит о том, что коллекция будет загружаться только при прямом обращении к ней (по требованию) - т.н. стратегия отложенной загрузки. Для реализации такой отложенной загрузки Hibernate использует вместо объектов-коллекций сгенерированные во время выполнения заглушки, называемые прокси-объектами.
Создаём класс, который будет отображать связь многие-ко-многим между классами Tag и Post:
Что в этой промежуточной сущности интересно:
- аннотация @Immutable объявляет класс неизменяемым. Это позволяет Hibernate не проверять состояние объекта во время выталкивания контекста;
- встроенный класс PostTag.Id инкапсулирует составной ключ, состоящий из полей post_id и tag_id;
- @Embedable объявляет класс PostTag.Id встроенным в таблицу класса PostTag;
- @EmbeddedId - делает почти то же самое, что @Embedable, только для столбцов первичного ключа;
- конструктор класса PostTag, в котором производится формирование первичного ключа и двунаправленной связи многие-ко-многим между объектами Post и Tag. Эту связь очень просто сделать однонаправленной. Для этого в сущности, в которой отображение этой связи не нужно, необходимо убрать поле Set<PostTag> postTags и из конструктора класса PostTag убрать добавление созданного объекта PostTag в это поле.

Как упрощённо выглядит в java-коде создание и удаление связи между объектами Post и Tag на примере класса PostService:
Всё доволно просто:
- для создания связи нужно создать объект PostTag,
- для разрушения связи - удалить соответствующий объект PostTag из коллекции объекта Post.
Всё остальное за нас делает Hibernate.

П.с.: В классах используются аннотации фреймворков Lombok, Spring и Hibernate.