Hypersistence Optimizer Runtime Scanner
Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Wouldn’t that be just awesome?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.
So, enjoy spending your time on the things you love rather than fixing performance issues in your production system on a Saturday night!
The 2.0 version of the Hypersistence Optimizer has just arrived, and it comes with a runtime scanner that is capable of analyzing your JPA and Hibernate data access operations and queries and give you tips about how you can speed up your application.
Along with the JPA and Hibernate mapping and configuration scanners, the runtime scanner makes Hypersistence Optimizer an indispensable tool for building High-Performance Java Persistence applications.
Decorating the Persistence Unit
To be able to intercept JPA
EntityManager and Hibernate
Session data access operations, the Persistence Unit (e.g.,
SessionFactory) needs to be decorated with runtime scanning capabilities.
Implementing the runtime scanner required a lot of work, but I’m glad I managed to bring you this awesome functionality.
The amount of work needed to add support for runtime scanning in @Hypersistence Optimizer.— Vlad Mihalcea (@vlad_mihalcea) February 6, 2020
Looking forward to finishing the remaining items scheduled for the 2.0 release.
Stay tuned!https://t.co/q9TnXs1H1p pic.twitter.com/pryZ71hZpx
The runtime scanner is able to intercept any type of query you are executing, from JPQL to Criteria API, legacy Criteria, and native SQL queries.
For instance, when executing the following JPQL query:
List<Post> posts = entityManager .createQuery( "select p " + "from Post p ", Post.class) .setMaxResults(5) .getResultList();
Hypersistence Optimizer will generate the following event:
ERROR [main]: Hypersistence Optimizer - CRITICAL - PaginationWithoutOrderByEvent - The [select p from Post p ] query uses pagination without an ORDER BY clause. Therefore, the result is not deterministic since SQL does not guarantee any particular ordering unless an ORDER BY clause is being used.
We forgot to add an ORDER BY clause, and, for this reason, the offset pagination result set would not be deterministic.
Or, when running the following Criteria API:
CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery<Post> criteria = builder.createQuery(Post.class); Root<Post> post = criteria.from(Post.class); post.fetch("comments"); criteria.select(post).distinct(true); List<Post> posts = entityManager .createQuery(criteria) .getResultList();
Hypersistence Optimizer will spot that you forgot to provide the
PASS_DISTINCT_THROUGH query hint:
ERROR [main]: Hypersistence Optimizer - CRITICAL - PassDistinctThroughEvent - The [ select distinct p from Post as p inner join fetch p.comments as pc ] query uses DISTINCT to deduplicate the returned entities. However, without setting the [hibernate.query.passDistinctThrough] JPA query hint to [false], the underlying SQL statement will also contain DISTINCT, which will incur extra sorting and duplication removal execution stages.
Analyzing the Persistence Context
As I explained in this article, when bootstrapping Hibernate natively like it’s the case when using the Spring
SessionFactoryBean, the Hibernate
Session is not flushed automatically prior to executing a native SQL query.
So, when counting the
post table records with the following SQL query, we can see that the
postCount value is going to be
Post post = new Post(); post.setTitle("High-Performance Java Persistence"); entityManager.persist(post); int postCount = ( (Number) session .createSQLQuery( "SELECT count(*) " + "FROM post ") .uniqueResult() ) .intValue(); assertEquals(0, postCount);
Hypersistence Optimizer will trigger the following event when executing the above query:
ERROR [main]: Hypersistence Optimizer - CRITICAL - FlushModeAutoEvent - The Hibernate Session uses the legacy [FlushMode.AUTO] strategy, that does not guarantee read-your-writes consistency for native SQL queries. When using the default [FlushMode.AUTO], Hibernate does not flush the Persistence Context prior to executing an SQL query, so the pending entity changes will not be visible to the query execution. Consider setting the current Session or Query [flushMode] property to the value of [ALWAYS], or add the [org.hibernate.flushMode] Hibernate configuration setting with the value of [always].
Simplifying the event retrieval
Previously, to get the list of
Event objects that were triggered by Hypersistence Optimizer, you had to provide a
ListEventHandler. Since version 2.0, you can fetch all events like this:
List<Event> events = hypersistenceOptimizer.getEvents();
Enabling and disabling scanners
You can now enable or disable the mapping, configuration, and runtime scanner from the
HibernateConfig instances that you are passing to the
This mechanism will give you better control over what needs to be scanned when you cannot use a singleton
HypersistenceOptimizer instance for all integration tests.
HTML and PDF documentation
The assembly pack you are downloading from the product pack contains the Installation and User Guides in both HTML and PDF formats.
This will allow you to read the documentation when you are offline or print the supported event list, which is a very valuable resource in itself.
I'm running an online workshop on the 11th of October about High-Performance SQL.
The 2.0 version is only the beginning. I’m looking forward to adding support for detecting slow queries, queries that fetch way too much data, and Persistence Context instance fetching entities in the read-write mode without applying any modification.
Enjoy running your Java database application at high speed, and stay tuned for more!