How does Hibernate handle this underneath?

Hello! While i was playing around with some dummy code to learn how entity states and EntityManager worked, i encountered some problems that i was not expecting to. If my understanding is not wrong, an entity can be in 4 different states: transient, managed, detached and removed. EntityManager.persist() is what allows you to make an entity managed and EntityManager.remove() is what allows you to mark an entity for removal (it turns into a removed state).

Now, taking this into account, and given the following code:

public static void main(String[] args) {
    Map<String, String> options = new HashMap<>();
    options.put("hibernate.show_sql", "true");
    options.put("hibernate.hbm2ddl.auto", "none");

    EntityManagerFactory emf = new HibernatePersistenceProvider()
        .createContainerEntityManagerFactory(new CustomPersistenceUnitInfo(), options);
    
    EntityManager em = emf.createEntityManager();

    try {
        em.getTransaction().begin();
        em.setFlushMode(FlushModeType.COMMIT);

        Employee e = new Employee();
        e.setId(1);

        em.persist(e);
        em.remove(e);
        
        em.getTransaction().commit();
    } finally {
        em.close();
    }
}

What i expect to happen is: an entity of Employee called “e” will be created (transient state). After the persist call the entity will be added to the context, changing its state to managed. After the remove call, the entity state will change to “removed”, that means that by the time the commit happens the only operation that should be mirrored to the database is the delete one, because we only have one entity in the context with the removed state. The insert should not happen because i didn’t manually flush after the persist call and as the documentation says, unflushed changes will be lost.

However, after i execute the code, i can see that an insert is happening. Why is that? I thought that all that mattered was the state that the entity has when the flush operation happens. Thank you so much in advance!

We simply did not implement such an optimization because we think it’s not worth it. No real world code should persist an entity just to remove it immediately after.
Note that persist needs to acquire generated identifiers and set the values on the instance, so an insert might be necessary after all, even if directly followed by a remove.

Hello beikov, thank you so much for your reply! What do you mean by that last part? Why should an insert be necessary with generated ids? Even if i detach the entity before flushing?

Edit: did you mean when we have configured the id to be:

@GeneratedValue(strategy = GenerationType.IDENTITY)

Because that’s the only case when the insert happens even if i dont call EntityManager.flush (I guess it’s because Hibernate needs to get the next id, and the only way to obtain it is to insert it).

I’m sorry if i ask way too obvious questions, i’m new to Hibernate and I’m trying to understand the basics. Thank you so much for your help beikov :smiley:

Hi Sergio,
I love that you’re interested in the lifecycles, it’s a very important aspect to master Hibernate and so many errors are caused by people not making a basic effort to study that.

Essentially you’re correct in your understanding: Hibernate could in theory be somewhat smarter in the case you describe, but like Christian mentioned this particular scenario is very unlikely to happen in practice and not really worth optimising for - especially if you consider that this little suggestion would actually be farily complex to implement, which implies other, more regular and normal usage, would slow down because of the more complex processing. I might be wrong on this and perhaps it could be done efficiently, but apparently nobody studied that particular aspect in debt and made a reasonable proposal; considering how widely it’s used I think that just confirms that nobody really cares for that particular corner case :wink:

The problem with identity generated IDs is that as soon as the persist method returns, a part of the API contract the ID needs to be assigned. This ID assignement isn’t deferred to the flush operation, so the insert needs to happen right away; there are several other ID generation strategies which are also likely to hit the database.

Also, consider studying the concept of “auto flushing”: there are conditions in which Hibernate will need to trigger a flush automatically (in your particular example you disable this, but I’m referring to the general case) ; since flush could have happened there’s many cases in which the insert has already happened when the final flush is triggered, so the delete operation needs to happen.

1 Like