Spring Boot performance tuning
Are you struggling with performance issues in your Spring, Jakarta EE, or Java EE application?
What if there were a tool that could automatically detect what caused performance issues in your JPA and Hibernate data access layer?
Wouldn’t it be awesome to have such a tool to watch your application and prevent performance issues during development, long before they affect production systems?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, Micronaut, or Play Framework.
So, rather than fixing performance issues in your production system on a Saturday night, you are better off using Hypersistence Optimizer to help you prevent those issues so that you can spend your time on the things that you love!
Introduction
While developing a Spring Boot application is rather easy, tuning the performance of a Spring Boot application is a more challenging task, as, not only it requires you to understand how the Spring framework works behind the scenes, but you have to know what is the best way to use the underlying data access framework, like Hibernate for instance.
In a previous article, I showed you how easily to optimize the performance of the Petclinic demo application. However, by default, the Petclinic Spring Boot application uses the in-memory HSQLDB database, which is not what you’d usually want your application to be optimized for.
In this article, we are going to switch the underlying database from the in-memory HSQLDB to MySQL and run Hypersistence Optimizer to generate a performance tuning report for the JPA and Hibernate data access layer in the context of the MySQL database.
Tuning the performance of a #SpringBoot app can be challenging. @vlad_mihalcea shows you #howto generate a performance tuning report for the #JPA and Hibernate data access layer in the context of the #MySQL database. https://t.co/V2CKQNcg4x pic.twitter.com/5Ii40fGXHo
— Java (@java) November 6, 2019
Spring Petclinic
Spring Petclinic is a demo application built using Spring Boot, which demonstrates the framework capabilities.
By default, Spring Boot uses HSQLDB, but while this in-memory database is used extensively for testing, in a production environment, you are more likely to use a database like MySQL or PostgreSQL.
Luckily, Spring Boot offers a MySQL configuration and a Spring profile, which we can use as a starting point for our analysis.
Switching tests to using the MySQL profile
First of all, we need to use the @ActiveProfiles annotation to activate the mysql Spring profile.
@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("mysql")
public class PetclinicIntegrationTests {
⠀
@Autowired
private VetRepository vets;
⠀
@Test
public void testFindAll() throws Exception {
vets.findAll();
vets.findAll(); // served from cache
}
}
After activating the mysql Spring profile, Spring Boot is going to use the application-mysql.properties configuration file to override settings from the default application.properties settings file.
In our case, the only change that was needed in the application-mysql.properties configuration file was to change the database connection credentials:
database=mysql spring.datasource.url=jdbc:mysql://localhost/petclinic spring.datasource.username=mysql spring.datasource.password=admin
Running Hypersistence Optimizer
As you can see in the PetclinicIntegrationTests class, running Hypersitence Optimizer is very easy, as you just have to pass the EntityManagerFactory instance to the HypersistenceOptimizer object constructor, and call the init method.
Hypersistence Optimizer : CRITICAL - TableGeneratorEvent - The [id] identifier attribute in the [org.springframework.samples.petclinic.owner.Pet] entity uses the TABLE strategy, which does not scale very well. Consider using the IDENTITY identifier strategy instead, even if it does not allow JDBC batch inserts. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#TableGeneratorEvent Hypersistence Optimizer : CRITICAL - TableGeneratorEvent - The [id] identifier attribute in the [org.springframework.samples.petclinic.owner.Owner] entity uses the TABLE strategy, which does not scale very well. Consider using the IDENTITY identifier strategy instead, even if it does not allow JDBC batch inserts. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#TableGeneratorEvent Hypersistence Optimizer : CRITICAL - TableGeneratorEvent - The [id] identifier attribute in the [org.springframework.samples.petclinic.visit.Visit] entity uses the TABLE strategy, which does not scale very well. Consider using the IDENTITY identifier strategy instead, even if it does not allow JDBC batch inserts. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#TableGeneratorEvent Hypersistence Optimizer : CRITICAL - TableGeneratorEvent - The [id] identifier attribute in the [org.springframework.samples.petclinic.owner.PetType] entity uses the TABLE strategy, which does not scale very well. Consider using the IDENTITY identifier strategy instead, even if it does not allow JDBC batch inserts. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#TableGeneratorEvent Hypersistence Optimizer : CRITICAL - TableGeneratorEvent - The [id] identifier attribute in the [org.springframework.samples.petclinic.vet.Specialty] entity uses the TABLE strategy, which does not scale very well. Consider using the IDENTITY identifier strategy instead, even if it does not allow JDBC batch inserts. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#TableGeneratorEvent Hypersistence Optimizer : CRITICAL - TableGeneratorEvent - The [id] identifier attribute in the [org.springframework.samples.petclinic.vet.Vet] entity uses the TABLE strategy, which does not scale very well. Consider using the IDENTITY identifier strategy instead, even if it does not allow JDBC batch inserts. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#TableGeneratorEvent Hypersistence Optimizer : MAJOR - DialectVersionEvent - Your application is using the [org.hibernate.dialect.MySQL5Dialect] Hibernate-specific Dialect. Consider using the [org.hibernate.dialect.MySQL57Dialect] instead as it's closer to your current database server version [MySQL 8.0]. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#DialectVersionEvent
TableGeneratorEvent
The TableGeneratorEvent is triggered because, in the previous article, we switched the entity identifier strategy from IDENTITY to SEQUENCE.
Using the SEQUENCE identifier strategy is the best option if and only if the underlying database supports sequence objects. However, MySQL 8 does not support database sequences, and, for this reason, Hibernate switches to using the TABLE generator, and, as explained in his article, the TABLE generator is a terrible choice when it comes to application performance.
Switching to using IDENTITY for MySQL
So, in our case, we are better off using the IDENTITY generator for MySQL. However, we don’t want to change the entity mappings to IDENTITY as that we might want to deploy the Petclinic application on other database systems, such as Oracle, SQL Server, or PostgreSQL, which do support database sequences. So, we want the default mapping to use the SEQUENCE strategy, but only change that to IDENTITY for MySQL.
Even if the
IDENTITYgenerator prevents Hibernate from batching INSERT statements at the JDBC level, it’s still better to use this strategy rather than theTABLEgenerator. For more details about JDBC batch updates, check out this article.
And, the solution is actually very simple. In fact, I already explained it in this article.
By providing a MySQL-specific orm.xml JPA configuration file that overrides the base class entity identifier strategy, we can switch to using IDENTITY when using MySQL.
So, we are going to create an orm.xml file that’s going to be deployed by the mysql profile in the META-INF folder in the application jar file.
The orm.xml configuration file looks as follows:
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings
xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm_2_2.xsd"
version="2.2">
⠀
<mapped-superclass
class="org.springframework.samples.petclinic.model.BaseEntity">
<attributes>
<id name="id">
<generated-value strategy="IDENTITY"/>
</id>
</attributes>
</mapped-superclass>
⠀
</entity-mappings>
That’s it!
Now, when rerunning the PetclinicIntegrationTests test case, Hypersistence Optimizer will generate the following report:
Hypersistence Optimizer : MINOR - IdentityGeneratorEvent - 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. Since the database does not support the SEQUENCE identifier strategy, you need to use plain JDBC or some other data access framework to batch insert statements. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#IdentityGeneratorEvent Hypersistence Optimizer : MINOR - IdentityGeneratorEvent - 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. Since the database does not support the SEQUENCE identifier strategy, you need to use plain JDBC or some other data access framework to batch insert statements. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#IdentityGeneratorEvent Hypersistence Optimizer : MINOR - IdentityGeneratorEvent - 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. Since the database does not support the SEQUENCE identifier strategy, you need to use plain JDBC or some other data access framework to batch insert statements. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#IdentityGeneratorEvent Hypersistence Optimizer : MINOR - IdentityGeneratorEvent - 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. Since the database does not support the SEQUENCE identifier strategy, you need to use plain JDBC or some other data access framework to batch insert statements. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#IdentityGeneratorEvent Hypersistence Optimizer : MINOR - IdentityGeneratorEvent - 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. Since the database does not support the SEQUENCE identifier strategy, you need to use plain JDBC or some other data access framework to batch insert statements. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#IdentityGeneratorEvent Hypersistence Optimizer : MINOR - IdentityGeneratorEvent - 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. Since the database does not support the SEQUENCE identifier strategy, you need to use plain JDBC or some other data access framework to batch insert statements. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#IdentityGeneratorEvent Hypersistence Optimizer : MAJOR - DialectVersionEvent - Your application is using the [org.hibernate.dialect.MySQL5Dialect] Hibernate-specific Dialect. Consider using the [org.hibernate.dialect.MySQL57Dialect] instead as it's closer to your current database server version [MySQL 8.0]. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#DialectVersionEvent
Notice that the TableGeneratorEvent was replaced by the IdentityGenerator. The IdentityGenerator is generated because the IDENTITY strategy prevents Hibernate from batching INSERT statements when calling persist as it needs to know the entity identifier when associating the persisting entity with the first-level cache.
But since we don’t really have an option for the IdentityGenerator when using MySQL, we can simply choose to ignore this event in our case.
Filtering Hyperstistence Optimizer events
Hypersitence Optimizer is very flexible. You can customize how the events are handled, whether you want them to be logged or collected to a List, and you can choose to filter events as well.
To filter out the IdentityGeneratorEvent, you need to configure the HyperstistenceOptimizer bean like this:
@Configuration
public class HypersistenceConfiguration {
@Bean
public HypersistenceOptimizer hypersistenceOptimizer(
EntityManagerFactory entityManagerFactory) {
return new HypersistenceOptimizer(
new JpaConfig(entityManagerFactory).setEventFilter(
event -> !(event instanceof IdentityGeneratorEvent)
)
);
}
}
Now, when rerunning the PetclinicIntegrationTests test case, Hypersistence Optimizer will prevent the IdentityGeneratorEvent from being included in the report:
Hypersistence Optimizer : MAJOR - DialectVersionEvent - Your application is using the [org.hibernate.dialect.MySQL5Dialect] Hibernate-specific Dialect. Consider using the [org.hibernate.dialect.MySQL57Dialect] instead as it's closer to your current database server version [MySQL 8.0]. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#DialectVersionEvent
DialectVersionEvent
The DialectVersionEvent is generated because the default configured Hibernate dialect is MySQL57Dialect while the Petclinic application is running against MySQL 8.
So, we just have to include the MySQL8Dialect in the Spring Boot application-mysql.properties configuration file:
database=mysql spring.datasource.url=jdbc:mysql://localhost/petclinic spring.datasource.username=mysql spring.datasource.password=admin spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL8Dialect
And now, no issue is being reported by Hypersistence Optimizer. 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 a very powerful tool that can help you detect JPA and Hibernate performance issues long before they hit the production system.
And, one of its greatest advantages is that it can do all these checks on every commit, so you will no longer overlook a JPA or Hibernate performance-related improvement because of a very tight development schedule.
All the optimizations presented in this article can be found in this GitHub repository.






