Four states of Hibernate entity objects

Posted by robburne on Tue, 21 Dec 2021 22:26:08 +0100

First, declare that in Hibernate, entity objects have four states, not three (see org.hibernate.event.def.AbstractSaveEventListener).

The three states of Hibernate objects circulated on the Internet are probably because the official hibernate documents are not updated. After one person writes, copy one by one, you know.

state invariant

/**
 * Persistent state
 */
protected static final int PERSISTENT = 0;
/**
 * Instantaneous state
 */
protected static final int TRANSIENT = 1;
/**
 * Free state
 */
protected static final int DETACHED = 2;
/**
 * Delete state
 */
protected static final int DELETED = 3;


Get entity state method

protected int getEntityState(Object entity, String entityName, EntityEntry entry, SessionImplementor source) {
    if (entry != null) {
        /**
         * the object is persistent
         * the entity is associated with the session, so check its status
         */
        if (entry.getStatus() != Status.DELETED) {
            /**
             * do nothing for persistent instances
             */
            if (log.isTraceEnabled()) {
                log.trace("persistent instance of: " + getLoggableName(entityName, entity));
            }
            return PERSISTENT;
        } else {
            /**
             * ie. e.status==DELETED
             */
            if (log.isTraceEnabled()) {
                log.trace("deleted instance of: " + getLoggableName(entityName, entity));
            }
            return DELETED;
        }
    } else {
        /**
         * the object is transient or detached 
         * the entity is not associated with the session, so try interceptor and unsaved-value
         */
        if (ForeignKeys.isTransient(entityName, entity, getAssumedUnsaved(), source)) {
            if (log.isTraceEnabled()) {
                log.trace("transient instance of: " + getLoggableName(entityName, entity));
            }
            return TRANSIENT;
        } else {
            if (log.isTraceEnabled()) {
                log.trace("detached instance of: " + getLoggableName(entityName, entity));
            }
            return DETACHED;
        }
    }
}


Next, we will analyze the four states of Hibernate entity objects combined with the source code.
1. Four states of Hibernate entity objects
Hibernate defines the following four states of an object:

