Автор оригинала: 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);
Теперь требование состоит в том, чтобы слишком упорядочить объекты следующим образом:
Listtopics = 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.сопоставление.Подкласс.идентификатор подкласса
соответствует имени класса сущности в алфавитном порядке, это не очень надежно в долгосрочной перспективе.
Однако вы можете объявить свой собственный ЗАКАЗ ПО предложению и указать конкретные критерии заказа, как показано в следующем примере:
Listtopics = 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
, а также использовать выражения РЕГИСТРА, основанные на типе сущности.