Hypersistence Optimizer Runtime Scanner

Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. Hypersistence Optimizer is that tool!

Introduction

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., EntityManagerFactory or SessionFactory) needs to be decorated with runtime scanning capabilities.

The User Guide provides you with step-by-step instructions and references to existing demo applications on GitHub that will help you set up the new runtime scanner is no time.

Implementing the runtime scanner required a lot of work, but I’m glad I managed to bring you this awesome functionality.

Intercepting queries

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 0.

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].

Cool, right?

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 JpaConfig and HibernateConfig instances that you are passing to the HypersistenceOptimizer constructor.

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 repository 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.

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

The 2.0 version is only a beginning. I’m looking forward to adding support for detecting slow queries, queries that fetch way too much data, 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!

Transactions and Concurrency Control eBook

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.