A beginner’s guide to Hibernate flush operation order
Are you struggling with performance issues in your Spring, Jakarta EE, or Java EE application?
What if there were a tool that could automatically detect what caused performance issues in your JPA and Hibernate data access layer?
Wouldn’t it be awesome to have such a tool to watch your application and prevent performance issues during development, long before they affect production systems?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, Micronaut, or Play Framework.
So, rather than fixing performance issues in your production system on a Saturday night, you are better off using Hypersistence Optimizer to help you prevent those issues so that you can spend your time on the things that you love!
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:
persistmergeremove
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:
OrphanRemovalActionAbstractEntityInsertActionEntityUpdateActionQueuedOperationCollectionActionCollectionRemoveActionCollectionUpdateActionCollectionRecreateActionEntityDeleteAction
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");
The 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.






