PerfTester Premium – User Guide

Too many Hibernate performance issues show up in production because they are hard to find in a typical test environment. You would need to simulate typical production scenarios. That often requires a huge test database and running hundreds of tests in parallel.

You can avoid that if you have very detailed information about how Hibernate interacts with your database and check that with common patterns of typical performance issues.

PerfTester provides you an automated solution for the 2nd option. It collects detailed statistics about Hibernate's database interactions, makes them testable, uses them to detect relevant performance issues automatically and provides you with detailed suggestions to fix them.

Download and License Key

Please download PerfTester premium here and add it to your local maven repository:

You received an email with your license key. Please add the folder perfTester to your home directory and add a file called license.txt to it that contains the your license key in the 1st line.

Java Doc

You can find the complete JavaDoc at: https://thorben-janssen.com/perftester-docs/apidocs.

Installation

Depending on your technology stack and usage scenario, installing PerfTester premium only requires an extra annotation or a few lines of configuration.

Spring Boot and JUnit 5

To run PerfTester Premium as part of a JUnit 5 test of your Spring Boot application, you need to add the following Maven dependency to your project.

Spring Boot Version 2.1 and above:

<dependency>
	<groupId>com.thorben.janssen.performance.tester</groupId>
	<artifactId>performancetester-junit5-springboot21</artifactId>
	<version>0.6.1</version>
	<scope>test</scope>
</dependency>

Spring Boot Version 2.0:

<dependency>
	<groupId>com.thorben.janssen.performance.tester</groupId>
	<artifactId>performancetester-junit5-springboot20</artifactId>
	<version>0.6.1</version>
	<scope>test</scope>
</dependency>

After you've done that, you can annotate your test class with @SpringHibernateStatisticsAwareTest to activate the PerfTester integration. 

@SpringHibernateStatisticsAwareTest
public class JdbcBatchingTestExample {
    @Test
    public void myTestMethod() {
        // perform your test
    }
}

Spring Boot

To integrate PerfTester Premium with your Spring Boot application, you need to add the following Maven dependency to your project.

Spring Boot Version 2.1 and above:

<dependency>
	<groupId>com.thorben.janssen.performance.tester</groupId>
	<artifactId>performancetester-hibernate53</artifactId>
	<version>0.6.1</version>
</dependency>

Spring Boot Version 2.0:

<dependency>
	<groupId>com.thorben.janssen.performance.tester</groupId>
	<artifactId>performancetester-hibernate5</artifactId>
	<version>0.6.1</version>
</dependency>

After you've done that, you need to add the following configuration parameters to your application.properties file.

spring.jpa.properties.hibernate.stats.factory = com.thorben.janssen.performance.tester.hibernate.standalone.StandaloneStatisticsFactory
spring.jpa.properties.hibernate.generate_statistics = true
spring.jpa.properties.hibernate.session.events.auto = com.thorben.janssen.performance.tester.hibernate.standalone.StandaloneBaseListener

Spring Data JPA without Spring Boot

To integrate PerfTester Premium with your Spring application, you need to add the following Maven dependency to your project.

Spring Data JPA version 2.1 and above:

<dependency>
	<groupId>com.thorben.janssen.performance.tester</groupId>
	<artifactId>performancetester-hibernate53</artifactId>
	<version>0.6.1</version>
</dependency>

Spring Data JPA 2.0:

<dependency>
	<groupId>com.thorben.janssen.performance.tester</groupId>
	<artifactId>performancetester-hibernate5</artifactId>
	<version>0.6.1</version>
</dependency>

After you've done that, you need to add the following configuration parameters to your application.properties file.

spring.jpa.properties.hibernate.stats.factory = com.thorben.janssen.performance.tester.hibernate.standalone.StandaloneStatisticsFactory
spring.jpa.properties.hibernate.generate_statistics = true
spring.jpa.properties.hibernate.session.events.auto = com.thorben.janssen.performance.tester.hibernate.standalone.StandaloneBaseListener

Java EE / Jakarta EE Application Servers

To integrate PerfTester Premium with your Java EE / Jakarta EE application, you need to add the following Maven dependency to your project.

Hibernate version 5.3 and above:

