9 High-Performance Tips when using MySQL with JPA and Hibernate

(Last Updated On: June 6, 2018)


Although there is an SQL Standard, every relational database is ultimately unique, and you need to adjust your data access layer so that you get the most out of the relational database in use.

In this article, we are going to see what you can do to boost up performance when using MySQL with JPA and Hibernate.

Don’t use the AUTO identifier GeneratorType

Every entity needs to have an identifier which uniquely identifies the table record associated with this entity. JPA and Hibernate allow you to automatically generate entity identifiers based on three different strategies:


As I explained in this article, The TABLE identifier strategy does not scale when increasing the number of database connections. More, even for one database connection, the identifier generation response time is 10 times greater than when using IDENTITY or SEQUENCE.

If you are using the AUTO GenerationType:

@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

Hibernate 5 is going to fall back to using the TABLE generator, which is bad for performance.

As I explained in this article, you can easily fix this issue with the following mapping:

@GeneratedValue(strategy= GenerationType.AUTO, generator="native")
@GenericGenerator(name = "native", strategy = "native")
private Long id;

The native generator will pick IDENTITY instead of TABLE.

IDENTITY generator disables JDBC batch inserts

Neither MySQL 5.7 or 8.0 support SEQUENCE objects, you need to use IDENTITY. However, as I explained in this article, the IDENTITY generator prevents Hibernate from using JDBC batch inserts.

JDBC batch updates and deletes are not affected, only the INSERT statements cannot be batched because, by the time the Persistence Context is flushed, the INSERT statements were already executed so that Hibernate knows what entity identifier to assign to the entities that got persisted.

If you want to fix this issue, you have to execute the JDBC batch inserts with a different framework, like jOOQ.

Speed-up integration testing with Docker and tmpfs

MySQL and MariaDB are notoriously slow when having to discard the database schema and recreating it every time a new integration test is about to run. However, you can easily address this issue with the help of Docker and tmpfs.

As I explained in this article, by mapping the data folder in-memory, integration tests are going to run almost as fast as with an in-memory database like H2 or HSQLDB.

Use JSON for non-structured data

Even when you are using a RDBMS, there are many times when you want to store non-structured data:

  • data coming from the client as JSON, which needs to be parsed and inserted into our system.
  • image processing results which can be cached to save reprocessing them

Although not supported natively, you can easily map a Java object to a JSON column. You can even map the JSON column type to a Jackson JsonNode.

More, you don’ even have to write these custom types, you can just grab them from Maven Central:


Cool, right?

Use Stored Procedures to save database roundtrips

When processing large volumes of data, it’s not very efficient to move all this data in and out of the database. It’s much better to do the processing on the database side by calling a Stored Procedure.

For more details, check out this article about how you can call a MySQL Stored Procedure with JPA and Hibernate.

Watch out for ResultSet streaming

SQL streaming makes sense for two-tier applications. If you want to do ResultSet streaming, you have to pay attention to the JDBC Driver as well. On MySQL, you need to set the Statement fetch size to Integer.MIN_VALUE.

However, for web-based applications, pagination is much more suitable. JPA 2.2 even introduces support for Java 1.8 Stream methods, but the Execution Plan might not be as efficient as when using SQL-level pagination.

PreparedStatements might be emulated

Although you might that, since Hibernate uses PreparedStatements by default, all statements are executed like this:

In reality, they are executed more like this:

As I explained in this article unless you set the useServerPrepStmts MySQL JDBC Driver property, PreparedStatements are going to be emulated at the JDBC Driver level to save one extra database roundtrip.

Always end database transactions

In a relational database, every statement is executed within a given database transaction. Therefore, transactions are not optional.

However, you should always end the current running transaction, either through a commit or a rollback. Forgetting to end transactions can lead to locks being held for a very long time, as well as preventing the MVCC cleanup process from reclaiming old tuples or index entries that are no longer needed.

Handing Date/Time is not that easy

There are two very complicated things in programming:

  • handling encodings
  • handing Date/Time across multiple Timezones

To address the second problem, it’s better to [save all timestamps in UTC timezone]https://vladmihalcea.com/how-to-store-date-time-and-timestamps-in-utc-time-zone-with-jdbc-and-hibernate/). However, when using MySQL, you also need to set the useLegacyDatetimeCode JDBC Driver configuration property to false.

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


As you can see, there are many things to keep in mind when using MySQL with JPA and Hibernate. Since MySQL is one of the most deployed RDBMS, being used by the vast majority of web applications, it’s very useful to know all these tips and adjust your data access layer to get the most out of it.

Subscribe to our Newsletter

* indicates required
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. 
Get the most out of your persistence layer!


4 thoughts on “9 High-Performance Tips when using MySQL with JPA and Hibernate

  1. I have used the artificial primary key in my entity (which I generate manually in the code)

    Now I have a scenario where I have two JVM processes.
    one JVM process fetches 10 to 20K entities from the database, make some changes and push them in the cache.
    Another JVM process gets the records from the cache and then save all records in the database.

    In this case, I could see individual select queries are getting fired for each entity (for 10k records 10k selects are getting fired) via hibernate merge and then a batch update is fired.

    How could I optimize this process of saving? Is there something called batch select? Ideally, hibernate merge should fire 1 query to merge the data and the batch update.

    1. Use session.update istead of entityManager.merge and you won’t get the extra SELECT queries.

      1. I tried using session.update
        I have 2 confusions:

        1 – as I understood, I would be able to use session.update only if I know I need to do update operation, not save (JPA save takes care of this by itself) right?

        2- now if I check my case that only update needs to be done. Now batching is not happening. I could see individual update queries getting fired.

        How to optimize this?

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.