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

Как упорядочить подклассы сущностей по их типу класса с помощью JPA и Hibernate

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

В этой статье мы рассмотрим, как упорядочивать подклассы сущностей при выполнении запроса JPQL с помощью Hibernate.

Предполагая, что у вас есть следующая модель отношений сущностей:

И, учитывая, что мы сохранили следующие сущности:

Board board = new Board();
board.setName("Hibernate");

entityManager.persist(board);

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);

Теперь требование состоит в том, чтобы слишком упорядочить объекты следующим образом:

List topics = entityManager
.createQuery(
    "select t " +
    "from Topic t " +
    "where t.board = :board " +
    "order by t.class", Topic.class)
.setParameter("board", board)
.getResultList();

assertEquals(2, topics.size());
assertTrue(topics.get(0) instanceof Announcement);
assertTrue(topics.get(1) instanceof Post);

Сопоставление для наследования SINGLE_TABLE выглядит следующим образом:

@Entity(name = "Topic")
@Table(name = "topic")
@Inheritance(
    strategy = InheritanceType.SINGLE_TABLE
)
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;

    //Gettters and setters omitted for brevity
}

@Entity(name = "Post")
public class Post extends Topic {

    private String content;

    //Gettters and setters omitted for brevity
}

@Entity(name = "Announcement")
public class Announcement extends Topic {

    @Temporal(TemporalType.TIMESTAMP)
    private Date validUntil;

    //Gettters and setters omitted for brevity
}

По умолчанию для наследования SINGLE_TABLE для различения подклассов будет использоваться столбец-дискриминатор DTYPE .

Следовательно, при выполнении запроса ORDER BY JPQL в режиме гибернации выполните следующий 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_,
       t.validUntil AS validUnt7_1_,
       t.DTYPE AS DTYPE1_1_
FROM topic t
WHERE t.board_id=?
ORDER BY t.DTYPE

Поэтому мы получаем ожидаемые результаты.

Однако, если мы переопределим стратегию наследования SINGLE_TABLE @DiscriminatorColumn и предоставим конкретный @DiscriminatorValue для каждого класса сущностей, тогда порядок будет соответствовать не имени класса, а тому, которое явно указано во время сопоставления сущностей.

При использовании стратегии ОБЪЕДИНЕННОГО наследования сопоставления сущностей выглядят следующим образом:

@Entity(name = "Topic")
@Table(name = "topic")
@Inheritance(strategy = InheritanceType.JOINED)
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;

    //Gettters and setters omitted for brevity
}

Схема таблицы для стратегии ОБЪЕДИНЕННОГО наследования выглядит следующим образом:

Поскольку на этот раз нет столбца дискриминатора, наш ЗАКАЗ ПО запросу не будет гарантированно работать, и Hibernate генерирует следующий SQL-запрос:

SELECT t.id AS id1_3_,
       t.board_id AS board_id5_3_,
       t.createdOn AS createdO2_3_,
       t.owner AS owner3_3_,
       t.title AS title4_3_,
       t1_.content AS content1_2_,
       t2_.validUntil AS validUnt1_0_,
       CASE
           WHEN t1_.id IS NOT NULL THEN 1
           WHEN t2_.id IS NOT NULL THEN 2
           WHEN t.id IS NOT NULL THEN 0
       END AS clazz_
FROM topic t
LEFT OUTER JOIN post t1_ ON t.id=t1_.id
LEFT OUTER JOIN announcement t2_ ON t.id=t2_.id
WHERE t.board_id=?
ORDER BY 
    CASE
        WHEN t1_.id IS NOT NULL THEN 1
        WHEN t2_.id IS NOT NULL THEN 2
        WHEN t.id IS NOT NULL THEN 0
    END

Виртуальный дискриминатор (например, clazz_ ) назначается во время начальной загрузки и зависит от того, как были загружены классы сущностей Hibernate. Хотя мы можем расположить объявления сущностей так, чтобы org.hibernate.сопоставление.Подкласс.идентификатор подкласса соответствует имени класса сущности в алфавитном порядке, это не очень надежно в долгосрочной перспективе.