<dependency>
	<groupId>com.thorben.janssen.performance.tester</groupId>
	<artifactId>performancetester-hibernate53</artifactId>
	<version>0.6.1</version>
</dependency>

Hibernate version 5.0-5.2:

<dependency>
	<groupId>com.thorben.janssen.performance.tester</groupId>
	<artifactId>performancetester-hibernate5</artifactId>
	<version>0.6.1</version>
</dependency>

After you've done that, you need to add the following configuration parameters to your persistence.xml file.

<persistence>
	<persistence-unit name="my-persistence-unit">
		...

		<properties>
			<property name="hibernate.stats.factory" value="com.thorben.janssen.performance.tester.hibernate.standalone.StandaloneStatisticsFactory" />
			<property name="hibernate.generate_statistics" value="true" />
			<property name="hibernate.session.events.auto" value="com.thorben.janssen.performance.tester.hibernate.standalone.StandaloneBaseListener" />

			...
		</properties>
	</persistence-unit>
</persistence>

Plain Hibernate

To integrate PerfTester Premium with your Java EE / Jakarta EE application, you need to add the following Maven dependency to your project.

Hibernate version 5.3 and above:

<dependency>
	<groupId>com.thorben.janssen.performance.tester</groupId>
	<artifactId>performancetester-hibernate53</artifactId>
	<version>0.6.1</version>
</dependency>

Hibernate version 5.0-5.2:

<dependency>
	<groupId>com.thorben.janssen.performance.tester</groupId>
	<artifactId>performancetester-hibernate5</artifactId>
	<version>0.6.1</version>
</dependency>

After you've done that, you need to add the following configuration parameters to your persistence.xml file.

<persistence>
	<persistence-unit name="my-persistence-unit">
		...

		<properties>
			<property name="hibernate.stats.factory" value="com.thorben.janssen.performance.tester.hibernate.standalone.StandaloneStatisticsFactory" />
			<property name="hibernate.generate_statistics" value="true" />
			<property name="hibernate.session.events.auto" value="com.thorben.janssen.performance.tester.hibernate.standalone.StandaloneBaseListener" />

			...
		</properties>
	</persistence-unit>
</persistence>

Plain Hibernate and JUnit 5

To run PerfTester Premium as part of a JUnit 5 test of an application that uses Hibernate standalone, you need to add the following Maven dependency to your project.

Hibernate version 5.3 and above:

<dependency>
	<groupId>com.thorben.janssen.performance.tester</groupId>
	<artifactId>performancetester-junit5-hibernate53</artifactId>
	<version>0.6.1</version>
</dependency>

Hibernate version 5.0-5.2:

<dependency>
	<groupId>com.thorben.janssen.performance.tester</groupId>
	<artifactId>performancetester-junit5-hibernate5</artifactId>
	<version>0.6.1</version>
</dependency>

After you've done that, you can annotate your test class with @HibernateStatisticsAwareTest to activate the PerfTester integration. 

@HibernateStatisticsAwareTest
public class JdbcBatchingTestExample {
    @Test
    public void myTestMethod() {
        // perform your test
    }
}

Detecting Performance Issues

After integrating PerfTester Premium with your application, it automatically monitors all of Hibernate's database interactions, detects performance issues automatically. At the end of each Hibernate session, PerfTester writes a message for each detected performance issue to your log file.

Detecting Performance Issues in JUnit5 Tests

If you use PerfTester's JUnit 5 integration, your test case will fail if any performance issue was detected. You can adjust this by annotating your test class or method with @IgnoreWarnings. The value attribute allows you to specify an array of PerformanceTesterWarningType that shall be logged but not cause your test case to fail. 

@SpringHibernateStatisticsAwareTest
public class JdbcBatchingTestExample {
    @Test
    @IgnoreWarnings({PerformanceTesterWarningType.NO_JDBC_BATCHING, PerformanceTesterWarningType.NO_JDBC_BATCH_ORDERING})
    public void myTestMethod() {
        // perform your test
    }
}

Or you can ignore all warning types except the ones referenced in butFailOn attribute.

