FlushMode in JPA and Hibernate – What it is and how to change it



Get access to all my video courses, 2 monthly Q&A calls, monthly coding challenges, a community of like-minded developers, and regular expert sessions.

Join the Persistence Hub!


The FlushMode defines when new entities and your changes on existing ones get written to the database. This might seem like a simple and obvious mechanism. But I recognized in my Q&A calls that it often causes some confusion because, by default, Hibernate doesn’t perform a flush when you call the persist method on your EntityManager, or the save method on your Spring Data JPA repository. It also doesn’t only perform a flush at the end of the transaction, even though this is an important and guaranteed trigger of a flush operation.

The specific flush handling depends on the combination of the configured FlushMode, the type of operations you’re executing, and some Hibernate-specific optimizations. I’ll explain all of that in this article.

FlushModes supported by JPA and Hibernate

The JPA specification only defines the FlushModeTypes AUTO and COMMIT. Hibernate extends this by the FlushModeTypes ALWAYS and MANUAL. Let’s take a closer look at all 4 modes.

FlushModeType.AUTO (JPA & Hibernate)

The JPA specification defines the FlushModeType.AUTO as the default flush mode. It flushes the persistence context in 2 situations:

  • before the transaction gets committed and
  • before executing a query that uses any database table for which your persistence context contains any pending changes.

Flushing the persistence context before the transaction gets committed should be obvious and not require any deeper explanation. But the 2nd situation is a little more complex and requires you to know how Hibernate determines which database tables a query affects. This is based on the query space I explained in a previous article.

For every JPQL or Criteria Query, Hibernate generates the SQL statement. It, therefore, knows which database tables are used in the query. Hibernate can use that when performing a dirty check on all entity objects in the current persistence context. If it finds a dirty entity mapped to one of the tables referenced by the query, it needs to flush these changes to the database.

Detecting the query space

Here you can see a simple example that persists a new ChessPlayer entity before it selects all ChessTournament entities in the first and the ChessPlayer with the firstName Magnus in the 2nd query.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

ChessPlayer player = new ChessPlayer();
player.setFirstName("Thorben");
player.setLastName("Janssen");
em.persist(player);

em.createQuery("SELECT t from ChessTournament t").getResultList();

Query q = em.createQuery("SELECT p FROM ChessPlayer p WHERE p.firstName = :firstName");
q.setParameter("firstName", "Magnus");
q.getResultList();

em.getTransaction().commit();
em.close();

As you can see in the log output, Hibernate doesn’t flush the new ChessPlayer entity before it executes the query that selects all ChessTournament entities. That query doesn’t reference the ChessPlayer table, and the new ChessPlayer will not affect the query result. Hibernate can, therefore, delay the insert statement’s execution, which might create some performance benefits.

11:56:14,076 DEBUG SQL:144 - select nextval ('player_seq')
11:56:14,085 DEBUG SQL:144 - select nextval ('player_seq')
11:56:14,188 DEBUG SQL:144 - select chesstourn0_.id as id1_2_, chesstourn0_.endDate as enddate2_2_, chesstourn0_.name as name3_2_, chesstourn0_.startDate as startdat4_2_, chesstourn0_.version as version5_2_ from ChessTournament chesstourn0_
11:56:14,213 DEBUG SQL:144 - insert into ChessPlayer (birthDate, firstName, lastName, version, id) values (?, ?, ?, ?, ?)
11:56:14,219 DEBUG SQL:144 - select chessplaye0_.id as id1_1_, chessplaye0_.birthDate as birthdat2_1_, chessplaye0_.firstName as firstnam3_1_, chessplaye0_.lastName as lastname4_1_, chessplaye0_.version as version5_1_ from ChessPlayer chessplaye0_ where chessplaye0_.firstName=?

But that changes when Hibernate selects a ChessPlayer with the firstName Magnus. That JPQL query references the ChessPlayer table, and Hibernate doesn’t know if the new ChessPlayer entity will affect the query result. It, therefore, flushes the entity and generates an SQL INSERT statement for it before it executes the query.

It gets a little more complex if you execute a native SQL query. As I explained in my guide to Hibernate’s query spaces, Hibernate can’t determine which tables a native SQL query uses. You should, therefore, register the query space for each native query. Otherwise, Hibernate isn’t able to decide if it needs to flush the persistence context. It, therefore, flushes the persistence context to ensure that the query returns the correct results.

You can see an example of this in the following code snippet. This time, I persist a new ChessPlayer entity and execute 2 native SQL queries that select all ChessTournament entities. For the 1st one, I register the ChessTournament entity as the query space. And for the 2nd one, I don’t register the query space.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

ChessPlayer player = new ChessPlayer();
player.setFirstName("Thorben");
player.setLastName("Janssen");
em.persist(player);

// with defined query space
Query q = em.createNativeQuery("SELECT * from ChessTournament", ChessTournament.class);
q.unwrap(SynchronizeableQuery.class).addSynchronizedEntityClass(ChessTournament.class);
q.getResultList();

