Tuning Spring Petclinic JPA and Hibernate configuration with Hypersistence Optimizer

(Last Updated On: June 3, 2019)
Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. Hypersistence Optimizer is that tool!

Introduction

In this article, we are going to see how we can tune the performance of the Spring Petclinic application using Hypersistence Optimizer.

Now, while you can manually analyze your data access layer to make sure that JPA and Hibernate are properly configured, it’s much better if you can automate this task. That’s because new entities might be mapped in the future, and you want to make sure that the same performance-specific rules are consistently applied on every commit.

Hypersistence Optimizer allows you to automatically detect JPA and Hibernate issues during development, so you can optimize your application long before you launch it into production.

Spring Petclinic with Hypersistence Optimizer

Setting up Hypersistence Optimizer with Spring Petclinic

The Spring Petclinic project is a fully-functional application demonstrating Spring capabilities. It’s similar to the Java Pet Store application that’s been available since J2EE times.

After forking the Spring Petclinic, we need to add the Hypersistence Optimizer dependency to the pom.xml Maven configuration file.

<dependency>
    <groupId>io.hypersistence</groupId>
    <artifactId>hypersistence-optimizer</artifactId>
    <version>${hypersistence-optimizer.version}</version>
</dependency>

Now, we need to navigate to the spring-petclinic/src/test/java/org/springframework/samples/petclinic/ folder and open the PetclinicIntegrationTests.java test case.

The only thing we need to add to this test case is to inject the EntityManagerFcatory reference and add a init method that initializes Hypersistence Optimiser:

@PersistenceUnit
private EntityManagerFactory entityManagerFactory;

@Before
public void init() {
    new HypersistenceOptimizer(
        new JpaConfig(entityManagerFactory)
    ).init();
}

That’s it!

You can view the entire commit I’ve done on my fork and see for yourself how easy it is to add Hypersistence Optimizer to your application.

Testing time

With Hypersistence Optimizer in place, it’s time to run the PetclinicIntegrationTests and check the application log which looks as follows:

Hypersistence Optimizer: MAJOR 	  - PostInsertGeneratorEvent - The [id] identifier attribute in the [org.springframework.samples.petclinic.owner.Pet] entity uses the [IdentityGenerator] strategy which prevents Hibernate from enabling JDBC batch inserts. Consider using the SEQUENCE identifier strategy instead. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#PostInsertGeneratorEvent

Hypersistence Optimizer: CRITICAL - EagerFetchingEvent - The [owner] attribute in the [org.springframework.samples.petclinic.owner.Pet] entity uses eager fetching. Consider using a lazy fetching which, not only that is more efficient, but it is way more flexible when it comes to fetching data. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#EagerFetchingEvent

Hypersistence Optimizer: CRITICAL - EagerFetchingEvent - The [type] attribute in the [org.springframework.samples.petclinic.owner.Pet] entity uses eager fetching. Consider using a lazy fetching which, not only that is more efficient, but it is way more flexible when it comes to fetching data. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#EagerFetchingEvent

Hypersistence Optimizer: CRITICAL - EagerFetchingEvent - The [visits] attribute in the [org.springframework.samples.petclinic.owner.Pet] entity uses eager fetching. Consider using a lazy fetching which, not only that is more efficient, but it is way more flexible when it comes to fetching data. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#EagerFetchingEvent

Hypersistence Optimizer: MAJOR    - PostInsertGeneratorEvent - The [id] identifier attribute in the [org.springframework.samples.petclinic.owner.Owner] entity uses the [IdentityGenerator] strategy which prevents Hibernate from enabling JDBC batch inserts. Consider using the SEQUENCE identifier strategy instead. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#PostInsertGeneratorEvent

Hypersistence Optimizer: MAJOR    - PostInsertGeneratorEvent - The [id] identifier attribute in the [org.springframework.samples.petclinic.visit.Visit] entity uses the [IdentityGenerator] strategy which prevents Hibernate from enabling JDBC batch inserts. Consider using the SEQUENCE identifier strategy instead. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#PostInsertGeneratorEvent

