Sunday, March 3, 2013

Hibernate flush modes

    Hibernate minimizes the trips to the database in order to gain some performance. It is the flush mode that controls how many times Hibernate hits the DB when it comes to write operations. The behaviour of the flushing mechanism is customizable. It is worth to know that the proper configuration of the flush mode can have quite a big influence on the overall performance (especially while testing when the transaction is not committed).
There are four types of FlushMode in Hibernate:
MANUAL, COMMIT, AUTO, ALWAYS.

I have created the following code snippet (placed within EJB) to present differences between them:
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
 public void flushModeTest() {
  log.info("Setting FlushMode to: " + FlushMode.MANUAL);
  entityManager.unwrap(Session.class).setFlushMode(FlushMode.MANUAL);
  
  Human h = new Human();
  h.setAnimal(new Animal());

  entityManager.persist(h);
  log.info("Persist invoked");
  
  log.info("Query for animals");
  List<Animal> animals = entityManager.createQuery("from Animal", Animal.class).getResultList();
  log.info("There are animals persisted: " + animals);
}
The effect of code execution is as follows:
 Setting FlushMode to: MANUAL
 Persist invoked
 Query for animals
 Hibernate: 
     select
         animal0_.animal_id as animal1_49_,
         animal0_.animal_name as animal2_49_ 
     from
         animal animal0_
 There are animals persisted: []
Despite the fact that we are using Container Managed Transactions (CMT) and Hibernate session is associated with JTA transaction - Human and Animal were not saved into the DB after transaction (TX) commit. We have a full control over flushing - nothing was flushed, nothing is committed. What is more, we have used JPQL query to ask DB about Animals. Such query does not use internal cache so the results are picked up from the DB. Again, nothing was flushed, nothing is visible - even within the same TX. We can invoke entityManager.flush() after entityManager.persist(h) and then the Animal entity will be found by JPQL query and Human with Animal will be saved into the DB after TX commit.
Next, I changed FlushMode to COMMIT. The result:
 Setting FlushMode to: COMMIT
 Persist invoked
 Query for animals
 Hibernate: 
     select
         animal0_.animal_id as animal1_67_,
         animal0_.animal_name as animal2_67_ 
     from
         animal animal0_
 There are animals persisted: []
 Hibernate: 
     insert 
     into
         animal
         (animal_name, animal_id) 
     values
         (?, ?)
 Hibernate: 
     insert 
     into
         human
         (animal_id, human_name, human_id) 
     values
         (?, ?, ?)
Hibernate flushes the data just before TX commit. It is quite intuitive.
The third flush mode is AUTO:
 Setting FlushMode to: AUTO
 Persist invoked
 Query for animals
 Hibernate: 
     insert 
     into
         animal
         (animal_name, animal_id) 
     values
         (?, ?)
 Hibernate: 
     insert 
     into
         human
         (animal_id, human_name, human_id) 
     values
         (?, ?, ?)
 Hibernate: 
     select
         animal0_.animal_id as animal1_73_,
         animal0_.animal_name as animal2_73_ 
     from
         animal animal0_
 There are animals persisted: [entity.Animal@48b5a02f]
AUTO mode is the most interesting one. Hibernate flushes the data just before JPQL query in order to reflect the real state - we have invoked entityManager.persist(h) so we believe that JPQL query should return persisted entities - we are not interested that Hibernate decided to defer the flushing event.
Even more interesting is another situation. I have modified the code a little:
Human h = new Human();
entityManager.persist(h);
log.info("Persist invoked");
As you can see, Animal entity is not persisted now. The result of the code execution:
 Setting FlushMode to: AUTO
 Persist invoked
 Query for animals
 Hibernate: 
     select
         animal0_.animal_id as animal1_80_,
         animal0_.animal_name as animal2_80_ 
     from
         animal animal0_
 There are animals persisted: []
 Hibernate: 
     insert 
     into
         human
         (animal_id, human_name, human_id) 
     values
         (?, ?, ?)
Hibernate is able to predict if flushing is necessary. That is a cool feature! The flushing occurred just before TX commit. I believe that Hibernate has sophisticated mechanism that allows to determine if flushing is necessary - it comes into play with complex queries.

The last flush mode is ALWAYS:
 Setting FlushMode to: ALWAYS
 Persist invoked
 Query for animals
 Hibernate: 
     insert 
     into
         human
         (animal_id, human_name, human_id) 
     values
         (?, ?, ?)
 Hibernate: 
     select
         animal0_.animal_id as animal1_92_,
         animal0_.animal_name as animal2_92_ 
     from
         animal animal0_
 There are animals persisted: []
Now, Hibernate does not take into account the fact that there is no use flushing because JPQL query does not correlate with persisted object. The main difference between AUTO and ALWAYS modes is that the first one flushes right before JPQL query only when it is necessary, whereas the latter one flushes right before JPQL query without any condition.

I hope that this blog entry shed some light on the flushing modes in Hibernate. Use them with caution! :)

1 comment :

  1. This is really very precise and useful. Thanks for sharing.

    ReplyDelete