Follow @vlad_mihalcea Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. Hypersistence Optimizer is that tool!

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.

: 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.

: 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.

: 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.

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.

Insert details about how the information is going to be processed DOWNLOAD NOW