How does AUTO flush strategy work in JPA and Hibernate

Introduction

Now that I described the the basics of JPA and Hibernate flush strategies, I can continue unraveling the surprising behavior of Hibernate’s AUTO flush mode.

Not all queries trigger a Session flush

Many would assume that Hibernate always flushes the Session before any executing query. While this might have been a more intuitive approach, and probably closer to the JPA’s AUTO FlushModeType, Hibernate tries to optimize that. If the current executed query is not going to hit the pending SQL INSERT/UPDATE/DELETE statements then the flush is not strictly required.

As stated in the reference documentation, the AUTO flush strategy may sometimes synchronize the current persistence context prior to a query execution. It would have been more intuitive if the framework authors had chosen to name it FlushMode.SOMETIMES.

JPQL/HQL and SQL

Like many other ORM solutions, Hibernate offers a limited Entity querying language (JPQL/HQL) that’s very much based on SQL-92 syntax.

The entity query language is translated to SQL by the current database dialect and so it must offer the same functionality across different database products. Since most database systems are SQL-92 complaint, the Entity Query Language is an abstraction of the most common database querying syntax.

While you can use the Entity Query Language in many use cases (selecting Entities and even projections), there are times when its limited capabilities are no match for an advanced querying request. Whenever we want to make use of some specific querying techniques, such as:

we have no other option, but to run native SQL queries.

Hibernate is a persistence framework. Hibernate was never meant to replace SQL. If some query is better expressed in a native query, then it’s not worth sacrificing application performance on the altar of database portability.

AUTO flush and HQL/JPQL

First we are going to test how the AUTO flush mode behaves when an HQL query is about to be executed. For this we define the following unrelated entities:

FlushAUTOEntities

The test will execute the following actions:

  • A Person is going to be persisted.
  • Selecting User(s) should not trigger a the flush.
  • Querying for Person, the AUTO flush should trigger the entity state transition synchronization (A person INSERT should be executed prior to executing the select query).
Product product = new Product();
session.persist(product);

assertEquals(
    0L,  
    session.createQuery("select count(id) from User")
    .uniqueResult()
);
assertEquals(
    product.getId(), 
    session.createQuery("select p.id from Product p")
    .uniqueResult()
);

Giving the following SQL output:

[main]: o.h.e.i.AbstractSaveEventListener - Generated identifier: f76f61e2-f3e3-4ea4-8f44-82e9804ceed0, using strategy: org.hibernate.id.UUIDGenerator
Query:{[select count(user0_.id) as col_0_0_ from user user0_][]} 
Query:{[insert into product (color, id) values (?, ?)][12,f76f61e2-f3e3-4ea4-8f44-82e9804ceed0]} 
Query:{[select product0_.id as col_0_0_ from product product0_][]}

As you can see, the User select hasn’t triggered the Session flush. This is because Hibernate inspects the current query space against the pending table statements. If the current executing query doesn’t overlap with the unflushed table statements, the a flush can be safely ignored.

HQL can detect the Product flush even for:

  • Sub-selects

    session.persist(product);
    assertEquals(0L,  session.createQuery(
        "select count(*) " +
        "from User u " +
        "where u.favoriteColor in (select distinct(p.color) from Product p)").uniqueResult());
    

    Resulting in a proper flush call:

    Query:{[insert into product (color, id) values (?, ?)][Blue,2d9d1b4f-eaee-45f1-a480-120eb66da9e8]} 
    Query:{[select count(*) as col_0_0_ from user user0_ where user0_.favoriteColor in (select distinct product1_.color from product product1_)][]}
    
  • Or theta-style joins

    session.persist(product);
    assertEquals(0L,  session.createQuery(
        "select count(*) " +
        "from User u, Product p " +
        "where u.favoriteColor = p.color").uniqueResult());
    

    Triggering the expected flush :

    Query:{[insert into product (color, id) values (?, ?)][Blue,4af0b843-da3f-4b38-aa42-1e590db186a9]} 
    Query:{[select count(*) as col_0_0_ from user user0_ cross join product product1_ where user0_.favoriteColor=product1_.color][]} 
    

The reason why it works is because Entity Queries are parsed and translated to SQL queries. Hibernate cannot reference a non existing table, therefore it always knows the database tables an HQL/JPQL query will hit.

So Hibernate is only aware of those tables we explicitly referenced in our HQL query. If the current pending DML statements imply database triggers or database level cascading, Hibernate won’t be aware of those. So even for HQL, the AUTO flush mode can cause consistency issues.

AUTO flush and native SQL queries

When it comes to native SQL queries, things are getting much more complicated. Hibernate cannot parse SQL queries, because it only supports a limited database query syntax. Many database systems offer proprietary features that are beyond Hibernate Entity Query capabilities.

Querying the Person table, with a native SQL query is not going to trigger the flush, causing an inconsistency issue:

Product product = new Product();
session.persist(product);
assertNull(
    session.createSQLQuery("select id from product")
    .uniqueResult()
);
DEBUG [main]: o.h.e.i.AbstractSaveEventListener - Generated identifier: 718b84d8-9270-48f3-86ff-0b8da7f9af7c, using strategy: org.hibernate.id.UUIDGenerator
Query:{[select id from product][]} 
Query:{[insert into product (color, id) values (?, ?)][12,718b84d8-9270-48f3-86ff-0b8da7f9af7c]} 

