Автор оригинала: Vlad Mihalcea.
Вступление
Каждый запрос сущности, будь то JPQL или API критериев, должен быть проанализирован и скомпилирован в AST (Абстрактное синтаксическое дерево) для создания соответствующего SQL-запроса. Компиляция запроса сущности требует времени, как описано в этой статье поэтому Hibernate предоставляет QueryPlanCache
для хранения уже скомпилированных планов.
Начиная с версии Hibernate 5.4, механизм Hibernate Статистика
позволяет отслеживать кэш плана запроса, и в этой статье будет показано, как воспользоваться этой функцией для ускорения выполнения запросов.
Для ознакомления с механизмом статистики гибернации ознакомьтесь с этой статьей .
Как оптимизировать планы запросов JPQL и API критериев с помощью #Hibernate Статистики @vlad_mihalcea https://t.co/bHfkKKCGOn pic.twitter.com/1k2PUeVpb7
Модель предметной области
Предполагая, что у нас есть следующая Должность
сущность:
Который отображается следующим образом:
@Entity(name = "Post") @Table(name = "post") public class Post { @Id private Integer id; private String title; //Getters and setters omitted for brevity }
JPQL В запросе
Теперь давайте посмотрим, что происходит, когда Hibernate выполняет запрос JPQL, содержащий предложение IN:
SessionFactory sessionFactory = entityManagerFactory() .unwrap(SessionFactory.class); Statistics statistics = sessionFactory.getStatistics(); statistics.clear(); doInJPA(entityManager -> { Listposts = entityManager.createQuery( "select p " + "from Post p " + "where p.id in :ids", Post.class) .setParameter("ids", Arrays.asList(1, 2, 3)) .getResultList(); }); for (String query : statistics.getQueries()) { LOGGER.info("Executed query: {}", query); }
Для проверки выполненных запросов сущностей мы можем использовать метод Statistics#getQueries
. Однако по умолчанию Hibernate не собирает никакой статистики, поэтому нам нужно включить эту функцию, установив для свойства hibernate.generate_statistics
configuration значение true
.
Теперь при выполнении приведенного выше запроса JPQL будут регистрироваться следующие запросы:
-- Executed query: select p from Post p where p.id in :ids -- Executed query: select p from Post p where p.id in (:ids_0, :ids_1, :ids_2)
Первый запрос-это тот, который мы предоставили, в то время как второй-расширенный запрос, поскольку JDBC не поддерживает привязку массива значений как отдельных В значениях параметров запроса.
API критериев В запросе
Теперь давайте переведем предыдущий запрос JPQL в его эквивалент API критериев:
SessionFactory sessionFactory = entityManagerFactory() .unwrap(SessionFactory.class); Statistics statistics = sessionFactory.getStatistics(); statistics.clear(); doInJPA(entityManager -> { CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuerycriteria = builder.createQuery(Post.class); Root fromPost = criteria.from(Post.class); criteria.where( builder.in( fromPost.get("id")).value(Arrays.asList(1, 2, 3) ) ); List posts = entityManager .createQuery(criteria) .getResultList(); }); for (String query : statistics.getQueries()) { LOGGER.info("Executed query: {}", query); }
Написание запросов API критериев JPA не очень просто. Плагин Codota IDE может помочь вам в написании таких запросов, что повысит вашу производительность.
Для получения более подробной информации о том, как вы можете использовать Codota для ускорения процесса написания запросов API критериев, ознакомьтесь с этой статьей .
При выполнении запроса API критериев Hibernate регистрирует следующие запросы исполняемых сущностей:
-- Executed query: select generatedAlias0 from Post as generatedAlias0 where generatedAlias0.id in (:param0) -- Executed query: select generatedAlias0 from Post as generatedAlias0 where generatedAlias0.id in (:param0_0, :param0_1, :param0_2)
Таким образом, каждый запрос сущности, будь то JPQL или API критериев, должен быть расширен, если он содержит предложение IN.
Кэш плана запроса и расширение предложения IN
Теперь, если количество параметров предложения IN изменяется, Hibernate придется скомпилировать значительное количество запросов сущностей, как показано в следующем примере:
SessionFactory sessionFactory = entityManagerFactory() .unwrap(SessionFactory.class); Statistics statistics = sessionFactory.getStatistics(); statistics.clear(); doInJPA(entityManager -> { for (int i = 1; i < 16; i++) { getPostByIds( entityManager, IntStream .range(1, i + 1) .boxed() .toArray(Integer[]::new) ); } }); assertEquals( 16L, statistics.getQueryPlanCacheMissCount() ); for (String query : statistics.getQueries()) { LOGGER.info("Executed query: {}", query); }
При выполнении приведенного выше тестового случая Hibernate генерирует следующие выходные данные:
--Executed query: select p from Post p where p.id in :ids --Executed query: select p from Post p where p.id in (:ids_0) --Executed query: select p from Post p where p.id in (:ids_0, :ids_1) --Executed query: select p from Post p where p.id in (:ids_0, :ids_1, :ids_2) --Executed query: select p from Post p where p.id in (:ids_0, :ids_1, :ids_2, :ids_3) --Executed query: select p from Post p where p.id in (:ids_0, :ids_1, :ids_2, :ids_3, :ids_4) --Executed query: select p from Post p where p.id in (:ids_0, :ids_1, :ids_2, :ids_3, :ids_4, :ids_5) --Executed query: select p from Post p where p.id in (:ids_0, :ids_1, :ids_2, :ids_3, :ids_4, :ids_5, :ids_6) --Executed query: select p from Post p where p.id in (:ids_0, :ids_1, :ids_2, :ids_3, :ids_4, :ids_5, :ids_6, :ids_7) --Executed query: select p from Post p where p.id in (:ids_0, :ids_1, :ids_2, :ids_3, :ids_4, :ids_5, :ids_6, :ids_7, :ids_8) --Executed query: select p from Post p where p.id in (:ids_0, :ids_1, :ids_2, :ids_3, :ids_4, :ids_5, :ids_6, :ids_7, :ids_8, :ids_9) --Executed query: select p from Post p where p.id in (:ids_0, :ids_1, :ids_2, :ids_3, :ids_4, :ids_5, :ids_6, :ids_7, :ids_8, :ids_9, :ids_10) --Executed query: select p from Post p where p.id in (:ids_0, :ids_1, :ids_2, :ids_3, :ids_4, :ids_5, :ids_6, :ids_7, :ids_8, :ids_9, :ids_10, :ids_11) --Executed query: select p from Post p where p.id in (:ids_0, :ids_1, :ids_2, :ids_3, :ids_4, :ids_5, :ids_6, :ids_7, :ids_8, :ids_9, :ids_10, :ids_11, :ids_12) --Executed query: select p from Post p where p.id in (:ids_0, :ids_1, :ids_2, :ids_3, :ids_4, :ids_5, :ids_6, :ids_7, :ids_8, :ids_9, :ids_10, :ids_11, :ids_12, :ids_13) --Executed query: select p from Post p where p.id in (:ids_0, :ids_1, :ids_2, :ids_3, :ids_4, :ids_5, :ids_6, :ids_7, :ids_8, :ids_9, :ids_10, :ids_11, :ids_12, :ids_13, :ids_14)
Таким образом, хотя один и тот же JPQL выполняется несколько раз из-за расширения предложения IN, Hibernate генерирует 15 запросов JPQL, которые необходимо проанализировать и скомпилировать отдельно.
statistics.getQueryPlanCacheMissCount()
возвращает значение 16, которое включает исходный запрос JPQL и 15 запросов JPQL, полученных после расширения предложения IN. По этой причине Кэш плана запроса не помогает в этой ситуации.
Заполнение параметров в предложении
Как уже объяснялось в этой статье , Hibernate предлагает функцию заполнения параметров в предложении, которая может помочь нам уменьшить количество пропусков в кэше плана запроса, которое мы видели ранее.
Чтобы включить заполнение параметров в предложении, необходимо включить следующее свойство конфигурации:
После включения спящий режим.query.in_clause_parameter_padding
свойство, мы можем ожидать всего 6 расширений запросов В предложении:
Statistics statistics = sessionFactory.getStatistics(); statistics.clear(); doInJPA(entityManager -> { for (int i = 1; i < 16; i++) { getPostByIds( entityManager, IntStream .range(1, i + 1) .boxed() .toArray(Integer[]::new) ); } }); assertEquals( 6L, statistics.getQueryPlanCacheMissCount() ); for (String query : statistics.getQueries()) { LOGGER.info("Executed query: {}", query); }
При запуске приведенного выше тестового примера мы действительно видим, что было выполнено только 6 расширений запросов:
-- Executed query: select p from Post p where p.id in :ids -- Executed query: select p from Post p where p.id in (:ids_0) -- Executed query: select p from Post p where p.id in (:ids_0, :ids_1) -- Executed query: select p from Post p where p.id in (:ids_0, :ids_1, :ids_2, :ids_3) -- Executed query: select p from Post p where p.id in (:ids_0, :ids_1, :ids_2, :ids_3, :ids_4, :ids_5, :ids_6, :ids_7) -- Executed query: select p from Post p where p.id in (:ids_0, :ids_1, :ids_2, :ids_3, :ids_4, :ids_5, :ids_6, :ids_7, :ids_8, :ids_9, :ids_10, :ids_11, :ids_12, :ids_13, :ids_14, :ids_15)
Круто, правда?
Вывод
Таким образом, когда запросы сущностей JPQL или API критериев выполняются с переменным числом значений параметров привязки В предложении, вы можете повысить производительность приложения, включив режим гибернации.запрос.in_clause_parameter_padding
свойство конфигурации.
Кроме того, благодаря статистике кэша плана запросов , включенной в Hibernate 5.4 Статистика
, вы можете лучше понять, что делает Hibernate за кулисами.