How do JPA and Hibernate define the AUTO flush mode
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
The Persistence Context acts as a transactional-write behind cache for the incoming entity state transitions, and all changes are synchronized with the database during flushing.
Although both the Hibernate Session
and the JPA EntityManager
define a flush()
method to manually trigger this process, it’s much more convenient to let Hibernate manage the Persistence Context flushing. Unfortunately, there’s is a major difference between how JPA and Hibernate define the automatic flushing mechanism.
When using the default AUTO
flush mode, the Persistence Context should be flushed as follows:
- before the transaction is committed,
- before running a JPQL or HQL query
- before executing a native SQL query
As previously explained, Hibernate triggers the AUTO
flush only for the first two events, and the native SQL queries must either override the ALWAYS
flush mode using the Query#setFlushMode(FlushMode flushMode)
method or add a table space synchronization (e.g. SQLQuery#addSynchronizedEntityClass(Class entityClass)
, SQLQuery#addSynchronizedEntityName(String entityName)
, SQLQuery#addSynchronizedQuerySpace(String querySpace)
).
This is only required for the native Hibernate API when using a
Session
explicitly.Since Hibernate 5.2, if you bootstrap Hibernate using JPA (e.g.
persistence.xml
), then even the HibernateFlushType.AUTO
will behave just like its JPA counterpart.
Only if you bootstrap Hibernate using the native mechanism, will the HibernateSession
use the legacyFlushType.AUTO
behavior.
JPA AUTO flushing
JPA is more strict, and the AUTO
flush mode must trigger a flush before any query. More the section 3.10.8
of the Java Persistence API specification says that the AUTO
flush mode should ensure that all pending changes are visible by any executing query.
This can be demonstrated by executing the following method:
assertTrue(((Number) entityManager .createNativeQuery("select count(*) from Post") .getSingleResult()).intValue() == 0); Post post = new Post("Hibernate"); post.setId(1L); entityManager.persist(post); assertTrue(((Number) entityManager .createNativeQuery("select count(*) from Post") .getSingleResult()).intValue() == 1);
When running this test, Hibernate generates the following SQL statements:
SELECT COUNT(*) FROM Post INSERT INTO post (title, version, id) VALUES ('Hibernate', 0, 1) SELECT COUNT(*) FROM Post
So, the flush was triggered and the INSERT
statement was executed prior to running the SELECT
statement.
This doesn’t happens when using a Session
:
assertTrue(((Number) entityManager .createQuery("select count(*) from Post") .getSingleResult()).intValue() == 0); Post post = new Post("Hibernate"); post.setId(1L); entityManager.persist(post); Session session = entityManager.unwrap(Session.class); assertTrue(((Number) session .createSQLQuery("select count(*) from Post") .uniqueResult()).intValue() == 0);
This time, Hibernate generates the following statements:
SELECT COUNT(*) FROM Post SELECT COUNT(*) as col_0_0_ FROM post blogentity0_ INSERT INTO post (title, version, id) VALUES ('Hibernate', 0, 1)
I'm running an online workshop on the 11th of October about High-Performance SQL.If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
Flushing is a very important concept for any ORM too, and Hibernate is no different.
As a rule of thumb, it’s better to make sure that native SQL queries don’t return inconsistent results when using the Hibernate Session
API.