Однако вы можете объявить свой собственный ЗАКАЗ ПО предложению и указать конкретные критерии заказа, как показано в следующем примере:

List topics = entityManager
.createQuery(
    "select t " +
    "from Topic t " +
    "where t.board = :board " +
    "order by " +
    "   case " +
    "   when type(t) = Announcement then 10" +
    "   when type(t) = Post then 20 " +
    "   end", Topic.class)
.setParameter("board", board)
.getResultList();

При выполнении приведенного выше запроса JPQL Hibernate создаст следующую инструкцию SQL:

SELECT t.id AS id1_3_,
       t.board_id AS board_id5_3_,
       t.createdOn AS createdO2_3_,
       t.owner AS owner3_3_,
       t.title AS title4_3_,
       t1_.content AS content1_2_,
       t2_.validUntil AS validUnt1_0_,
       CASE
           WHEN t1_.id IS NOT NULL THEN 1
           WHEN t2_.id IS NOT NULL THEN 2
           WHEN t.id IS NOT NULL THEN 0
       END AS clazz_
FROM topic t
LEFT OUTER JOIN post t1_ ON t.id=t1_.id
LEFT OUTER JOIN announcement t2_ ON t.id=t2_.id
WHERE t.board_id=?
ORDER BY 
    CASE
        WHEN 
            CASE
                WHEN t1_.id IS NOT NULL THEN 1
                WHEN t2_.id IS NOT NULL THEN 2
                WHEN t.id IS NOT NULL THEN 0
            END = 2 
        THEN 10
        WHEN 
            CASE
                WHEN t1_.id IS NOT NULL THEN 1
                WHEN t2_.id IS NOT NULL THEN 2
                WHEN t.id IS NOT NULL THEN 0
            END = 1 
        THEN 20
    END

Вы также можете использовать тот же трюк, когда хотите упорядочить подклассы сущностей по их имени сущности при использовании стратегии наследования SINGLE_TABLE с типом столбца ЦЕЛОЕ ЧИСЛО дискриминатор.

@Наследование(стратегия.ПРИСОЕДИНИЛСЯ) с @DiscriminatorClass

Чтобы указать конкретный столбец дискриминатора, нам необходимо указать @Класс дискриминатора в объявлении сопоставления сущностей суперкласса:

@Entity(name = "Topic")
@Table(name = "topic")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
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;

    //Gettters and setters omitted for brevity
}

Теперь таблица базового класса будет содержать столбец TYPE точно так же, как стратегия наследования сущностей SINGLE_TABLE :

Таким образом, при выполнении ЗАКАЗА ПО JPQL-запросу Hibernate сгенерирует следующий SQL-запрос:

SELECT t.id AS id2_3_,
       t.board_id AS board_id6_3_,
       t.createdOn AS createdO3_3_,
       t.owner AS owner4_3_,
       t.title AS title5_3_,
       t1_.content AS content1_2_,
       t2_.validUntil AS validUnt1_0_,
       t.DTYPE AS DTYPE1_3_
FROM topic t
LEFT OUTER JOIN post t1_ ON t.id=t1_.id
LEFT OUTER JOIN announcement t2_ ON t.id=t2_.id
WHERE t.board_id=?
ORDER BY t.DTYPE

Хотя обычно вам никогда не нужно было бы использовать @DiscriminatorColumn со стратегией ОБЪЕДИНЕННОГО наследования, в этом случае это единственный способ убедиться, что вы можете упорядочить подклассы сущностей по их имени класса.

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

Как неоднократно демонстрировалось в моем блоге, Hibernate очень гибок, когда дело доходит до удовлетворения различных требований к доступу к данным. В этой статье вы видели, что даже стратегия наследования JOINED может использовать столбец @DiscriminatorColumn , а также использовать выражения РЕГИСТРА, основанные на типе сущности.