Автор оригинала: Vlad Mihalcea.
Каждый запрос JPQL должен быть скомпилирован перед выполнением, и, поскольку этот процесс может быть ресурсоемким, Hibernate предоставляет QueryPlanCache
для этой цели.
Для запросов сущностей представление запроса String
анализируется в AST (Абстрактное синтаксическое дерево). Для собственных запросов этап синтаксического анализа не может скомпилировать запрос, поэтому он извлекает только информацию об именованных параметрах и типе возвращаемого запроса.
Руководство для начинающих по JPQL в режиме гибернации и встроенному хранилищу запросов @vlad_mihalcea https://t.co/9vf3a4Ty5V pic.twitter.com/mhTDFM9Ifr
Кэш плана запросов является общим как для сущностных, так и для собственных запросов, и его размер регулируется следующим свойством конфигурации:
По умолчанию QueryPlanCache
хранит 2048 планов, которых может быть недостаточно для крупных корпоративных приложений.
Для собственных запросов QueryPlanCache
хранит также ParameterMetadata
, в котором содержится информация об имени параметра, положении и соответствующем типе гибернации. Кэш ParameterMetadata
управляется с помощью следующего свойства конфигурации:
Если приложение выполняет больше запросов, чем может вместить QueryPlanCache
, из – за компиляции запроса произойдет снижение производительности.
Предполагая, что в нашем приложении есть следующие объекты:
@Entity(name = "Post") @Table(name = "post") public class Post { @Id private Long id; private String title; @OneToMany( mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true ) private Listcomments = new ArrayList<>(); public void addComment(PostComment comment) { comments.add(comment); comment.setPost(this); } //Getters and setters omitted for brevity } @Entity(name = "PostComment") @Table(name = "post_comment") public class PostComment { @Id private Long id; @ManyToOne(fetch = FetchType.LAZY) private Post post; private String review; //Getters and setters omitted for brevity }
Мы собираемся измерить фазу компиляции для следующих JPQL и собственных запросов:
protected Query getEntityQuery1(EntityManager entityManager) { return entityManager.createQuery(""" select new com.vladmihalcea.book.hpjp.hibernate.fetching.PostCommentSummary( p.id, p.title, c.review ) from PostComment c join c.post p """) .setFirstResult(10) .setMaxResults(20) .setHint(QueryHints.HINT_FETCH_SIZE, 20); } protected Query getEntityQuery2(EntityManager entityManager) { return entityManager.createQuery(""" select c from PostComment c join fetch c.post p where p.title like :title """ ); } protected Query getNativeQuery1(EntityManager entityManager) { return entityManager.createNativeQuery(""" select p.id, p.title, c.review * from post_comment c join post p on p.id = c.post_id """) .setFirstResult(10) .setMaxResults(20) .setHint(QueryHints.HINT_FETCH_SIZE, 20); } protected Query getNativeQuery2(EntityManager entityManager) { return entityManager.createNativeQuery(""" select c.*, p.* from post_comment c join post p on p.id = c.post_id where p.title like :title """) .unwrap(NativeQuery.class) .addEntity(PostComment.class) .addEntity(Post.class); }
Измерения будут проводиться следующим образом:
protected void compileQueries( Functionquery1, Function query2) { LOGGER.info("Warming up"); doInJPA(entityManager -> { for (int i = 0; i < 10000; i++) { query1.apply(entityManager); query2.apply(entityManager); } }); LOGGER.info( "Compile queries for plan cache size {}", planCacheMaxSize ); doInJPA(entityManager -> { for (int i = 0; i < 2500; i++) { long startNanos = System.nanoTime(); query1.apply(entityManager); timer.update( System.nanoTime() - startNanos, TimeUnit.NANOSECONDS ); startNanos = System.nanoTime(); query2.apply(entityManager); timer.update( System.nanoTime() - startNanos, TimeUnit.NANOSECONDS ); } }); logReporter.report(); }
И методы тестирования JUnit могут просто вызывать компиляционные запросы
метод, подобный этому:
@Test public void testEntityQueries() { compileQueries( this::getEntityQuery1, this::getEntityQuery2 ); } @Test public void testNativeQueries() { compileQueries( this::getNativeQuery1, this::getNativeQuery2 ); }
Размер кэша плана будет изменяться с помощью функции @Параметризованный
JUnit:
private final int planCacheMaxSize; public PlanCacheSizePerformanceTest( int planCacheMaxSize) { this.planCacheMaxSize = planCacheMaxSize; } @Parameterized.Parameters public static CollectionrdbmsDataSourceProvider() { List planCacheMaxSizes = new ArrayList<>(); planCacheMaxSizes.add(new Integer[] {1}); planCacheMaxSizes.add(new Integer[] {100}); return planCacheMaxSizes; } @Override protected void additionalProperties( Properties properties) { properties.put( "hibernate.query.plan_cache_max_size", planCacheMaxSize ); properties.put( "hibernate.query.plan_parameter_metadata_max_size", planCacheMaxSize ); }
Таким образом, мы изменим размер кэша QueryPlanCache
и ParameterMetadata
от 1 до 100. Когда размер кэша плана равен 1, запросы всегда будут компилироваться, в то время как при размере кэша плана 100 планы запросов будут обслуживаться из кэша.
При выполнении вышеупомянутых модульных тестов мы получим следующие результаты.
Производительность кэша плана запросов к сущностям JPQL
Как вы можете ясно видеть, запросы сущностей JPQL могут значительно улучшить кэш плана запросов, и именно поэтому вам следует убедиться, что находится в спящем режиме.query.plan_cache_max_size
может вместить подавляющее большинство запросов сущностей, необходимых для запуска вашего приложения.
QueryPlanCache
влияет как на запросы JPQL, так и на запросы API критериев, поскольку запросы критериев переводятся в JPQL.
Производительность кэша собственного плана запросов
Хотя это и не так впечатляюще, как для запросов JPQL, кэш плана запросов также может ускорить собственные запросы, поэтому убедитесь, что вы установили режим гибернации.запрос.plan_parameter_metadata_max_size
право собственности на конфигурацию.
Очевидного повышения производительности при использовании именованных запросов по сравнению с динамическими нет, поскольку за кулисами именованный запрос может кэшировать только свое определение (например, NamedQueryDefinition
), а фактический кэш плана запроса доступен как для динамических, так и для именованных запросов.
Наиболее важные параметры, которые необходимо принять во внимание, – это те, которые управляют кэшем плана запросов Hibernate.
Для запросов сущностей кэш плана действительно может повлиять на производительность. Для собственных запросов выигрыш менее значителен.
Кэш плана, в котором хранятся как сущностные, так и собственные запросы, важно настроить его размер таким образом, чтобы он мог вместить все выполняемые запросы. В противном случае некоторые запросы сущностей, возможно, придется перекомпилировать, что приведет к увеличению времени ответа на текущую транзакцию.