A beginner’s guide to Hibernate flush operation order

(Last Updated On: December 5, 2018)

Introduction

As explained in this article, Hibernate shifts the developer mindset from SQL to entity state transitions. A JPA entity may be in one of the following states:

  • New/Transient: the entity is not associated with a persistence context, be it a newly created object the database doesn’t know anything about.
  • Persistent: the entity is associated with a persistence context (residing in the 1st Level Cache) and there is a database row representing this entity.
  • Detached: the entity was previously associated with a persistence context, but the persistence context was closed, or the entity was manually evicted.
  • Removed: the entity was marked as removed and the persistence context will remove it from the database at flush time.

Moving an object from one state to another is done by calling the EntityManager methods such as:

  • persist
  • merge
  • remove

Cascading allows propagating a given event from a parent to a child, also easing managing entities relationship management.

During the flush time, Hibernate will translate the changes recorded by the current Persistence Context into SQL queries.

Domain Model

Now, let’s consider we have the following entity:

@Entity(name = "Post")
@Table(
    name = "post",
    uniqueConstraints = @UniqueConstraint(
        name = "slug_uq",
        columnNames = "slug"
    )
)
public class Post {

    @Id
    private Long id;

    private String title;

    @NaturalId
    private String slug;

    //Getters and setters omitted for brevity
}

Notice the slug property is marked with the @NaturalId annotation since this represents a business key.

Now, let’s consider we have persisted the following Post entity in our database:

Post post = new Post();
post.setId(1L);
post.setTitle("High-Performance Java Persistence");
post.setSlug("high-performance-java-persistence");

entityManager.persist(post);

Testing time

Let’s assume you want to remove the existing Post entity and persist a new instance with the same slug attribute:

Post post = entityManager.find(Post.class, 1L);
entityManager.remove(post);

Post newPost = new Post();
newPost.setId(2L);
newPost.setTitle("High-Performance Java Persistence Book");
newPost.setSlug("high-performance-java-persistence");
entityManager.persist(newPost);

If you try to do that, Hibernate will throw the following exception:

Query:["insert into post (slug, title, id) values (?, ?, ?)"], 
Params:[(high-performance-java-persistence, High-Performance Java Persistence Book, 2)]

-- SQL Error: -104, SQLState: 23505
-- integrity constraint violation: unique constraint or index violation; SLUG_UQ table: POST

Hibernate did not execute the DELETE first as we did in our test case. It executed the INSERT statement first, and that’s why we get the ConstraintviolationException.

You may wonder why this is happening since we are calling remove prior to adding the second post entity, and the answer is flush operations order.

Every entity state transition generates an action which is enqueued by the Persistence Context. You can see all the action queues in the ActionQueue class which also provides the order of all the operations happening at flush time:

  • OrphanRemovalAction
  • AbstractEntityInsertAction
  • EntityUpdateAction
  • QueuedOperationCollectionAction
  • CollectionRemoveAction
  • CollectionUpdateAction
  • CollectionRecreateAction
  • EntityDeleteAction

So, the DELETE statements are executed right at the end of the flush while the INSERT statements are executed towards the beginning.

A hacky workaround

One way to work around this issue is to manual flush the Persistence Context after the remove operation:

Post post = entityManager.find(Post.class, 1L);
entityManager.remove(post);

entityManager.flush();

Post newPost = new Post();
newPost.setId(2L);
newPost.setTitle("High-Performance Java Persistence Book");
newPost.setSlug("high-performance-java-persistence");
entityManager.persist(newPost);

This will output the desired behavior:

Query:["delete from post where id=?"], 
Params:[(1)]

Query:["insert into post (slug, title, id) values (?, ?, ?)"], 
Params:[(high-performance-java-persistence, High-Performance Java Persistence Book, 2)]

The proper fix

However, just because we can flush the Persistence Context manually, it does not mean this is the right way to do it.

A manual flush call is a code smell. In reality, you are better off updating the existing entity instead of removing and reinserting it back with the same business key:

Post post = entityManager.unwrap(Session.class)
.bySimpleNaturalId(Post.class)
.load("high-performance-java-persistence");

post.setTitle("High-Performance Java Persistence Book");

Te UPDATE will leave the index entries as is while the remove-and-insert operation will incur additional work on the database side because both the table record and all index entries (Primary Key, the secondary index for slug) must be removed only to be re-added back.

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

Conclusion

Knowing the flush operation order is very important when using JPA and Hibernate. Because Hibernate executes the SQL statements in a strict order, JDBC batching can be applied automatically.

If you think you need to flush the Persistence Context manually, think twice. You might have a remove-then-insert use case which is better handled by a single entity update instead.

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!

Advertisements

6 thoughts on “A beginner’s guide to Hibernate flush operation order

  1. Hi,

    Thanks for the detailed explanation. I’m just wondering why hibernate keeps order of operations happening at flush time like that? The java doc says ‘Execute all SQL (and second-level cache updates) in a special order so that foreign-key constraints cannot be violated’. Would you mind elaborating more on that?

    Best Regards,
    Jo

    1. Every time you do inserts and deletes automatically, you need a certain order because of the parent-child cascading concerns.

      1. Yes, but I m just wondering why hibernate won’t execute the actions(inserts/updates/deletes) chronologically?

      2. Because if it did that, you’d get ConstraintViolationExceptions. I don’t have any particular example for that, but you could try to modify Hibernate to see what exactly could happen if we did it that way.

  2. Hello,

    I have an entity which has as primary keys 3 fields and I need to modify one of them. Just calling merge is not possible as it is part of the id. What i did was to remove the entity, call flush and then persist it.
    And as this is part of migrating more entities with the old id to a new id, after the persist i realised I need another flush.

    The code looks like this:

    for (BasicKeyDAO entity : entities) {
    	hzmUnitEntityManager.remove(entity);
    	hzmUnitEntityManager.flush();
    	
    	entity.getKey()).setOrganizationId(targetOrganization.getOrganizationId());
    	
    	hzmUnitEntityManager.persist(entity);
    	//without this flush the entity is not persisted
    	hzmUnitEntityManager.flush();
    }
    

    Can you please tell me the correct way to do this?

    best regards,
    Maria

    1. Entity identifiers should be immutable, therefore, you should not change them. Now, I don’t think you need to call flush() since there shouldn’t be any conflict between the old and the new entities. So, your code should be changed to:

      for (BasicKeyDAO entity : entities) {
          hzmUnitEntityManager.remove(entity);
          
      	BasicKeyDAO newEntity = createNewEntity(entity);
           
          hzmUnitEntityManager.persist(newEntity);
      }
      

      If this is executed from a @Transactional service, you should not call flush() at all since the commit will call flush anyway.

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.