Hypersistence Optimizer: MAJOR    - PostInsertGeneratorEvent - The [id] identifier attribute in the [org.springframework.samples.petclinic.owner.PetType] entity uses the [IdentityGenerator] strategy which prevents Hibernate from enabling JDBC batch inserts. Consider using the SEQUENCE identifier strategy instead. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#PostInsertGeneratorEvent

Hypersistence Optimizer: MAJOR    - PostInsertGeneratorEvent - The [id] identifier attribute in the [org.springframework.samples.petclinic.vet.Specialty] entity uses the [IdentityGenerator] strategy which prevents Hibernate from enabling JDBC batch inserts. Consider using the SEQUENCE identifier strategy instead. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#PostInsertGeneratorEvent

Hypersistence Optimizer: MAJOR    - PostInsertGeneratorEvent - The [id] identifier attribute in the [org.springframework.samples.petclinic.vet.Vet] entity uses the [IdentityGenerator] strategy which prevents Hibernate from enabling JDBC batch inserts. Consider using the SEQUENCE identifier strategy instead. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#PostInsertGeneratorEvent

Hypersistence Optimizer: CRITICAL - EagerFetchingEvent - The [specialties] attribute in the [org.springframework.samples.petclinic.vet.Vet] entity uses eager fetching. Consider using a lazy fetching which, not only that is more efficient, but it is way more flexible when it comes to fetching data. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#EagerFetchingEvent

Hypersistence Optimizer: MAJOR    - SkipAutoCommitCheckEvent - You should set the [hibernate.connection.provider_disables_autocommit] configuration property to [true] while also making sure that the underlying DataSource is configured to disable the auto-commit flag whenever a new Connection is being acquired. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#SkipAutoCommitCheckEvent

Hypersistence Optimizer: CRITICAL - JdbcBatchSizeEvent - If you set the [hibernate.jdbc.batch_size] configuration property to a value greater than 1 (usually between 5 and 30), Hibernate can then execute SQL statements in batches, therefore reducing the number of database network roundtrips. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#JdbcBatchSizeEvent

Hypersistence Optimizer: CRITICAL - QueryPaginationCollectionFetchingEvent - You should set the [hibernate.query.fail_on_pagination_over_collection_fetch] configuration property to the value of [true], as Hibernate can then prevent in-memory pagination when join fetching a child entity collection. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#QueryPaginationCollectionFetchingEvent

Hypersistence Optimizer: MAJOR    - QueryInClauseParameterPaddingEvent - You should set the [hibernate.query.in_clause_parameter_padding] configuration property to the value of [true], as Hibernate entity queries can then make better use of statement caching and fewer entity queries will have to be compiled while varying the number of parameters passed to the in query clause. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#QueryInClauseParameterPaddingEvent

Expaining the performance issues reported by Hypersistence Optimizer

A total of 14 issues have been reported which we will group by type and explain what we need to do to address them.

PostInsertGeneratorEvent

The PostInsertGeneratorEvent issue was reported 6 times.

The problem is that all entities (e.g. Pet, Owner, Visit, PetType, Specialty, Vet) inherit the entity identifier definition from BaseEntity who uses the IDENTITY startegy:

@MappedSuperclass
public class BaseEntity 
        implements Serializable {
    @Id
    @GeneratedValue(
        strategy = GenerationType.IDENTITY
    )
    private Integer id;

    //Getters and setters omitted for brevity
}

To fix the issue, you need to use GenerationType.SEQUENCE as Hibernate can then use batch inserts during flush time.

The problem with the IDENTITY entity identifier strategy is that it prevents Hibernate from batching INSERT statements at flush time. For more details about this issue, check out the Hypersistence Optimizer documentation issue description.

EagerFetchingEvent

The EagerFetchingEvent tell you that a given entity association is fetched eagerly by Hibernate, and that can lead to N+1 query issues or make your application fetch more data than necessary.

