Наследование в JPA

inheritanceКлассы в Java могут вступать в наследственные отношения и эти отношения должны как-то сохраняться и при переносе классов в базы данных, в которых наследования, за исключением некоторых реализаций, как бы и нет. предлагает целых четыре решения по заполнению этой пропасти между классами и таблицами.

@MappedSuperclass

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

Аннотация @MappedSuperclass позволяет включать класс и его jpa аннотации в производный класс, не делая базовый класс сущностью. Типичное использование в примере выше — абстрактный базовый класс, несущий в себе суррогатный первичный ключ.

В базе данных всё будет выглядеть, как если бы поля базового класса были определены непосредственно в производном классе.

mappedsuperclass

Одна таблица на все классы

Остальные стратегии JPA рассчитаны на сущности, которые унаследованы от других сущностей. Стратегия по умолчанию в этом случае — хранить все данные базового класса и всех его производных классов в одной таблице. В этой таблице будут созданые столбцы для всех возможных полей всех производных классов и отдельный столбец, в котором будет хранится признак класса, то есть метка, указывающая к какому конкретному классу относится эта строка.

Базовый класс аннотируется аннотацией @Inheritance и, по желанию, @DiscriminatorColumn, в параметрах которой можно задать наименование и тип столбца, в котором будут храниться признаки класса. К производным классам добавляется аннотация @DiscriminatorValue, задающее значение признака класса.

Этот подход хорош тем, что позволяет сравнительно быстро загружать объекты и, при этом, обрабатывать общие поля внешними средствами, без знания о структуре классов. С другой стороны, теряется возможность указывать not null ограничения для столбцов, могут быть проблемы с производительностью при изменении/добавлении строк, если на таблице придётся определять много индексов и некоторые базы данных не очень эффективно работают с длинными строками в таблицах. singletableinheritance

По таблице и join’у каждому классу

Другая стратегия — создавать для каждого производного класса свою собственную таблицу, в которой будут храниться его собственные поля. А поля базового класса — в собственной таблице базового класса. В этом случае в аннотации @Inheritance базового класса указывается другая стратегия strategy = InheritanceType.JOINED, а в производных классах указывать ничего не надо.

Этот подход решает проблемы предыдущего, с not null ограничениями и возможные переизбытком индексов, но за счёт выполнения сравнительно медленной операции join при чтении данных производного класса из базы. Раздельный доступ к данным базового и производных классов сохраняется.

joinedtables_basejoinedtables_productjoinedtable_service

Раздельные таблицы у каждого класса

И наконец последний вариант, когда каждый класс, и базовый и производные, получают по собственной таблице в которой есть всех их поля, а таблицы не связаны между собой. Для этого у базового класса в аннотации @Inheritance указывается стратегия strategy = InheritanceType.TABLE_PER_CLASS, а у производных классов опять ничего не указывается.

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

personaltable_cargopersonaltable_liquidcargopersonaltable_packedcargo

Код примера доступен на github.