Hibernate Application Performance Tuning
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!
Introduction
Because performance tuning is very important when it comes to developing a data access layer, in this article, I’m going to show you how you can optimize the famous Hibernate Caveat Emptor application using Hypersistence Optimizer.
The Caveat Emptor application was created by Christian Bauer and Gavin King for the Hibernate in Action book they published in 2004 and was included in both the first and second editions of the well-known Java Persistence with Hibernate book.
Getting the Caveat Emptor application
You can get all three versions of the Caveat Emptor application from the Java Persistence with Hibernate website.
For this article, I chose the 2006 Caveat Emptor native Hibernate version and wanted to see if I could run Hypersistence Optimizer to detect performance issues.
Now, since Hypersistence Optimizer requires at least Hibernate 3.3, I had to make several dependency upgrades in order to run my test:
hibernate3.jar
⇢hibernate-core-3.3.0.GA.jar
commons-collections-2.1.1.jar
⇢commons-collections-3.2.2.jar
testng-5.4-jdk15.jar
⇢testng-6.14.3.jar
jboss-common.jar
⇢slf4j-api-1.6.1.jar
andslf4j-log4j12-1.6.1.jar
And I added the following dependencies:
javax-jpa-1.0.jar
hypersistence-optimizer-2.4.0.jar
And, that’s it!
Adding a Hibernate application performance tuning test
Now, to analyze the performance issues of the Caveat Emptor application, I created the following integration test:
public class PerformanceTuningTest extends HibernateIntegrationTest { protected void prepareSettings() { dataSetLocation = "auction/test/basedata.xml"; beforeTestOperations.add( DatabaseOperation.CLEAN_INSERT ); } @Test(groups = "integration-hibernate") public void checkPerformanceIssues() { List<Event> events = new HypersistenceOptimizer( new HibernateConfig( HibernateUtil.getSessionFactory() ) ).getEvents(); Assert.assertTrue(events.isEmpty()); } }
And, when running this integration test, I got the following outcome:
Hypersistence Optimizer - 47 issues were found: - 1 BLOCKER, - 31 CRITICAL, - 5 MAJOR, - 10 MINOR
Of all those issues, 40 are mapping-related issues:
- 12
IdentityGeneratorEvent
- 9
IntegerVersionColumnSizeEvent
- 7
EagerFetchingEvent
- 4
BidirectionalSynchronizationEvent
- 2
UnidirectionalOneToManyEvent
- 2
ElementCollectionEvent
- 1
ElementCollectionListEvent
- 1
StringDiscriminatorTypeEvent
- 1
BatchFetchingEvent
And 7 are configuration-related problems:
- 1
DriverManagerConnectionProviderEvent
- 1
MultiLineStatementLoggingEvent
- 1
SqlCommentStatementLoggingEvent
- 1
JdbcBatchOrderInsertsEvent
- 1
JdbcBatchOrderUpdatesEvent
- 1
JdbcBatchVersionedEntitiesEvent
- 1
SchemaGenerationEvent
IdentityGeneratorEvent
The IdentityGeneratorEvent
is reported as follows:
CRITICAL - IdentityGeneratorEvent - The [id] identifier attribute in the [ItemEntity] entity uses the [IdentityGenerator] strategy, which prevents Hibernate from enabling JDBC batch inserts. Consider using the SEQUENCE identifier strategy instead.
If we go to the ItemEtity
, we can see that, indeed, the id
is mapped like this:
<!ENTITY idgenerator "identity"> <id name="id" type="long" column="ITEM_ID" node="@id"> <generator class="&idgenerator;"/> </id>
Using the
IDENTITY
generator is not recommended if the underlying database supports sequences since you won’t be able to benefit from automatic batch inserts.
EagerFetchingEvent
The EagerFetchingEvent
is reported like this:
CRITICAL - EagerFetchingEvent - The [deliveryAddress] attribute in the [Shipment] entity uses eager fetching. Consider using lazy fetching, which not only that is more efficient, but is way more flexible when it comes to fetching data.
The deliveryAddress
association in the Shipment
entity is mapped like this:
<many-to-one name="deliveryAddress" class="AddressEntity" column="DELIVERY_ADDRESS_ID" not-null="true" update="false" fetch="join" foreign-key="FK_DELIVERY_ADDRESS_ID" />
Because the fetch
attribute is set to the join
strategy, this association is going to be fetched eagerly every time the Shipment
is loaded, even when we don’t need this association.
Using eager fetching mappings is not recommended because this can lead to very serious performance issues. Not only that the associations will be fetched even when they are not needed, but they can cause N+1 query issues as well.
For more details about this topic, check out this article.
BidirectionalSynchronizationEvent
The BidirectionalSynchronizationEvent
is reported like this:
CRITICAL - BidirectionalSynchronizationEvent - The [categorizedItems] bidirectional association in the [Item] entity requires both ends to be synchronized. Consider adding the [addCategorizedItem(CategorizedItem categorizedItem)] and [removeCategorizedItem(CategorizedItem categorizedItem)] synchronization methods.
Indeed, if you navigate the Item
, you’ll see that it doesn’t contain the addCategorizedItem
and removeCategorizedItem
methods.
Not synchronizing both sides of a bidirectional association is not recommended since Hibernate doesn’t guarantee that the entity state transitions will propagate properly.
For more details about this topic, check out this article.
Cool, right?
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
And there is more!
You can earn a significant passive income stream from promoting all these amazing products that I have been creating.
If you're interested in supplementing your income, then join my affiliate program.
Conclusion
Hypersistence Optimizer is a very versatile tool. Not only that it works with Spring Boot, Spring, Jakarta EE, Java EE, Play, or other frameworks, but you can use it even with legacy applications that are using very old Hibernate versions.
If you want to speed up your data access layer, Hypersistence Optimizer will surely help you achieve your goal. And, not only will it detect current issues, but you can use it to prevent those issues from ever happening again via an automated integration test that checks that no issue is being detected.
