First of all, thank you for taking the time to read this and help me, i appreciate it a lot. I am currently learning both JPA and Hibernate fundamentals, trying to learn and understand the basics from scratch so that i can later understand bigger concepts. To be more specific, right now i’m focused on entity states, EntityManger operations and the Persistence Context. While i was playing around these topics with some dummy code examples, several doubts began to arrive to my mind and i would appreciate if someone was able to clear them and explain the correct behavior.
Firstly, so that you can have some context, i will explain my current mental model regarding the persistence context, entity states and EntityManager operations because maybe what’s wrong is not the code, it’s my mental approach. Basically, an entity can be in 4 states: transient (new entity), managed/persistent (the entity is in the context), detached (an entity that was previously in the context but not anymore) and removed (an entity that it’s still in the context but has been marked for removal).
When it comes to the basic EntityManager operations, this is what i understand: EntityManger.persist(e) adds an entity to the context (the entity becomes managed), EntityManger.remove(e) marks an entity (already managed) for removal (the entity will be in the removed state), if you call persist again it will become managed again and will not be removed. And finally, EntityManager.detach(e) takes the entity out of the context, which means that unflushed changes to that entity will be ignored because the context no longer manages the entity.
All these operations will be mirrored to the database when a flush happens or when the commit of the transaction happens. Next, i will provide several examples which i don’t understand and hopefully you can help me learn from them :D.
For the examples, i have a basic entity (Employee), which has the following structure:
@Entity
@Table(name = "employees")
public class Employee {
@Id
@Column(name = "id")
private int id;
private String name;
private String address;
...getters and setters
}
And my Main.java code to play around is this:
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();
// EXAMPLES CODE WILL BE HERE
em.getTransaction().commit();
} finally {
em.close();
}
}
Here are the examples:
1.
Employee e = new Employee();
e.setId(1);
em.persist(e);
em.detach(e);
Taking into account that the “employees” table is empty, using the approach that i explained before 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. Finally, the entity will be removed from the context becoming detached due to the detach call. This would mean that, by the time the commit of the transaction happens, the context should be empty and no entity should be inserted. However, i am getting the following error:
ERROR: HHH000099: an assertion failure occurred (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): org.hibernate.AssertionFailure: possible non-threadsafe access to session.
I know that in the real world it wouldn’t make sense to detach an instance after persisting (without flushing first), but it’s just for understanding the behavior correctly :).
2.
Employee e = new Employee();
e.setId(1);
em.persist(e);
em.remove(e);
For this example, let’s suppose that in the employees table we already have a row with id=1. In this case, this is what i expect to happen: a new entity with transient state will be created. It will be added to the context, changing its state to managed since the persistence context will keep track of it. However, with the remove call we mark that entity for removal, changing its state to removed. By the time the commit happens, in the context we should have the entity with the removed state, so the remove operation should be the one that happens. However, this is what’s actually happening:
Hibernate: insert into employees (address,name,id) values (?,?,?).
And this will throw an exception because an entry with id “1” already exists in the database. Why is an insert happening? Shouldn’t just the remove operation be propagated to the database?
3.
Employee e = new Employee();
e.setId(1);
em.persist(e);
em.setAddress("New address");
Given an empty employees table in the database, what i expect to happen with this code is a single insert query, with id=1 and address=”New address”. The reasoning behind it is this: the persist operation adds the entity to the context, becoming a managed entity. With the setAddress call, we changed the value of that same managed entity. By the time the commit happens, we will have a managed entity that has id=1 and address=”New address”. However, when i execute the code, it generates two queries (first insert with address=null and then update with address=”New address”) instead of just inserting the entity with id=1 and address=”New address”. Why is this happening like this? All the context sees is a managed entity with those values.
4.
Employee e = new Employee();
e.setId(1);
em.persist(e);
em.clear();
em.remove(e);
In this example, this is what i expect to happen: first of all the newly created entity will be added to the context. By calling em.clear(), we remove e from the context, becoming detached. After that, we pass the instance to the remove method, which according to the docs should throw an exception if a detached instanced is passed. But that’s not the case. Something strange is happening: first of all, a select query is triggered, trying to find that same entity in the database. If the entity is not found in the database, the call will not trigger any exception. If the entity is in the database, an exception of “Removing a detached instance…” will be called (expected result). Shouldn’t the exception be thrown in both cases? Because in both cases the entity is detached.
If instead of clearing the context i use the detach function, an exception will be called for both cases (if the entity is in the database or not). What’s happening?
- And the last one is just a question. Given a managed entity in the context, how does the context know if he should insert an Entity or leave it like it is? For example:
Employee e = new Employee();
e.setId(1);
Employee managedEntity = em.merge(e);
em.persist(managedEntity);
em.detach(managedEntity);
em.persist(managedEntity);
Let’s suppose that a row with id=1 already exists in the database. With the merge operation we copy the state of the “e” instance into a managed entity (it will trigger a select to the database to get that instance) and returns the managed instance. With the first persist call we add that entity to the context, which will be ignored because the entity is already managed. After that, we remove that instance of the context, becoming detached. Then, we add it again using persist.
My expected behavior should be that nothing happens because the entity already exists in the database, but what’s actually happening is that an insert is being triggered. However, if i only leave the first persist (and remove the detach and second persist calls) no insert will happen. What’s the difference? In both cases the entity is managed, why is the end result different?
I know this was a very long post and i completely understand if you don’t feel like reading that much. Any bit of help would mean the world to me :D.
Sergio Rodriguez is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
1