Code: https://github.com/codinko/SpringHibernateSessionFactory

branch: save-vs-persist

Best video reference: https://www.youtube.com/watch?v=SH29O-bcQlc

Theory & code snippets:

some terms . “JPA Entity Manager, JPA Entity Life cycle states, Session as persistence context, Attaching entity to persistence context “etc …

Session as a Persistence Context Implementation

The Session interface has several methods that eventually result in saving data to the database: persistsaveupdatemergesaveOrUpdate. To understand the difference between these methods, we must first discuss the purpose of the Session as a persistence context and the difference between the states of entity instances in relation to the Session.

We should also understand the history of Hibernate development that led to some partly duplicated API methods.

2.1. Managing Entity Instances

Apart from object-relational mapping itself, one of the problems that Hibernate was intended to solve is the problem of managing entities during runtime. The notion of “persistence context” is Hibernate’s solution to this problem.

  • Persistence context can be thought of as a container or a first-level cache for all the objects that you loaded or saved to a database during a session.

The session is a logical transaction, which boundaries are defined by your application’s business logic.

  • When you work with the database through a persistence context, and all of your entity instances are attached to this context, you should always have a single instance of entity for every database record that you’ve interacted during the session with.

In Hibernate, the persistence context is represented by org.hibernate.Session instance. For JPA, it is the javax.persistence.EntityManager.

When we use Hibernate as a JPA provider and operate via EntityManager interface, the implementation of this interface basically wraps the underlying Session object. However, Hibernate Session provides a richer interface with more possibilities so sometimes it is useful  to work with Session directly.

2.2. States of Entity Instances

Any entity instance in your application appears in one of the three main states in relation to the Session persistence context:

  • transient — this instance is not, and never was, attached to a Session; this instance has no corresponding rows in the database; it’s usually just a new object that you have created to save to the database;
  • persistent — this instance is associated with a unique Session object; upon flushing [ eg: flush by txn.commit() ] the Session to the database, this entity is guaranteed to have a corresponding consistent record in the database;
  • detached — this instance was once attached to a Session (in a persistent state), but now it’s not; an instance enters this state if you evict it from the context, clear or close the Session, or put the instance through serialization/deserialization process.

Here is a simplified state diagram with comments on Session methods that make the state transitions happen.

2016-07-11_13-38-11

When the entity instance is in the persistent state, all changes that you make to the mapped fields of this instance will be applied to the corresponding database records and fields upon flushing the Session. The persistent instance can be thought of as “online”, whereas the detached instance has gone “offline” and is not monitored for changes.

This means that when you change fields of a persistent object, you don’t have to call saveupdate or any of those methods to get these changes to the database: all you need is to commit the transaction, or flush; and then close the session, when you’re done with it. – code snippet 1

3. Differences Between the Operations

It is important to understand from the beginning that all of the methods (persistsaveupdatemergesaveOrUpdate) do not immediately result in the corresponding SQL UPDATE or INSERT statements. Only some does.

  • The actual saving of data to the database occurs on committing the transaction or flushing the Session.

The mentioned methods basically manage the state of entity instances by transitioning them between different states along the lifecycle.

As an example entity, we will use a simple annotation-mapped entity Person:

@Entity
public class Person {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    // ... getters and setters
}

3.1. Persist

The persist method is intended for adding a new entity instance to the persistence context, i.e. transitioning an instance from transient to persistent state.

We usually call it when we want to add a record to the database (persist an entity instance):

Person person = new Person();
person.setName("Harley");
session.persist(person);

What happens after the persist method is called?

  • The person object has transitioned from transient to persistent state.
  • The object is in the persistence context now, but not yet saved to the database.
  • The generation of INSERT statements will occur only upon committing the transaction, flushing or closing the session. [ If ID is auto generated then INSERT statements will be generated during persist() but data will be saved only during commit/flush]

Notice that the persist method has void return type. It operates on the passed object “in place”, changing its state. The person variable references the actual persisted object.

 

This method is a later addition to the Session interface. The main differentiating feature of this method is that it conforms to the JSR-220 specification (EJB persistence). The semantics of this method is strictly defined in the specification, which basically states, that:

  • transient instance becomes persistent (and the operation cascades to all of its relations with cascade=PERSIST or cascade=ALL),
  • if an instance is already persistent, then this call has no effect for this particular instance (but it still cascades to its relations with cascade=PERSIST or cascade=ALL),
  • if an instance is detached, you should expect an exception, either upon calling this method, or upon committing or flushing the session.

 

  • You may call this method on an already persistent instance, and nothing happens.
  • But if you try to persist a detached instance, the implementation is bound to throw an exception.
  • In the following example we persist the entity, evict it from the context so it becomes detached, and then try to persist again.
Person person = new Person();
person.setName("Harley");
session.persist(person);
session.evict(person);
session.persist(person); // PersistenceException!

3.2. Save

The save method is an “original” Hibernate method that does not conform to the JPA specification.