@SpringHibernateStatisticsAwareTest
public class JdbcBatchingTestExample {
    @Test
    @IgnoreWarnings(butFailOn = {PerformanceTesterWarningType.NO_JDBC_BATCHING, PerformanceTesterWarningType.NO_JDBC_BATCH_ORDERING})
    public void myTestMethod() {
        // perform your test
    }
}

If you don't set either of the 2 annotation attributes, PerfTester will not only log all detected performance issues but your test case will not fail because of them.

PerformanceTesterWarningTypes

The PerformanceTesterWarningType is an enum that specifies the type of the detected performance issue.

N_PLUS_ONE

PerfTester detected a potential n+1 select issue. This is typically caused when Hibernate loads a lazily fetched association for multiple entities or when you configured eager fetching.

[N_PLUS_ONE] Potential n+1 select issue detected for entity com.thorben.janssen.app.spring.nPlusOne.entity.Publisher. You fetch it as a to-one association in 100% of the times.
You should use a JOIN FETCH or an EntityGraph to initialize the association. Learn more at: https://thorben-janssen.com/5-ways-to-initialize-lazy-relations-and-when-to-use-them/
Please review the following places in which you load com.thorben.janssen.app.spring.nPlusOne.entity.Publisher entity objects:
Query: LoadInfo [query=Initialize eager association, executionPoint=com.thorben.janssen.app.spring.nPlusOne.SpringNPlusOneWarningTestExamples.fail_FindAllBooks_whenUsingEntityProjection(SpringNPlusOneWarningTestExamples.java:93)]
Loaded entity ids: 1, 2

Before you fix this warning, please check how many associations might get initialized when you deploy this code to production. N+1 select issues are only a problem, if your n gets big enough (rule of thumb: >5).

READ_ENTITIES_WITHOUT_WRITE

PerfTester detected that you read several entities which you don't use for any write operations. Other projections, like DTOs, provide a better read performance and should be preferred in general.

[READ_ENTITIES_WITHOUT_WRITE] You read com.thorben.janssen.app.spring.entityReadWithoutWrite.entity.Author entity objects 4 times but you only use them in 0 write operations.
You should consider using a DTO projection instead. It provides much better performance for read-only operations, see https://thorben-janssen.com/dto-projections/.
Please review the following places in which you load com.thorben.janssen.app.spring.entityReadWithoutWrite.entity.Author entity objects:
Query: LoadInfo [query=select generatedAlias0 from Author as generatedAlias0, executionPoint=com.thorben.janssen.app.spring.entityReadWithoutWrite.service.AuthorService.getAllAuthors(AuthorService.java:26)]
Loaded entity ids: 1, 2, 3, 4

LONG_DATABASE_OPERATIONS

PerfTester measured the time spend on database interactions and detected that they took up a huge part of your execution time. You should take a closer look at the other PerfTester warnings and how you interact with the database.

[LONG_DATABASE_OPERATIONS] It seems like Hibernate slows down your application. Your database operations take up 73% of the execution time.
Upgrade to Performance Tester Pro to get detailed information about these issues and suggestions on how to fix them.
Visit https://thorben-janssen.com/performanceTesterPro to learn more.

TOO_MANY_STATEMENTS

PerfTester detected a huge number of executed JDBC statements, which slow down your application. You should take a closer look at the other PerfTester warnings and how you interact with the database.

[TOO_MANY_STATEMENTS] You executed 103 JDBC statements. This slows down your application and is an indicator for inefficient entity mappings and/or data access code.
Upgrade to Performance Tester Pro to get detailed information about these issues and suggestions on how to fix them.
Visit https://thorben-janssen.com/performanceTesterPro to learn more.

MANY_TO_MANY_AS_BAG

When you model a many-to-many association as a java.util.List, Hibernate calls this a bag. Unfortunately, Hibernate handles bags very inefficiently. You should better model the association using a java.util.Set

