Отображение в БД иерархии наследования встраиваемых @Embeddable классов в Hibernate

Если классы-значения, встраиваемые в классы-сущности, организованы в виде иерархии, то при их отображении в БД необходимо учитывать разветвлённость этой иерархии. Если иерархия никак не ветвится, то есть у каждого предка в цепочке наследования есть только один потомок, то ничего особого предпринимать не нужно, достаточно делать стандартную разметку аннотацией @Embeddable.

Если у какого-то из встраиваемых классов-предков более одного потомка, то есть иерархия наследования начинает ветвиться, то может понадобиться более тонкая разметка классов аннотациями.

Подготовка

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

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

spring.jpa.hibernate.ddl-auto=update

Код

Создадим абстрактный класс-предок, от которого будут наследоваться конкретные классы:

Мы помечаем класс-предок аннотацией @MappedSuperclass, чтобы указать Hibernate’у, что отображаться в таблицы по тем или иным правилам будут именно наследники этого класса, а не он сам. Все правила отображения будут заданы в наследниках.

Создадим классы-наследники, которые будут встраиваться в сущности, и поля которых будут отображаться в таблицах сущностей (каждый в своём файле):

Классы RussianTranslation и EnglishTranslation наследуют объявленный выше класс AbstractName и помечаются аннотацией @Embeddable, что указывает Hibernate’у, что у этих классов не будет соответствующих таблиц, а данные этих классов будут храниться в таблицах, созданных для классов-сущностей, которые содержат поля типа RussianTranslation или EnglishTranslation.

Аннотации @Embeddable было бы достаточно, если бы мы были уверены, что не окажется ни одного класса-сущности, который бы содержал одновременно и поле RussianTranslation, и поле EnglishTranslation. Но именно такой класс-сущность мы и создадим далее. И в принципе стоит закладываться на то, что по мере развития модели предметной области могут появляться и одновременно использоваться всевозможные наследники тех или иных классов.

Поэтому в нашем случае классы RussianTranslation и EnglishTranslation дополнительно размечены аннотациями @AttributeOverride, в которых переопределяются имена колонок, создаваемых для отображения данных полей родительского класса. Если этого не сделать, приложение не сможет подняться.

Создадим класс-сущность, в который и будут встраиваться классы-значения RussianTranslation и EnglishTranslation:

Создадим репозиторий к этому классу:

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

Тест создаёт сущность Person, предварительно снабдив её данными встраиваемых классов. Затем сохраняет данные в БД и тут же извлекает их. Убеждается, что сохранявшиеся данные идентичны извлечённым.

Посмотрим на схему БД, которую создал Hibernate на основе наших аннотаций:

Как мы и ожидали, у нас одна сущность — Person и в БД создана только одна таблица. Данные обоих встраиваемых классов RussianTranslation и EnglishTranslation хранятся в одной строке этой таблицы. И у RussianTranslation, и у EnglishTranslation есть поля firstName и lastName, унаследованные от предка AbstractName. Но внутри этой таблицы никаких конфликтов имён нет (и вообще всё прекрасно запустилось и работает) благодаря тому, что мы переопределили имена колонок, создаваемых для этих двух полей у каждого из потомков:

  • firstName и lastName класса RussianTranslation называются FIRST_NAME_IN_RUSSIAN и LAST_NAME_IN_RUSSIAN.
  • firstName и lastName класса EnglishTranslation соответственно называются FIRST_NAME_IN_ENGLISH и LAST_NAME_IN_ENGLISH.

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