Автор оригинала: 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, нам не нужно беспокоиться о переносимости запросов в случае, если нашему приложению необходимо поддерживать несколько систем реляционных баз данных.