// without query space
em.createNativeQuery("SELECT * from ChessTournament", ChessTournament.class).getResultList();

em.getTransaction().commit();
em.close();

As you can see in the log output, the 1st quey doesn’t trigger a flush of the new ChessPlayer entity. Hibernate checked the query space and knew that the new ChessPlayer entity isn’t relevant for this native query.

But the 2nd query triggered a flush. This is the same query statement as the previous one, but I didn’t register the query space. Due to that, Hibernate didn’t know if the query referenced the ChessPlayer table and had to flush the new entity.

12:01:38,984 DEBUG SQL:144 - select nextval ('player_seq')
12:01:38,992 DEBUG SQL:144 - select nextval ('player_seq')
12:01:39,037 DEBUG SQL:144 - SELECT * from ChessTournament
12:01:39,058 DEBUG SQL:144 - insert into ChessPlayer (birthDate, firstName, lastName, version, id) values (?, ?, ?, ?, ?)
12:01:39,066 DEBUG SQL:144 - SELECT * from ChessTournament

FlushModeType.COMMIT (JPA & Hibernate)

The FlushModeType.COMMIT requires a flush before committing the transaction but doesn’t define what needs to happen before executing a query. When using Hibernate 5 or 6, executing any query doesn’t flush any pending changes.

You can see that in the following example that persists a new ChessPlayer before selecting all ChessPlayer entities from the database.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

ChessPlayer player = new ChessPlayer();
player.setFirstName("Thorben");
player.setLastName("Janssen");
em.persist(player);

List<ChessPlayer> players = em.createQuery("SELECT p from ChessPlayer p").getResultList();
for (ChessPlayer p : players) {
	log.info(p);
}

em.getTransaction().commit();
em.close();

The JPQL query that selects all ChessPlayer entities doesn’t cause a flush of the newly persisted ChessPlayer entity. Due to that, the player isn’t part of the query result.

12:14:17,117 DEBUG SQL:144 - select nextval ('player_seq')
12:14:17,125 DEBUG SQL:144 - select nextval ('player_seq')
12:14:17,225 DEBUG SQL:144 - select chessplaye0_.id as id1_1_, chessplaye0_.birthDate as birthdat2_1_, chessplaye0_.firstName as firstnam3_1_, chessplaye0_.lastName as lastname4_1_, chessplaye0_.version as version5_1_ from ChessPlayer chessplaye0_
12:14:17,241  INFO TestSample:96 - ChessPlayer [id=1, firstName=Magnus, lastName=Carlsen, birthDate=1990-09-30, version=0]
12:14:17,241  INFO TestSample:96 - ChessPlayer [id=2, firstName=Jorden, lastName=van Foreest, birthDate=1999-04-30, version=0]
12:14:17,241  INFO TestSample:96 - ChessPlayer [id=3, firstName=Anish, lastName=Giri, birthDate=1994-06-28, version=0]
12:14:17,241  INFO TestSample:96 - ChessPlayer [id=4, firstName=Fabiano, lastName=Caruana, birthDate=1992-07-30, version=0]
12:14:17,249 DEBUG SQL:144 - insert into ChessPlayer (birthDate, firstName, lastName, version, id) values (?, ?, ?, ?, ?)

FlushModeType.ALWAYS (Hibernate)

The FlushModeType.ALWAYS is Hibernate-specific and tells Hibernate to flush the persistence context before executing a query. Using this mode, Hibernate doesn’t check if the flush is required and handles all types of queries in the same way.

In the following example, I persist a new ChessPlayer entity before selecting all ChessTournament entities from the database.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

ChessPlayer player = new ChessPlayer();
player.setFirstName("Thorben");
player.setLastName("Janssen");
em.persist(player);

em.createQuery("SELECT t from ChessTournament t").getResultList();

em.getTransaction().commit();
em.close();

The new player doesn’t change the query result, and the flush wouldn’t be necessary. But because I configured FlushModeType.ALWAYS, Hibernate flushes the persistence context anyways.

12:29:41,306 DEBUG SQL:144 - select nextval ('player_seq')
12:29:41,318 DEBUG SQL:144 - select nextval ('player_seq')
12:29:41,449 DEBUG SQL:144 - insert into ChessPlayer (birthDate, firstName, lastName, version, id) values (?, ?, ?, ?, ?)
12:29:41,459 DEBUG SQL:144 - select chesstourn0_.id as id1_2_, chesstourn0_.endDate as enddate2_2_, chesstourn0_.name as name3_2_, chesstourn0_.startDate as startdat4_2_, chesstourn0_.version as version5_2_ from ChessTournament chesstourn0_

FlushModeType.MANUAL (Hibernate)

The FlushModeType.MANUAL is the 2nd Hibernate-specific mode. It deactivates all automatic flushes and requires the application to trigger the flushes automatically.

I use this in the following example when persisting a new ChessPlayer, selecting all ChessPlayer entities from the database and flushing the persistence context.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

