DEV Community

Cover image for How is your data actually flushed? Hibernate ActionQueue event priorities.
Yevhenii Kukhol
Yevhenii Kukhol

Posted on

How is your data actually flushed? Hibernate ActionQueue event priorities.

We have a problem🤔

Imagine you're working with a default project using Spring Data JPA. Here's the entity in question:

@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class Person {
    private static final String GENERATOR = "person_generator";

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = GENERATOR)
    @SequenceGenerator(name = GENERATOR, sequenceName = "person_sequence")
    private Long id;
    private String name;
    @Column(unique = true)
    private String email;
}
Enter fullscreen mode Exit fullscreen mode

What's the output of the following code?

    @Transactional
    public void someImportantOperation(Long personId) {
        var oldPerson = personRepository.findById(personId)
                .orElseThrow();
        var oldPersonEmail = oldPerson.getEmail();

        personRepository.delete(oldPerson);

        var newPerson = new Person(
                null, "New Person With The Same Email", oldPersonEmail
        );

        personRepository.save(newPerson);
    }
Enter fullscreen mode Exit fullscreen mode

1... 2... 3...
Exception!

ERROR: duplicate key value violates unique constraint "person_email_uniq"
  Detail: Key (email)=(mail@gmail.com) already exists.
Enter fullscreen mode Exit fullscreen mode

Q:
But why? I've just deleted the oldPerson!
A:
It's because Hibernate executes SQL in a specific order!😎

Explanation

Try to delve into Hibernate's code: in methods like Session.persist, Session.remove, and others. For example in org.hibernate.internal.SessionImpl#delete you will find:

    @Override
    public void delete(Object object) throws HibernateException {
        checkOpen();
        fireDelete( new DeleteEvent( object, this ) );
    }
Enter fullscreen mode Exit fullscreen mode

No actual SQL is constructed or executed! Hibernate simply triggers a DeleteEvent!
The same happens with ALL other operations (except READ operations)

These events need processing, right?

You're goddamn right!

The logic for handling those events is related to the class org.hibernate.engine.spi.ActionQueue.

There are two facts about it:

  1. These events are handled ONLY on Transaction Commit or Flush operations.
  2. These events are not handled in the order they were added to the ActionQueue. That is why we had a DataIntegrityViolationException in the example above.

Hibernate documentation states:

The ActionQueue executes all operations in the following order:

  1. OrphanRemovalAction

  2. EntityInsertAction or EntityIdentityInsertAction

  3. EntityUpdateAction

  4. QueuedOperationCollectionAction

  5. CollectionRemoveAction

  6. CollectionUpdateAction

  7. CollectionRecreateAction

  8. EntityDeleteAction

So, it means that in the example above (from the first section), Hibernate should:

  1. Do select in order to get oldPerson
  2. Insert newPerson. Because EntityInsertAction has a higher priority.
  3. Delete oldPerson. Because EntityDeleteAction has the lowest priority.

And... Here is the SQL log that proves it!

Hibernate: select p1_0.id,p1_0.email,p1_0.name from person p1_0 where p1_0.id=?
Hibernate: select nextval('person_sequence')
Hibernate: insert into person (email,name,id) values (?,?,?)
WARN 60977 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 0, SQLState: 23505
ERROR 60977 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : ERROR: duplicate key value violates unique constraint "person_email_uniq"
  Detail: Key (email)=(mail@gmail.com) already exists.
Enter fullscreen mode Exit fullscreen mode

If you're a truly patient person, you can always delve into the ActionQueue code, set breakpoints, and DYOR (do your own research)!

What's in it for me?

  1. Now you can flex in technical interviews that you're a TOTAL PRO EXPERT in Hibernate 💪
  2. You understand that Hibernate is asynchronous 🤓
  3. You recognize that Hibernate executes SQL commands in a specific order, and you can use this knowledge in your apps to avoid tricky situations like the ones shown at the beginning of the article 😏

Bonus: Why ActionQueue have such a specific order of operations?

Let's break these operations down:

  1. OrphanRemovalAction
    Used when you delete something from the @OneToMany(orphanRemoval = true) association. More about it here: link.

  2. EntityInsertAction or EntityIdentityInsertAction
    Insertions are prioritized due to the Hibernate developers' suggestion that it's more consistent to create entities at the beginning. This ensures that any subsequent operations that refer to this entity will find it in the database.
    For example: if you prioritize delete/update, you might attempt to delete/update something that hasn't been created yet.

  3. EntityUpdateAction
    There are no comments necessary – it seems logical to run update queries after creating them. This guarantees that all entities you're attempting to update exist in the database.

  4. QueuedOperationCollectionAction
    This action refers to all collection-related queued operations (not entity-related!). For these actions to occur, your entity should own that collection relation on the JPA level.

  5. CollectionRemoveAction
    Try to verify this one yourself!🤪

  6. CollectionUpdateAction
    Try to verify this one yourself!🤪

  7. CollectionRecreateAction
    This concerns dropping and recreating a collection in its entirety. This might be necessary when the collection's structure has changed significantly. As this could be a resource-intensive operation, it's prioritized after other more granular collection operations.

  8. EntityDeleteAction
    Delete the entity. It's likely positioned with the lowest priority to avoid situations where you reference a non-existent entity. So it seems logical.

That's it! Thank you for the reading!

PS: If you find something wrong, please correct me in the comments!

My links:
Linkedin
GitHub (mostly dead)

Top comments (0)