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

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

@MappedSuperclass

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

/**
* Common 'id' part of all entities.
*/
@SuppressWarnings("PMD")
@MappedSuperclass
@ToString
public abstract class AbstractIdentifiableObject {
    /**
     * Common id field.
     */
    @Id
    @GeneratedValue
    @Getter
    @Setter
    private Long id;
}
/**
* Single financial operation.
*/
@SuppressWarnings("PMD")
@ToString
@Entity
@Table(name = "journal")
public class Operation extends AbstractIdentifiableObject {
    /**
     * Operation's account.
     */
    @Getter
    @Setter
    @Column(nullable = false, updatable = false)
    private Integer accountId;


    /**
     * Operation's amount.
     */
    @Getter
    @Setter
    @Column(nullable = false, updatable = false, scale = 2, precision = 10)
    private BigDecimal amount;
}

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

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

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

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

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

@SuppressWarnings("PMD")
@Entity
@Inheritance
@DiscriminatorColumn
@ToString
public abstract class AbstractNamedObject extends AbstractIdentifiableObject {
    /**
     * The Name.
     */
    @Getter
    @Setter
    private String name;
}

534534

@SuppressWarnings("PMD")
@Entity
@DiscriminatorValue("PRODUCT")
@ToString
public class Product extends AbstractNamedObject {
    /**
     * Category, to which this product belongs.
     */
    @Getter
    @Setter
    private Long productCategoryId;
}
@SuppressWarnings("PMD")
@Entity
@DiscriminatorValue("SERVICE")
@ToString
public class Service extends AbstractNamedObject {
    /**
     * Service group, to which that service belongs.
     */
    @Getter
    @Setter
    private Long serviceGroup;
}

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

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

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

@SuppressWarnings("PMD")
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Table(name = "descriptions")
@ToString
public abstract class ObjectWithDescription extends AbstractIdentifiableObject {
    /**
     * Description text.
     */
    @Getter
    @Setter
    private String description;
}
@SuppressWarnings("PMD")
@Entity
@ToString
public class ProductCategory extends ObjectWithDescription {
    /**
     * Some human friendly category code.
     */
    @Getter
    @Setter
    private String code;
}
@SuppressWarnings("PMD")
@Entity
@ToString
public class ServiceGroup extends ObjectWithDescription {
    /**
     * Some human friendly group code.
     */
    @Getter
    @Setter
    private String code;
}

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

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

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

@SuppressWarnings("PMD")
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@ToString
public class Cargo extends AbstractIdentifiableObject {
    /**
     * Cargo weight in kg.
     */
    @Getter
    @Setter
    private Long weight;
}
@SuppressWarnings("PMD")
@Entity
@ToString
public class LiquidCargo extends Cargo {
    /**
     * Cargo volume.
     */
    @Getter
    @Setter
    private Long volume;


    /**
     * Liquid type.
     */
    @Getter
    @Setter
    private String liquidType;
}
@SuppressWarnings("PMD")
@Entity
@ToString
public class PackedCargo extends Cargo {
    /**
     * Square box width.
     */
    @Getter
    @Setter
    private Long width;


    /**
     * Box height.
     */
    @Getter
    @Setter
    private Long height;
}

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

Скачать код примера

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *