Автор оригинала: Vlad Mihalcea.
Вступление
В этой статье я собираюсь объяснить, почему платформа Blaze Persistence предоставляет лучший API для написания запросов по критериям JPA.
Blaze Persistence-это очень продвинутая система доступа к данным , поддерживаемая Кристианом Бейковым , который также является Участник проекта Hibernate ORM Blaze Persistence is a very advanced data access framework maintained by Christian Beikov
Я впервые услышал о постоянстве Blaze во время интервью , которое Кристиан дал нам для форума Hibernate , и если вы используете JPA и Hibernate, вам определенно следует также использовать постоянство Blaze.
Модель предметной области
Давайте предположим, что у нас есть родительская post
таблица, которая имеет отношение один ко многим таблицам с post_comment
дочерней таблицей.
Таблица post
содержит одну запись:
| id | title | |----|-----------------------------------| | 1 | High-Performance Java Persistence |
И post_comment
дочерняя таблица содержит три строки, которые связаны с одной записью post
:
| id | review | post_id | |----|---------------------------------------|---------| | 1 | Best book on JPA and Hibernate! | 1 | | 2 | A great reference book. | 1 | | 3 | A must-read for every Java developer! | 1 |
Получение поста с последним комментарием
Теперь нам нужен отчет, который предоставит нам информацию о посте вместе с его последней версией post_comment
, и для этой цели мы можем использовать следующий SQL-запрос:
SELECT p.id AS post_id, p.title AS post_title, pc2.review AS comment_review FROM ( SELECT pc1.id AS id, pc1.review AS review, pc1.post_id AS post_id, MAX(pc1.id) OVER (PARTITION BY pc1.post_id) AS max_id FROM post_comment pc1 ) pc2 JOIN post p ON p.id = pc2.post_id WHERE pc2.id = pc2.max_id
При выполнении приведенного выше SQL-запроса мы получаем ожидаемый набор результатов:
| post_id | post_title | comment_review | |---------|-----------------------------------|---------------------------------------| | 1 | High-Performance Java Persistence | A must-read for every Java developer! |
Критерии JPA стойкости Blaze
Теперь, если мы хотим динамически генерировать вышеупомянутый SQL-запрос, мы не можем использовать API критериев JPA, поскольку он не поддерживает оконные функции или производные таблицы.
К счастью, не только постоянство Blaze может помочь нам сгенерировать этот запрос, но и для Oracle, PostgreSQL, MySQL или SQL Server.
Первым шагом является создание Фабрики построителя критериев
с использованием существующей EntityManagerFactory
ссылки на объект:
CriteriaBuilderFactory cbf = Criteria .getDefault() .createCriteriaBuilderFactory( entityManagerFactory );
И запрос критериев будет выглядеть следующим образом:
Listtuples = cbf .create(entityManager, Tuple.class) .fromSubquery(PostCommentMaxIdCTE.class, "pc2") .from(PostComment.class, "pc1") .bind("id").select("pc1.id") .bind("review").select("pc1.review") .bind("postId").select("pc1.post.id") .bind("maxId").select("MAX(pc1.id) OVER (PARTITION BY pc1.post.id)") .end() .joinOn(Post.class, "p", JoinType.INNER) .onExpression("p.id = pc2.postId").end() .where("pc2.id").eqExpression("pc2.maxId") .select("p.id", "post_id") .select("p.title", "post_title") .select("pc2.review", "comment_review") .getResultList();
Обратите внимание, насколько похож API критериев сохранения Blaze по сравнению с исходным SQL-запросом.
При запросе сущностей JPA Blaze Persistence может использовать метамодель сущностей JPA для создания SQL-запроса, но для Производной таблицы, которая строит проекцию с помощью вызова функции MAX
Window, нам необходимо предоставить структуру CTE, которая определяет Метамодель для базовой проекции.
По этой причине мы создали класс Post Comment Max Id CTE
, который содержит ps2
Проекция производной таблицы:
@CTE @Entity public class PostCommentMaxIdCTE { @Id private Long id; private String review; private Long postId; private Long maxId; }
Структуры CTE должны быть зарегистрированы в JPA таким же образом, как и юридические лица. Итак, если вы используете Spring Boot, вы можете использовать аннотацию @EntityScan
для ссылки на пакет, в котором расположены классы CTE Blaze Persistence:
@EntityScan("com.vladmihalcea.hpjp.jpa.repository.cte")
И при выполнении приведенного выше запроса критериев сохранения Blaze в PostgreSQL выполняется следующий SQL-запрос:
SELECT blazepersi1_.id AS col_0_0_, blazepersi1_.title AS col_1_0_, blazepersi0_.review AS col_2_0_ FROM ( SELECT blazepersi0_.id AS col_0_0_, blazepersi0_.review AS col_1_0_, blazepersi0_.post_id AS col_2_0_, MAX(blazepersi0_.id) OVER ( PARTITION BY blazepersi0_.post_id ) AS col_3_0_ FROM post_comment blazepersi0_ ) blazepersi0_(id, review, postid, maxid) INNER JOIN post blazepersi1_ ON ( (NULL IS NULL) AND blazepersi1_.id = blazepersi0_.postid ) WHERE blazepersi0_.id = blazepersi0_.maxid
И при выполнении того же запроса критериев JPA для сохранения Blaze в MySQL генерируется следующий SQL-запрос:
SELECT blazepersi1_.id AS col_0_0_, blazepersi1_.title AS col_1_0_, blazepersi0_.review AS col_2_0_ FROM ( SELECT NULL id, NULL review, NULL postId, NULL maxId FROM dual WHERE 1 = 0 UNION ALL ( SELECT blazepersi0_.id AS col_0_0_, blazepersi0_.review AS col_1_0_, blazepersi0_.post_id AS col_2_0_, MAX(blazepersi0_.id) OVER ( PARTITION BY blazepersi0_.post_id ) AS col_3_0_ FROM post_comment blazepersi0_ ) ) blazepersi0_ INNER JOIN post blazepersi1_ ON ( ( NULL IS NULL ) AND blazepersi1_.id = blazepersi0_.postid ) WHERE blazepersi0_.id = blazepersi0_.maxid
Круто, правда?
Вывод
Сохраняемость Blaze-очень полезное дополнение для JPA и Hibernate, поскольку оно позволяет создавать динамические запросы, которые могут использовать нетривиальные функции SQL, такие как:
- CTE
- Рекурсивный CTE
- Функции окна
- Боковые Соединения
И, поскольку сгенерированные SQL-запросы создаются на основе базового диалекта Hibernate, нам не нужно беспокоиться о переносимости запросов в случае, если нашему приложению необходимо поддерживать несколько систем реляционных баз данных.