The newly persisted Product was only inserted during transaction commit, because the native SQL query didn’t triggered the flush. This is major consistency problem, one that’s hard to debug or even foreseen by many developers. That’s one more reason for always inspecting auto-generated SQL statements.

The same behaviour is observed even for named native queries:

@NamedNativeQueries(
    @NamedNativeQuery(name = "product_ids", query = "select id from product")
)
assertNull(
    session.getNamedQuery("product_ids")
    .uniqueResult()
);

So even if the SQL query is pre-loaded, Hibernate won’t extract the associated query space for matching it against the pending DML statements.

It’s worth noting that this behavior applies to Hibernate-specific API, and not to JPA AUTO flush mode.

Check out this article for more details.

Overruling the current flush strategy

Even if the current Session defines a default flush strategy, you can always override it on a query basis.

Query flush mode

The ALWAYS mode is going to flush the persistence context before any query execution (HQL or SQL). This time, Hibernate applies no optimization and all pending entity state transitions are going to be synchronized with the current database transaction.

assertEquals(
    product.getId(), 
    session.createSQLQuery("select id from product")
    .setFlushMode(FlushMode.ALWAYS)
    .uniqueResult()
);

Instructing Hibernate which tables should be synchronized

You could also add a synchronization rule on your current executing SQL query. Hibernate will then know what database tables need to be synchronized prior to executing the query. This is also useful for second level caching as well.

assertEquals(product.getId(), session.createSQLQuery("select id from product").addSynchronizedEntityClass(Product.class).uniqueResult());

If you enjoyed this article, I bet you are going to love my book as well.

Conclusion

The AUTO flush mode is tricky and fixing consistency issues on a query basis is a maintainer’s nightmare. If you decide to add a database trigger, you’ll have to check all Hibernate queries to make sure they won’t end up running against stale data.

My suggestion is to use the ALWAYS flush mode, even if Hibernate authors warned us that:

this strategy is almost always unnecessary and inefficient.

Inconsistency is much more of an issue that some occasional premature flushes. While mixing DML operations and queries may cause unnecessary flushing this situation is not that difficult to mitigate. During a session transaction, it’s best to execute queries at the beginning (when no pending entity state transitions are to be synchronized) and towards the end of the transaction (when the current persistence context is going to be flushed anyway).

The entity state transition operations should be pushed towards the end of the transaction, trying to avoid interleaving them with query operations (therefore preventing a premature flush trigger).

Enter your email address to follow this blog and receive notifications of new posts by email.

Advertisements

16 thoughts on “How does AUTO flush strategy work in JPA and Hibernate

  1. Interesting fact : if you use em.createNativeQuery – flush will be occured, because getSingleResult or getResultList invoke beforeQuery method, look at QueryImpl class, and there is a comment – For JPA native SQL queries, we may need to perform a flush before executing the query.
    if ( getEntityManager().isTransactionInProgress() ) {
    getEntityManager().flush();
    }

  2. I really like your posts! I always learn lots of things with them!

    I don’t see problems in using default flush mode (AUTO). It works very well for most of the cases and, as you well said, it’s recommended by Hibernate documentation. But if there’s any possibility of using native queries or even bulk operations then ALWAYS flush mode seems to be the best option – although you might change a little bit your code without changing flush mode.

    Anyway, when working with integration tests those types of concerns (inconsistency?) tend to decrease. What do you think?

    1. Thanks Rafael. It’s not bad if you know how it works. But I bet many are caught off-guard by this mechanism and might bump into consistency issues without even knowing what caused them.
      From a performance perspective, AUTO flush is actually a good thing.

      1. Yeah! You’re right!

        If we realize we can see the issue isn’t mainly related to Hibernate auto flush strategy but with the fact of most of developers do not know how persistence context (and dirty checking) works.

        Knowing how persistence context works is one of the most important concepts behind the learning of Hibernate and JPA. But normally people only care about mapping.

  3. Don’t you think there is a lack of FlushMode called AUTO_AND_NATIVE? It will optimize HQL (as it usually can do it correctly) but will flush for every native query, as not flushing here is much more worse than flushing for every query?

  4. Hi Vlad, great blog(s). I’m confused however…I’m currently on Hibernate 4.3.11, and the flush behaviour I see is that, even for an SQL-query, the persistence context is flushed before executing the query, therefore conflicting with your statements. I tested it within a transaction. See beforeQuery-method in org.hibernate.jpa.internal.QueryImpl. Has it changed along the way?

  5. Moreover, if you only use JPA (and never any native SQL), what’s a use case where you would want dirty checking and flushing within a transaction? The query results are always merged with the persistence context before returning them to the end user, right?! That would be a great performance booster, but am I missing something here?

    1. if you only use JPA (and never any native SQL)

      First of all, JPA is a means not a goal. In a non-trivial enterprise application, you need native SQL queries. Entity queries are useful only if you need to modify the selected entities in the currently running transaction, or in a subsequent one.

      The query results are always merged with the persistence context before returning them to the end user, right?!

      Only entities are managed, therefore get attached to the Persistence Context. DTO projections are not.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s