Отображение полей типа Set и List в встраиваемых (@Embeddable) классах

Встраиваемые(@Embeddable) классы-значения могут содержать поле типа той или иной коллекции (List, Set и т.п.). Тогда данные этой коллекции будут храниться в отдельной таблице, которая может быть настроена с помощью аннотаций @ElementCollection и @CollectionTable.

Подготовка

Создадим базовое веб-приложения на связке Spring Boot 3 + Hibernate + PostgreSQL

Убедитесь, что файле /src/main/resources/application.properties есть следующая строка, позволяющая Hibernate’у автоматически создавать (и обновлять) схему БД при запуске приложения на основании аннотаций в классах предметной области:

spring.jpa.hibernate.ddl-auto=update

Код

Создадим встраиваемый класс-значение, содержащий одно поле типа Set:

Мы помечаем поле Set<String> аннотациями @ElementCollection и @CollectionTable. Данные этого поля будут храниться в отдельной таблице. В параметрах аннотации @CollectionTable мы задаём настройки этой таблицы. В параметр name передаём название этой таблицы. В параметре joinColumns мы передаём аннотацию @JoinColumn (можно передать несколько массивом), в которой в параметре name задаём имя колонки-внешнего ключа на родительскую таблицу. Если её не задать, то будет автоматически создана колонка вида ИМЯТАБЛИЦЫ_ID. Но мы именно такое имя и задали, только вручную.

Напишем класс-сущность, содержащий поле созданного ранее класс-значения:

Мы ожидаем, что раз поле email имеет тип Email, который является встраиваемым типом (помечен аннотацией @Embeddable), то поля объектов классов Person и Email будут отображены в одну таблицу PERSON.

Напишем репозиторий для класса-сущности:

Обратите внимание, как мы присоединяем данные из таблицы PERMISSIONS. У класса Person нет прямой ссылки на Set<String> permissions, поэтому ссылка получается ступенчатая: p.email.permissions.

Напишем тест, который продемонстрирует работу кода:

Тест демонстрирует, что код работает, как и ожидается. Мы сохраняем в базу объект irina с полями email, которое в свою очередь содержит множество (Set) строк с «разрешениями». Затем извлекаем из БД по id соответствующую строку и проверяем, что все извлечённые данные полностью соответствуют сохранённым.

Рассмотри созданную схему данных:

Мы видим, что, действительно, Hibernate создал для данных поля Set<String> permission отдельную таблицу, тогда как данные остальных полей встраиваемого класса Email сохраняет в таблицу PERSON, как мы и ожидаем от полей встраиваемых классов.