In the Pet entity, the type and owner associations are implicitly fetched eagerly since, by default, @ManyToOne and @OneToOne associations use the FetchType.EAGER strategy.

What’s more concerning is that the visits collection is also set to FetchType.EAGER because it’s unlikely that every use case will ever need this association initialized, and currently, fetching a single Pet entity will require 3 additional joins.

@ManyToOne
@JoinColumn(name = "type_id")
private PetType type;

@ManyToOne
@JoinColumn(name = "owner_id")
private Owner owner;

@OneToMany(cascade = CascadeType.ALL, mappedBy = "petId", fetch = FetchType.EAGER)
private Set<Visit> visits = new LinkedHashSet<>();

Also, the Vet entity defines a @ManyToMany association that uses FetchType.EAGER:

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
    name = "vet_specialties", 
    joinColumns = @JoinColumn(
        name = "vet_id"
    ), 
    inverseJoinColumns = @JoinColumn(
        name = "specialty_id"
    )
)
private Set<Specialty> specialties;

To fix the issue, you need to use FetchType.LAZY for all associations. For more details about this issue, check out the Hypersistence Optimizer documentation issue description.

SkipAutoCommitCheckEvent

The SkipAutoCommitCheckEvent tells you that you need to enable the hibernate.connection.provider_disables_autocommit Hibernate configuration property. This way, the auto-commit check done by Hibernate at the beginning of a RESOURCE_LOCAL transaction can be skipped as long as you already disabled the auto-commit at the JDBC connection pool level.

To fix the issue, you need to add the following configuration properties to the application.properties file:

spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true
spring.datasource.hikari.auto-commit=false

For more details about this issue, check out the Hypersistence Optimizer documentation issue description.

JdbcBatchSizeEvent

The JdbcBatchSizeEvent tells you that you need to set up the hibernate.jdbc.batch_size Hibernate configuration property. This way, Hibernate can use JDBC batching at flush time.

To fix the issue, you need to add the following configuration properties to the application.properties file:

spring.jpa.properties.hibernate.jdbc.batch_size=5
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true

For more details about this issue, check out the Hypersistence Optimizer documentation issue description.

QueryPaginationCollectionFetchingEvent

The QueryPaginationCollectionFetchingEvent tells you that you need to enable the hibernate.query.fail_on_pagination_over_collection_fetch Hibernate configuration property. This way, Hibernate will fail whenever in-memory pagination is being done instead of just logging a warning message that might be overlooked.

To fix the issue, you need to add the following configuration properties to the application.properties file:

spring.jpa.properties.hibernate.query.fail_on_pagination_over_collection_fetch=true

For more details about this issue, check out the Hypersistence Optimizer documentation issue description.

QueryInClauseParameterPaddingEvent

The QueryInClauseParameterPaddingEvent tells you that you need to enable the hibernate.query.in_clause_parameter_padding Hibernate configuration property. This way, your application can better take advantage of statement caching as Hibernate will use fewer IN clause parameter combinations.

To fix the issue, you need to add the following configuration properties to the application.properties file:

spring.jpa.properties.hibernate.query.in_clause_parameter_padding=true

For more details about this issue, check out the Hypersistence Optimizer documentation issue description.

To see all the changes that I’ve done to address the problems reported by Hypersitence Optimizer, check out this GitHub commit. Now, when the test runs, no issue is being reported.

Cool, right?

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

Conclusion

Hypersistence Optimizer is very useful if your application uses JPA and Hibernate.

Not only that it can detect JPA mapping issues, but it can also scan the Hibernate configurations and let you know what you need to change so that your application works faster.

And it can do that on every commit, so you will no longer overlook a JPA or Hibernate performance-related improvement.

Download free ebook sample

Newsletter logo
10 000 readers have found this blog worth following!

If you subscribe to my newsletter, you'll get:
  • A free sample of my Video Course about running Integration tests at warp-speed using Docker and tmpfs
  • 3 chapters from my book, High-Performance Java Persistence,
  • a 10% discount coupon for my book.

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.

Want to run your data access layer at warp speed?