[MANY_TO_MANY_AS_BAG] You modeled your many-to-many association com.thorben.janssen.app.spring.manyToManyList.entity.Author.books as a java.util.List. Hibernate calls this a PersistentBag and handles it very inefficiently. Hibernate removes all association entries and adds the current ones.
You should better model your many-to-many associations as a java.util.Set, see https://thorben-janssen.com/association-mappings-bag-list-set/
Please review your com.thorben.janssen.app.spring.manyToManyList.entity.Author.books attribute and the following places in which you update com.thorben.janssen.app.spring.manyToManyList.entity.Author entity objects:
Query: LoadInfo [executionPoint=com.thorben.janssen.app.spring.manyToManyList.ManyToManyListInsteadOfSetTestExamples.fail_ManyToMany_bag(ManyToManyListInsteadOfSetTestExamples.java:24)]

NO_JDBC_BATCHING

JDBC batching groups multiple identical insert, update and delete statements together and provides a great way to improve the performance of write operations. PerfTester Premium checks if this optimization is missing and if a use case might benefit from it.

[NO_JDBC_BATCHING] You are inserting 10 com.thorben.janssen.app.spring.jdbcBatching.entity.Author entities without using JDBC batching.
You should consider activating JDBC batching by configuring the hibernate.jdbc.batch_size property. Insert statements also benefit from Hibernate's order_inserts optimization. Make sure to set the hibernate.order_inserts property to true.

NO_JDBC_BATCH_ORDERING

If you use JDBC batching, you should also let Hibernate order all insert and update statements to include as many statements in one batch as possible. PerfTester Premium checks if this optimization is missing and if a use case might benefit from it.

[NO_JDBC_BATCH_ORDERING] You are using JDBC batching without Hibernate's order insert and order update optimization.
You should consider activating the ordering of JDBC insert and update statements by setting the hibernate.order_inserts and the hibernate.order_updates parameters to true. This enables you to use JDBC batches more efficiently.

TOO_MANY_JOIN_FETCHES

JOIN FETCH clauses tell Hibernate to initialize an otherwise lazily fetched association when loading an entity. Using it is a general best practice to improve the performance of read operation. But multiple JOIN FETCH clauses also massively increase the size of the result set. This can become a performance issue.

[TOO_MANY_JOIN_FETCHES] Your query SELECT a FROM Author a JOIN FETCH a.books b JOIN FETCH b.publisher JOIN FETCH a.address uses 3 JOIN FETCH clauses to initialize associations. Depending on the number of elements of the joined associations, this can create a huge product that slows down your application. If you used a small test dataset, you should run the same test using a dump of your production database.
It might be better to split your query into multiple ones to reduce the size of the created product. Learn more at: https://thorben-janssen.com/fix-multiplebagfetchexception-hibernate/#Option_2_Split_it_into_multiple_queries
Please review the following places in which you execute this query: 
com.thorben.janssen.app.spring.tooManyJoinFetchClauses.service.AuthorService.getAuthorsWithBooksAndPublisherAndAddress(AuthorService.java:42) (14576 records)

INEFFICIENT_PRIMARY_KEY_GENERATION_STRATEGY

Hibernate supports various generation strategies to create unique primary key values. But not all of them provide a great performance. PerfTester finds the best strategy for the used database dialect and throws a warning if it isn't used.

[INEFFICIENT_PRIMARY_KEY_GENERATION_STRATEGY] You use the GenerationType.TABLE to generate primary key values for your com.thorben.janssen.app.spring.primaryKeyGenerator.entity.Address entity. This is a very inefficient approach that uses a separate database table and database locks to simulate a sequence.
You should better use GenerationType.SEQUENCE to use a database sequence, see https://thorben-janssen.com/jpa-generate-primary-keys/

MISSING_SEQUENCE_GENERATOR_OPTIMIZATION

Hibernate provides proprietary optimizations to use database sequences as efficiently as possible. PerfTester Premium checks if this optimization is used.

[MISSING_SEQUENCE_GENERATOR_OPTIMIZATION] You use the GenerationType.SEQUENCE to generate primary key values for your com.thorben.janssen.app.spring.primaryKeyGenerator.entity.Author entity. This is the most efficient approach to generate primary key values. You could improve the performance even further by activating Hibernate's proprierty optimization to reduce the number of select statement.
Make sure to set the GenerationType.SEQUENCE for your primary key attribute. If you specified a @SequenceGenerator, you should set the allocationSize attribute to a value greater than 1 (default = 50). See https://thorben-janssen.com/jpa-generate-primary-keys/ for more information.