Its purpose is basically the same as persist, but it has different implementation details. The documentation for this method strictly states that it persists the instance, “first assigning a generated identifier”. The method is guaranteed to return the Serializable value of this identifier.

Person person = new Person();
person.setName("Harley");
Long id = (Long) session.save(person);
The effect of saving an already persisted instance is the same as with persist. Difference comes when you try to save a detached instance:
Person person = new Person();
person.setName("John");
Long id1 = (Long) session.save(person);
session.evict(person);
Long id2 = (Long) session.save(person);

 

The id2 variable will differ from id1. The call of save on a detached instance creates a new persistent instance and assigns it a new identifier, which results in a duplicate record in a database upon committing or flushing.

 

More theory:

If an entity is attached to the current persistence context, it has the life cycle state ‘Managed’ .Managed is also called ‘Persistent‘.  That means it is mapped to a database record. A persistence provider generates the required INSERT or UPDATE statements to propagate all changes.

A managed entity is also stored in the First level cache.

Snip20180115_1.png

 

Summary – what’s the difference between Save and Persist?

  • JPA specification defines the persist() method. You can use it with all JPA implementations including Hibernate
  • save() method is Hibernate specific

 

  • JPA’s persist() returns void
  • Hibernate’s save() returns the primary key of the Entity.

 

  • Hibernate’s save() generates the primary key value immediately.
  • JPA doesn’t mention when primary key value has to be assigned. So the persistence provider can do that anytime between call of persist() and flush of persistence context.

 

  • Hibernate uses name of the Entity class and primary key value to store the entity in the First level cache. So therefore it needs the primary key value when it executes the persist() method.

MYSQL DB and table in our usecases

Snip20180115_11

Hibernate Persist() and save() , when enitity hasGenerated ID value scenario and otherwise

use-case P1:

  • session.persist() and txn.commit()
  •  Entity’s ID is not auto generated, it is set explicitly
  • when persist() is called, the identifier is assigned.
  • only when commit() is called, INSERT statement is created and session is flushed to database.

Snip20180115_9

Snip20180115_10

use-case P2:

  • session.persist() and txn.commit()
  • @Generated ID value for Entity’s ID
  • when persist() is called, the Insert statement is generated, [ data is not inserted to database]
  • only when commit() is called, session is flushed to database, and data got inserted to database.

Snip20180115_7.png

Snip20180115_4

Snip20180115_6

use-case P3:

  • session.persist() and session.flush() without txn.commit()
  • @Generated ID value for Entity’s ID
  • when persist() is called, the Insert statement is generated, [ data is not inserted to database]
  • when flush() is called, session appears to have flushed to database. However data is NOT present in database! [ why bcoz we didn’t do commit being inside Transactional context ]

Snip20180115_12.png

Snip20180115_13.png

Let’s compare this one (use-case P3 )quickly with output of use-case P2. Both seems almost equal, however when you look at database, the record for use-case P3 is NOT present in database.

—- use-case P2  output [ for comparison ]

Snip20180115_6


 

use-case P4 : 

  • session.persist() demos

For this use-case, we will club all the theory & code that we learnt so far.

session.persist()

  • The object has transitioned from transient to persistent state.
  • The object is in the persistence context now, but not yet saved to the database.
  • The generation of INSERT statements will occur only upon committing the transaction, flushing or closing the session. [ If ID is auto generated then INSERT statements will be generated during persist() but data will be saved only during commit/flush]
  • If an instance is already persistent, then this call has no effect for this particular instance (but it still cascades to its relations with cascade=PERSIST or cascade=ALL),
  • If an instance is detached, you should expect an exception, either upon calling this method, or upon committing or flushing the session.

i) persist() , then persist() again

Snip20180115_14.png

Snip20180115_17

ii) persist(), then change ID and then persist() again

Snip20180115_19.png

iv) persist() , then evict() then persist() again with same ID

Snip20180115_20.png

Snip20180115_21.png

To understand why this happened, you need to see the below use-case.

iv) persist() , then evict() then persist() again with new ID

Snip20180115_15.png

Snip20180115_16.png

session.save()

The save method is an Hibernate  specific method.

Its purpose is basically the same as persist, but it has different implementation details. The documentation for this method strictly states that it persists the instance, “first assigning a generated identifier”.

The method is guaranteed to return the Serializable value of this identifier.

use-case S1

  • session.save() and txn.commit()
  •  Entity’s ID is not auto generated, it is set explicitly
  • when save() is called, the identifier is assigned.
  • only when commit() is called, INSERT statement is created and session is flushed to database.

Snip20180115_23.png

Snip20180115_26

 If you compare this to persist() as in use-case P1, it looks the same output.
use-case S2
  • session.save() and txn.commit()
  • @Generated ID value for Entity’s ID
  • when save() is called, the INSERT statement is generated, [ data is not inserted to database]
  • only when commit() is called, session is flushed to database, and data got inserted to database.

Snip20180115_25.png

Snip20180115_26