Entity life cycle

An entity instance goes through different states based on the operation we are performing. We refer to this as the entity life cycle. Instance of an entity can be in one of four states –

  1. Transient or New
  2. Persistent or Managed
  3. Detached
  4. Removed

Following image depicts the transition between the various lifecycle states of an entity.

Transient

When an entity object is initially created using the new keyword, its state is transient.  In this state the object is not yet associated with an EntityManager and has no corresponding row in the database. Transient entities can be created inside or outside transactions.

Persistent

A transient entity becomes persistent when it is persisted to the database using the persist() method of EntityManager. Please remember, persist() method must be invoked within an active transaction. During commit, EntityManager stores the new entity in the database.

If an entity object is retrieved from the database using find() method of EntityManager, that object will also be in the persistent state. Any changes of a persistent object is tracked by EntityManager and is saved in DB during commit.

Removed

A persistent entity can be marked for deletion using the remove() method of EntityManager within an active transaction. If you do so, the entity will change its state from persistent to removed. Also, the corresponding row will be deleted from the database during commit.

Detached

When persistent entities are disconnected from the EntityManager, they go into the detached state. It can happen in multiple ways –

  1. You can call the detach() method of EntityManager and pass an entity.
  2. If you close the EntityManager using the close() method, all persistent objects become detached.
  3. If you call the clear() method of the entity manager, it will clear an EntityManager’s persistence context and all persistent objects will become detached.
Other important EnityManager methods

There are many other EnityManager methods present. We will discuss a few of them which are used commonly.

refresh()

The refresh() method will refresh the content of the persistent entity by the latest data from the database. So, any changes that you have made will be discarded.

User user = entityManager.find(User.class, 1L);
user.setEmail("[email protected]");
entityManager.refresh(user); // reset the email from DB

If you execute above code, you will see 2 select queries. The First one will be due to the find() method, and the second will be for the refresh() method.

flush()

The flush() method is used to synchronize the current persistence context to the underlying database session. This operation will cause necessary DML statements (insert/update/delete etc.) to be executed in the database but the current transaction will not be committed. So, the changes due to the flush() method are not visible to other EntityManager unless the transaction is committed.

By default, Hibernate flushes the persistence context in following situations –

  • Before a transaction gets committed, flush will be triggered.
  • Before executing Java Persistence query language (JPQL) or native SQL query.

We will discuss JPQL later in detail, but for now just remember – JPQL is just like native SQL query. Instead of the actual table name, we use entity names. Hibernate, or any other JPA implementation internally converts that JPQL into SQL and fires that query.

When you issue a query using JPQL or native SQL, Hibernate first checks the persistence context to verify if any of the entities are dirty. If it finds a dirty entity, it flushes all the changes to the database. This is to ensure that all the updates to the state of all entities in the persistence context are visible to the query.

In case there is no dirty entity, flush will be triggered just before commit.

To test this, let’s assume we have 2 entities, User and Song and we are performing the following operations.

EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
		
User user = entityManager.find(User.class, 68L);
user.setEmail("[email protected]");

Song song = new Song();
song.setSinger("Singer A");
song.setTitle("Title A");

entityManager.persist(song);

Query query = entityManager.createQuery("Select e.email from User e where e.userId = 67");
String email = (String) query.getSingleResult();

transaction.commit();

If you execute above code, following events will happen –

  1. First, to select the User with primary key 68, a select will be fired.
  2. The update to email will not issue any DB query immediately as it will wait till commit. But Hibernate will mark this object as dirty.
  3. The persist method will not issue an insert query immediately as it will wait till commit.
  4. query.getSingleResult() will force Hibernate to flush the entire persistence context. So, one insert for Song and one update for User will happen.
  5. Finally the provided JPQL query will be fired.

merge()

If you modify a managed entity within an active transaction, changes to that entity are detected automatically and those changes will be updated in the database when the transaction is committed.

EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();		
User user = entityManager.find(User.class, 68L);
user.setEmail("[email protected]");
transaction.commit();

During commit, the user table will be updated with email. After this, if you close the EntityManager, the user object will be converted to detached state from persistent state. Please remember, you can use the EntityManager until you close it. Once closed, there is no way to re-open it.

Now, create a separate EntytyManager and try to update the detached entity.

EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();		
User user = entityManager.find(User.class, 68L);
user.setEmail("[email protected]");
transaction.commit();

entityManager.close();

entityManager = factory.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
user.setEmail("[email protected]");
transaction.commit();

You may expect, the user email will be updated to [email protected], But if you check in DB, you will see the email is [email protected]. The reason is, as the user is detached after the first EntityManager is closed, it’s not part of any persistence context and the changes are not tracked by EntityManager.

Now try to forcefully persist the user inside the second transaction.

transaction = entityManager.getTransaction();
transaction.begin();
user.setEmail("[email protected]");
entityManager.persist(user);
transaction.commit();

You will get following exception

org.hibernate.PersistentObjectException: detached entity passed to persist: com.demo.User

To solve this, you have to convert the detached entity to a persistent entity. For that, you can call the merge() method.

entityManager = factory.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
User mergedUser = entityManager.merge(user);
mergedUser.setEmail("[email protected]");
entityManager.persist(mergedUser);
transaction.commit();

When you call the merge() method, following events happen –

  1. Hibernate checks whether a persistent entity is present in the persistence context with the same id.
  2. The persistence context will be initially empty and there will be no entity with the same id. So, Hibernate will load an instance with the same id from DB.
  3. Then, Hibernate will copy the detached entity states onto this loaded persistent instance.
  4. All the changes will be updated to the database during commit.

That’s it for now. Hope you have enjoyed this tutorial. If you have any doubt, please ask in the comment section. I will try to answer that as soon as possible. Till then, bye bye.