How is your data actually flushed? Hibernate ActionQueue event priorities.
Tags: hibernate, jpa, spring, orm
Published: 2023-09-23 17:23:06

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

```java
@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;
}
```

What's the output of the following code?

```java
    @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);
    }
```

_1... 2... 3..._
**Exception!**
```log
ERROR: duplicate key value violates unique constraint "person_email_uniq"
  Detail: Key (email)=(mail@gmail.com) already exists.
```

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

```java
	@Override
	public void delete(Object object) throws HibernateException {
		checkOpen();
		fireDelete( new DeleteEvent( object, this ) );
	}
```
No actual SQL is constructed or executed! Hibernate simply **triggers** a `DeleteEvent`!
The same happens with ALL other operations (except `READ` operations)

</br>
## These events need processing, right?

![You're goddamn right!](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/82qnie2qu76ecj3tn2qb.jpeg)
 
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](https://docs.jboss.org/hibernate/orm/6.3/userguide/html_single/Hibernate_User_Guide.html) 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!**

```sql
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.
```

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](https://www.linkedin.com/feed/update/urn:li:activity:7110594670316908545/). 

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](https://www.linkedin.com/in/eugene-kukhol-3b20a8179/)
[GitHub (mostly dead)](https://github.com/Eragoo)