Transient: after a Session is created, an object created by the new operator and not yet associated with a Hibernate Session is considered transient. Transient objects will not be persisted to the database or given a persistent identity. If transient objects are not referenced in the program, they will be destroyed by the garbage collector.
Persistent: a persistent instance may be just saved or just loaded. Either way, by definition, it exists within the scope of the associated Session. Hibernate will detect any changes to the object in the persistent state and send the object data when the current unit of work is completed (state) is synchronized with the database, and developers do not need to UPDATE manually.
Detached state: also called free state. After the Session associated with the persistent object is closed, the object becomes unmanaged. The unmanaged state cannot be persisted directly and needs to be saved again.
DELETED: after calling the delete method of the Session, the object becomes DELETED. At this time, the information of the object is still saved in the Session, the reference to the DELETED object is still valid, and the object can continue to be modified. If the DELETED object is re associated with a new Session (i.e. performing persistence), it will become persistent again (changes made during DELETED will be persisted to the database).
The existence range of the three states is Session. After the Session is opened, the newly created object is instantaneous. After it is associated with the Session, it is persistent. It is deleted from the Session and becomes disconnected. (that is, we won't talk about the state of the object before and after the Session is opened) what's the difference between transient state and off pipe state? Let's leave this question first, and then we will draw a conclusion through the source code analysis.
2. Status diagram

 

3. Complete example

package com.demo.hibernate;
 
import java.util.Date;
 
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.classic.Session;
 
import com.demo.hibernate.bean.Event;
 
public class Main {
 
    public static void main(String[] args) {
        Configuration cfg = new Configuration();
        cfg.configure();
        SessionFactory sessionFactory = cfg.buildSessionFactory();
        Session session = sessionFactory.openSession();
        try {
            Transaction tx = session.beginTransaction();
            try {
                // Create an object, transient
                Event e = new Event();
                e.setTitle("My Event");
                e.setDate(new Date());
                // It is still transient before save
                session.save(e);
                // Persistent state after save
 
                // At this time, the Event information is modified and will be persisted
                e.setTitle("Update My Event!");
                //Submit the transaction and save the persistent object to the database
                tx.commit();
                /**
                 * In the above example, two Sql statements will be generated, as follows:
                 * Hibernate: insert into EVENTS (EVENT_ID, EVENT_DATE, title) values (null, ?, ?)
                 * Hibernate: update EVENTS set EVENT_DATE=?, title=? where EVENT_ID=?
                 */
            } catch (Exception e) {
                e.printStackTrace();
                tx.rollback();
            }
        } finally {
            //Close the session. The object is in the off pipe state. It has nothing to do with the session
            session.close();
        }
    }
}


4. Relevant source code analysis
4.1 Hibernate internal entity Status
In Hibernate, there are six states for entities, as follows:

/**
 * After the entity is saved / loaded, the entity is managed by the Session and is also in a persistent state. It is in a persistent state externally
 */
public static final Status MANAGED = new Status("MANAGED");
/**
 * Whether persistent entity objects can be changed can be configured through hibernate mapping
 * By default, mutable="true". After persistence, objects can be changed
 * After mutable="false", the above example code will not generate the sql statement of update
 * <class name="Event" table="EVENTS" mutable="false">
 * ...
 * </class>
 */
public static final Status READ_ONLY = new Status("READ_ONLY");
/**
 * After deleting data from the Session, the status of the entity is in the off pipe status
 */
public static final Status DELETED = new Status("DELETED");
/**
 * The status of the entity after data is actually deleted from the DB
 */
public static final Status GONE = new Status("GONE");
/**
 * The status of the placeholder before the object is loaded
 */
public static final Status LOADING = new Status("LOADING");
/**
 * The status of the placeholder before saving the object
 */
public static final Status SAVING = new Status("SAVING");
just as Status Class, these states are only in hibernate Internal use, not visible to the application.
4.2 Persistent context PersistenceContext
PersistenceContext yes Session An interface used to save entity persistence context information in PersistenceContext Including its status. When saving an object, it will start from PersistenceContext Query the status of the entity object. If the object is already in persistent status, return its unique identifier. Otherwise, perform the persistence operation, as follows:
protected Serializable performSaveOrUpdate(SaveOrUpdateEvent event) {
    EntityEntry entry = event.getSession().getPersistenceContext().getEntry(event.getEntity());
    if (entry != null && entry.getStatus() != Status.DELETED) {
        return entityIsPersistent(event);
    } else {
        return entityIsTransient(event);
    }
}


As can be seen from the above code, hibernate believes that among the six states of the entities mentioned above, status All States except deleted are persistent.
4.3 difference between transient state and off pipe state
As mentioned earlier, the basic definitions of transient state and off pipe state are detailed here.

Transient state is a state that has nothing to do with session. Session will not have any information about transient objects.

In the off pipe state, there is no state information of the off pipe state object in the Session. How does hibernate determine whether an entity object is in the off pipe state or transient state?

Reviewing the above state diagram, we find that in addition to the transient state, the entity objects in the other three states have unique identification information. Well, hibernate distinguishes the transient state from the off-line state by whether the entity object has generated unique identification information. The relevant source code is as follows:

public Boolean isTransient(Object entity, SessionImplementor session) throws HibernateException {
    final Serializable id;
    if ( canExtractIdOutOfEntity() ) {
        id = getIdentifier( entity, session );
    }
    else {
        id = null;
    }
    //...
    // we *always* assume an instance with a null
    // identifier or no identifier property is unsaved!
    if ( id == null ) {
        return Boolean.TRUE;
    }
    // check the id unsaved-value
    Boolean result = entityMetamodel.getIdentifierProperty()
            .getUnsavedValue().isUnsaved( id );
    if ( result != null ) {
        return result;
    }
    //...
}


The above code also shows that if the set entity object primary key generator is assigned, the off pipe state is equivalent to the transient state.
4.4 persist ent operation
Since the object has so many states, how does the persistence operation work?

switch ( entityState ) {
    case DETACHED: {
        throw new PersistentObjectException(
                "detached entity passed to persist: " +
                    getLoggableName( event.getEntityName(), entity )
        );
    }
    case PERSISTENT: {
        entityIsPersistent( event, createCache );
        break;
    }
    case TRANSIENT: {
        entityIsTransient( event, createCache );
        break;
    }
    case DELETED: {
        entityEntry.setStatus( Status.MANAGED );
        entityEntry.setDeletedState( null );
        event.getSession().getActionQueue().unScheduleDeletion( entityEntry, event.getObject() );
        entityIsDeleted( event, createCache );
        break;
    }
    default: {
        throw new ObjectDeletedException(
            "deleted entity passed to persist",
            null,
            getLoggableName( event.getEntityName(), entity )
        );
    }
}

If the current object is in persistent state, basically nothing needs to be done;
If the current object is transient, you need to persist it and execute the save/saveOrUpdate/persist operation;

If the current object is in delete status, modify its status and execute persist;

If the current object is in off pipe status, you need to perform update/saveOrUpdate.
4.5 selection of object persistence mode
At present, we know that there are many ways to persist an object, including save, persist, update, saveOrUpdate and get/load, but please note that their selection is still based on specific rules.

Save: basically, objects in all States can be persisted through save. However, please be careful not to cause data duplication. Therefore, this method is generally used only from transient state to persistent state.
Persist: it can be seen from the interface that the persist method has fewer return values, that is, unique identifiers, than the save method. In fact, the difference between them is only that. However, please note that persist actually generates unique identifiers during execution, but persist does not return the generated unique identifiers. Examples are as follows:

//...
session.persist(e);
System.out.println(e.getId());
//...


The above example will actually print out the unique ID generated by persistence.

Update: look at the method name annotation to update the state of an object in the out of control state to persistence, that is, it is only used for the out of control state object.

saveOrUpdate: without explanation, the combination of save and update also confirms that the transient state and the off pipe state are very close.
get/load: load data from the database. The loaded object is the persistent state. Load adopts the lazy loading mechanism.
 

Topics: Java Hibernate Cache