ChessPlayer player = new ChessPlayer();
player.setFirstName("Thorben");
player.setLastName("Janssen");
em.persist(player);

List<ChessPlayer> players = em.createQuery("SELECT p from ChessPlayer p").getResultList();
for (ChessPlayer p : players) {
	log.info(p);
}
		
em.flush();
em.getTransaction().commit();
em.close();

I deactivated all automatic flushes, and the JPQL query no longer caused a flush of the newly persisted ChessPlayer entity. Due to that, the player isn’t part of the query result and doesn’t get flushed until I call the EntityManager.flush() method.

14:50:16,552 DEBUG SQL:144 - select nextval ('player_seq')
14:50:16,559 DEBUG SQL:144 - select nextval ('player_seq')
14:50:16,652 DEBUG SQL:144 - select chessplaye0_.id as id1_1_, chessplaye0_.birthDate as birthdat2_1_, chessplaye0_.firstName as firstnam3_1_, chessplaye0_.lastName as lastname4_1_, chessplaye0_.version as version5_1_ from ChessPlayer chessplaye0_
14:50:16,669  INFO TestSample:135 - ChessPlayer [id=1, firstName=Magnus, lastName=Carlsen, birthDate=1990-09-30, version=0]
14:50:16,669  INFO TestSample:135 - ChessPlayer [id=2, firstName=Jorden, lastName=van Foreest, birthDate=1999-04-30, version=0]
14:50:16,669  INFO TestSample:135 - ChessPlayer [id=3, firstName=Anish, lastName=Giri, birthDate=1994-06-28, version=0]
14:50:16,669  INFO TestSample:135 - ChessPlayer [id=4, firstName=Fabiano, lastName=Caruana, birthDate=1992-07-30, version=0]
14:50:16,678 DEBUG SQL:144 - insert into ChessPlayer (birthDate, firstName, lastName, version, id) values (?, ?, ?, ?, ?)

I don’t recommend using FlushModeType.MANUAL. It creates a high risk that you miss flushing some changes to the database or that some of your queries don’t use the latest changes.

Some teams try to use it to improve the performance of their persistence layer. But in almost all cases, delaying the flush operation only improves the performance if you implemented your persistence layer in the wrong way. It only hides performance issues instead of solving it.

Considering the high risks this FlushMode introduces, I recommend fixing these performance problems instead of hiding them. This usually doesn’t require more work than implementing and testing the manual flush handling.

How to configure the FlushMode

You can configure the flush mode globally or set it for each database query. This enables you to define a standard flush mode for your application and override it for a specific query.

You can set the standard flush mode for your application by configuring the property org.hibernate.flushMode in your persistence.xml file. By default, the FlushMode is set to AUTO, and I recommend you DON’T change it.

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

        <properties>
            <property name="org.hibernate.flushMode" value="COMMIT"/>
			
			...
        </properties>
    </persistence-unit>
</persistence>

The way you can configure a query-specific flush mode depends on the FlushModeType you want to set. If you want to use the FlushModeTypes AUTO or COMMIT, which are defined by the JPA specification, you can call the setFlushMode method on your Query or TypedQuery interface.

Query q = em.createQuery("SELECT p from ChessPlayer p");
q.setFlushMode(FlushModeType.COMMIT);
q.getResultList();

And if you want to use a Hibernate-specific flush mode, you need to use Hibernate’s Session to create your query and call its setHibernateFlushMode method.

Query q = em.createQuery("SELECT p from ChessPlayer p");
q.unwrap(org.hibernate.query.Query.class).setHibernateFlushMode(FlushMode.ALWAYS);
q.getResultList();

Triggering flush programmatically

As you already saw in the section on FlushModeType.MANUAL, you can trigger a flush programmatically by calling the EntityManager.flush method.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

Query q = em.createQuery("SELECT p from ChessPlayer p");
q.setFlushMode(FlushModeType.COMMIT);
q.getResultList();

// trigger a flush		
em.flush();

em.getTransaction().commit();
em.close();

If you’re using FlushModeType AUTO or COMMIT, this should only be necessary if you use a JPQL or Criteria query to perform a bulk update or remove operations. Hibernate’s automatic flush handling is easier to use and more efficient in all other cases.

Conclusion

The FlushMode defines when your persistence provider flushes new and changed entities to the database.

Based on the JPA specification, it can either do that automatically before executing a query and before committing the transaction (FlushModeType.AUTO) or only before committing the transaction (FlushModeType.COMMIT).

Hibernate supports 2 additional FlushModes that you can use to flush the persistence context before every query (FlushModeType.ALWAYS)or to manage the flushes programmatically and deactivate all automatic flushes (FlushModeType.MANUAL).

I recommend using FlushModeType.AUTO for all persistence layers. It avoids the risk that you might miss flushing a pending change or that a query works on outdated data. And Hibernate provides its own optimization, based on the query space, to avoid any unnecessary flushes.

Related Articles

Responses

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.