Рубрики
Без рубрики

Лучший способ сопоставить столбец @DiscriminatorColumn с JPA и гибернацией

Автор оригинала: Vlad Mihalcea.

Как объяснялось ранее , наследование по одной таблице является наиболее эффективной стратегией наследования сущностей.

Однако для запроса JPQL, такого как этот:

List posts = entityManager
.createQuery(
    "select p " +
    "from Post p " +
    "where p.board = :board", Post.class)
.setParameter("board", board)
.getResultList();

Hibernate генерирует SQL-запрос, который фильтруется по соответствующему столбцу дискриминатора (например, ВВЕДИТЕ по умолчанию):

SELECT t.id AS id2_1_,
       t.board_id AS board_id8_1_,
       t.createdOn AS createdO3_1_,
       t.owner AS owner4_1_,
       t.title AS title5_1_,
       t.content AS content6_1_
FROM   topic t
WHERE  t.DTYPE = 'Post'
       AND t.board_id = 1

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

Однако по умолчанию СТРОКА DiscriminatorType ожидает столбец VARCHAR , который должен содержать самое длинное имя подкласса сущности. Для класса Объявление нам нужно не менее 12 байт для хранения имени класса сущности, в то время как для объекта Сообщение требуется 4 байта.

Если столбец типа дискриминатора проиндексирован и мы храним 1 миллион Объявление и 100 миллионов Post сущности, для индекса потребуется 393 МБ (12 + 400 миллионов байт). С другой стороны, если столбец дискриминатора имеет значение TINYINT (для хранения значения дискриминатора требуется только 1 байт), нам нужно всего 96 МБ (1 + 100 миллионов байт).

В этой статье я собираюсь объяснить, как вы можете получить максимальную отдачу от ЦЕЛОГО числа Дискриминантный тип при сохранении описательности СТРОКИ по умолчанию Дискриминантный тип .

Учитывая, что в нашей системе есть следующие сущности:

Иерархия классов Тема , Сообщение и Объявление отображается в одной таблице базы данных:

Сущность Тема базового класса отображается следующим образом:

@Entity(name = "Topic")
@Table(name = "topic")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
    discriminatorType = DiscriminatorType.INTEGER,
    name = "topic_type_id",
    columnDefinition = "TINYINT(1)"
)
public class Topic {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    private String owner;

    @Temporal(TemporalType.TIMESTAMP)
    private Date createdOn = new Date();

    @ManyToOne(fetch = FetchType.LAZY)
    private Board board;

    //Getters and setters omitted for brevity
}

Обратите внимание на аннотацию @DiscriminatorColumn , которая объявляет, что в столбце topic_type_id ожидается ЦЕЛОЕ число дискриминатор, который имеет тип TINYINT(1) .

В то время как для дискриминатора по умолчанию STRING вам не нужно указывать определенное значение для каждой сущности подкласса, поскольку используется имя класса, для типа INTEGER дискриминатора каждая сущность подкласса должна предоставлять @Значение дискриминатора с уникальным целочисленным значением.

Подкласс Post будет использовать значение 1 в столбце topic_type_id :

@Entity(name = "Post")
@Table(name = "post")
@DiscriminatorValue("1")
public class Post extends Topic {

    private String content;

    //Getters and setters omitted for brevity
}

В то время как Объявление подкласс будет использовать значение 2 в столбце topic_type_id :

@Entity(name = "Announcement")
@Table(name = "announcement")
@DiscriminatorValue("2")
public class Announcement extends Topic {

    @Temporal(TemporalType.TIMESTAMP)
    private Date validUntil;

    //Getters and setters omitted for brevity
}

При вставке одного Сообщение и Объявление юридическое лицо, а также:

Post post = new Post();
post.setOwner("John Doe");
post.setTitle("Inheritance");
post.setContent("Best practices");
post.setBoard(board);

entityManager.persist(post);

Announcement announcement = new Announcement();
announcement.setOwner("John Doe");
announcement.setTitle("Release x.y.z.Final");
announcement.setValidUntil(
    Timestamp.valueOf(
        LocalDateTime.now().plusMonths(1)
    )
);
announcement.setBoard(board);

entityManager.persist(announcement);

Hibernate создает следующие инструкции SQL:

Query:["insert into topic (board_id, createdOn, owner, title, content, topic_type_id, id) values (?, ?, ?, ?, ?, 1, ?)"], 
Params:[(1, 2017-06-02 16:30:35.963, John Doe, Inheritance, Best practices, 2)]

Query:["insert into topic (board_id, createdOn, owner, title, validUntil, topic_type_id, id) values (?, ?, ?, ?, ?, 2, ?)"], 
Params:[(1, 2017-06-02 16:30:35.974, John Doe, Release x.y.z.Final, 2017-07-02 16:30:35.98, 3)]

Обратите внимание на литеральные значения 1 и 2 в выполненной инструкции SQL INSERT.

Даже если тип ЦЕЛОЕ ЧИСЛО дискриминатор намного компактнее, чем его аналог СТРОКА , ему, тем не менее, не хватает выразительности, потому что, когда вы видите значение 2, вы автоматически не думаете, что оно представляет Объявление строку.

Итак, как мы можем это исправить?

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

Мы можем инициализировать topic_type следующим образом:

INSERT INTO topic_type (
    description, 
    name, 
    id
) 
VALUES (
    'Post is a subclass of the Topic base class', 
    'com.vladmihalcea.book.hpjp.hibernate.inheritance.discriminator.Post', 
    1
)

INSERT INTO topic_type (
    description, 
    name, 
    id
) 
VALUES (
    'Announcement is a subclass of the Topic base class', 
    'com.vladmihalcea.book.hpjp.hibernate.inheritance.discriminator.Announcement', 
    2
)

Теперь, когда нам нужно найти тип данной темы записи, вам просто нужно соединить ее с таблицей topic_type :

SELECT 
    tt.name,
    t.id,
    t.createdOn,
    t.owner,
    t.title,
    t.content,
    t.validUntil,
    t.board_id
FROM topic t
INNER JOIN topic_type tt ON t.topic_type_id = tt.id

Вот и все!

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

Хотя по умолчанию СТРОКА Тип дискриминатора очень удобен, гораздо лучше использовать ЦЕЛОЧИСЛЕННЫЙ тип дискриминатора. Чтобы уменьшить недостаток выразительности, вы можете добавить новую таблицу для хранения описания для каждого конкретного значения типа дискриминатора.