4. Persistence Contexts

Both the org.hibernate.Session API and javax.persistence.EntityManager API represent a context for dealing with persistent data. This concept is called a persistence context. Persistent data has a state in relation to both a persistence context and the underlying database.

  • transient
  • the entity has just been instantiated and is not associated with a persistence context. It has no persistent representation in the database and typically no identifier value has been assigned (unless the assigned generator was used).

  • managed, or persistent

  • the entity has an associated identifier and is associated with a persistence context. It may or may not physically exist in the database yet.

  • detached

  • the entity has an associated identifier, but is no longer associated with a persistence context (usually because the persistence context was closed or the instance was evicted from the context)

  • removed

  • the entity has an associated identifier and is associated with a persistence context, however it is scheduled for removal from the database.

Much of the org.hibernate.Session and javax.persistence.EntityManager methods deal with moving entities between these states.

4.1. Accessing Hibernate APIs from JPA

JPA defines an incredibly useful method to allow applications access to the APIs of the underlying provider.

Example 152. Accessing Hibernate APIs from JPA

Session session = entityManager.unwrap( Session.class );
SessionImplementor sessionImplementor = entityManager.unwrap( SessionImplementor.class );

SessionFactory sessionFactory = entityManager.getEntityManagerFactory().unwrap( SessionFactory.class );

4.2. Bytecode Enhancement

Hibernate "grew up" not supporting bytecode enhancement at all. At that time, Hibernate only supported proxy-based for lazy loading and always used diff-based dirty calculation. Hibernate 3.x saw the first attempts at bytecode enhancement support in Hibernate. We consider those initial attempts (up until 5.0) completely as an incubation. The support for bytecode enhancement in 5.0 onward is what we are discussing here.

4.2.1. Capabilities

Hibernate supports the enhancement of an application Java domain model for the purpose of adding various persistence-related capabilities directly into the class.

Lazy attribute loading

Think of this as partial loading support. Essentially you can tell Hibernate that only part(s) of an entity should be loaded upon fetching from the database and when the other part(s) should be loaded as well. Note that this is very much different from proxy-based idea of lazy loading which is entity-centric where the entity’s state is loaded at once as needed. With bytecode enhancement, individual attributes or groups of attributes are loaded as needed.

Lazy attributes can be designated to be loaded together and this is called a "lazy group". By default, all singular attributes are part of a single group, meaning that when one lazy singular attribute is accessed all lazy singular attributes are loaded. Lazy plural attributes, by default, are each a lazy group by themselves. This behavior is explicitly controllable through the @org.hibernate.annotations.LazyGroup annotation.

Example 153. @LazyGroup example

@Entity
public class Customer {

    @Id
    private Integer id;

    private String name;

    @Basic( fetch = FetchType.LAZY )
    private UUID accountsPayableXrefId;

    @Lob
    @Basic( fetch = FetchType.LAZY )
    @LazyGroup( "lobs" )
    private Blob image;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public UUID getAccountsPayableXrefId() {
        return accountsPayableXrefId;
    }

    public void setAccountsPayableXrefId(UUID accountsPayableXrefId) {
        this.accountsPayableXrefId = accountsPayableXrefId;
    }

    public Blob getImage() {
        return image;
    }

    public void setImage(Blob image) {
        this.image = image;
    }
}

In the above example we have 2 lazy attributes: accountsPayableXrefId and image. Each is part of a different fetch group (accountsPayableXrefId is part of the default fetch group), which means that accessing accountsPayableXrefId will not force the loading of image, and vice-versa.

As a hopefully temporary legacy hold-over, it is currently required that all lazy singular associations (many-to-one and one-to-one) also include @LazyToOne(LazyToOneOption.NO_PROXY). The plan is to relax that requirement later.
In-line dirty tracking

Historically Hibernate only supported diff-based dirty calculation for determining which entities in a persistence context have changed. This essentially means that Hibernate would keep track of the last known state of an entity in regards to the database (typically the last read or write). Then, as part of flushing the persistence context, Hibernate would walk every entity associated with the persistence context and check its current state against that "last known database state". This is by far the most thorough approach to dirty checking because it accounts for data-types that can change their internal state (java.util.Date is the prime example of this). However, in a persistence context with a large number of associated entities it can also be a performance-inhibiting approach.

If your application does not need to care about "internal state changing data-type" use cases, bytecode-enhanced dirty tracking might be a worthwhile alternative to consider, especially in terms of performance. In this approach Hibernate will manipulate the bytecode of your classes to add "dirty tracking" directly to the entity, allowing the entity itself to keep track of which of its attributes have changed. During flush time, Hibernate simply asks your entity what has changed rather that having to perform the state-diff calculations.

Bidirectional association management

Hibernate strives to keep your application as close to "normal Java usage" (idiomatic Java) as possible. Consider a domain model with a normal Person/Book bidirectional association:

Example 154. Bidirectional association

@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;

    private String name;

    @OneToMany(mappedBy = "author")
    private List<Book> books = new ArrayList<>(  );

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Book> getBooks() {
        return books;
    }
}

@Entity(name = "Book")
public static class Book {

    @Id
    private Long id;

    private String title;

    @NaturalId
    private String isbn;

    @ManyToOne
    private Person author;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Person getAuthor() {
        return author;
    }

    public void setAuthor(Person author) {
        this.author = author;
    }

    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }
}

Example 155. Incorrect normal Java usage

Person person = new Person();
person.setName( "John Doe" );

Book book = new Book();
person.getBooks().add( book );
try {
    book.getAuthor().getName();
}
catch (NullPointerException expected) {
    // This blows up ( NPE ) in normal Java usage
}

This blows up in normal Java usage. The correct normal Java usage is:

Example 156. Correct normal Java usage

Person person = new Person();
person.setName( "John Doe" );

Book book = new Book();
person.getBooks().add( book );
book.setAuthor( person );

book.getAuthor().getName();

Bytecode-enhanced bi-directional association management makes that first example work by managing the "other side" of a bi-directional association whenever one side is manipulated.

Internal performance optimizations

Additionally we use the enhancement process to add some additional code that allows us to optimized certain performance characteristics of the persistence context. These are hard to discuss without diving into a discussion of Hibernate internals.

4.2.2. Performing enhancement

Run-time enhancement

Currently run-time enhancement of the domain model is only supported in managed JPA environments following the JPA defined SPI for performing class transformations. Even then, this support is disabled by default. To enable run-time enhancement, specify hibernate.ejb.use_class_enhancer=true as a persistent unit property.

Also, at the moment, only annotated classes are supported for run-time enhancement.
Gradle plugin

Hibernate provides a Gradle plugin that is capable of providing build-time enhancement of the domain model as they are compiled as part of a Gradle build. To use the plugin a project would first need to apply it:

Example 157. Apply the Gradle plugin

ext {
    hibernateVersion = 'hibernate-version-you-want'
}

buildscript {
    dependencies {
        classpath "org.hibernate:hibernate-gradle-plugin:$hibernateVersion"
    }
}

hibernate {
    enhance {
        // any configuration goes here
    }
}

The configuration that is available is exposed through a registered Gradle DSL extension:

  • enableLazyInitialization
  • Whether enhancement for lazy attribute loading should be done.

  • enableDirtyTracking

  • Whether enhancement for self-dirty tracking should be done.

  • enableAssociationManagement

  • Whether enhancement for bi-directional association management should be done.

The default value for all 3 configuration settings is true

The enhance { } block is required in order for enhancement to occur. Enhancement is disabled by default in preparation for additions capabilities (hbm2ddl, etc) in the plugin.

Maven plugin

Hibernate provides a Maven plugin capable of providing build-time enhancement of the domain model as they are compiled as part of a Maven build. See the section on the Gradle plugin for details on the configuration settings. Again, the default for those 3 is true.

The Maven plugin supports one additional configuration settings: failOnError, which controls what happens in case of an error. Default behavior is to fail the build, but it can be set so that only a warning is issued.

Example 158. Apply the Maven plugin

<build>
    <plugins>
        [...]
        <plugin>
            <groupId>org.hibernate.orm.tooling</groupId>
            <artifactId>hibernate-enhance-maven-plugin</artifactId>
            <version>$currentHibernateVersion</version>
            <executions>
                <execution>
                    <configuration>
                        <failOnError>true</failOnError>
                        <enableLazyInitialization>true</enableLazyInitialization>
                        <enableDirtyTracking>true</enableDirtyTracking>
                        <enableAssociationManagement>true</enableAssociationManagement>
                    </configuration>
                    <goals>
                        <goal>enhance</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        [...]
    </plugins>
</build>

4.3. Making entities persistent

Once you’ve created a new entity instance (using the standard new operator) it is in new state. You can make it persistent by associating it to either a org.hibernate.Session or javax.persistence.EntityManager.

Example 159. Making an entity persistent with JPA

Person person = new Person();
person.setId( 1L );
person.setName("John Doe");

entityManager.persist( person );

Example 160. Making an entity persistent with Hibernate API

Person person = new Person();
person.setId( 1L );
person.setName("John Doe");

session.save( person );

org.hibernate.Session also has a method named persist which follows the exact semantic defined in the JPA specification for the persist method. It is this org.hibernate.Session method to which the Hibernate javax.persistence.EntityManager implementation delegates.

If the DomesticCat entity type has a generated identifier, the value is associated to the instance when the save or persist is called. If the identifier is not automatically generated, the manually assigned (usually natural) key value has to be set on the instance before the save or persist methods are called.

4.4. Deleting (removing) entities

Entities can also be deleted.

Example 161. Deleting an entity with JPA

entityManager.remove( person );

Example 162. Deleting an entity with Hibernate API

session.delete( person );
Hibernate itself can handle deleting detached state. JPA, however, disallows it. The implication here is that the entity instance passed to the org.hibernate.Session delete method can be either in managed or detached state, while the entity instance passed to remove on javax.persistence.EntityManager must be in managed state.

4.5. Obtain an entity reference without initializing its data

Sometimes referred to as lazy loading, the ability to obtain a reference to an entity without having to load its data is hugely important. The most common case being the need to create an association between an entity and another existing entity.

Example 163. Obtaining an entity reference without initializing its data with JPA

Book book = new Book();
book.setAuthor( entityManager.getReference( Person.class, personId ) );

Example 164. Obtaining an entity reference without initializing its data with Hibernate API

Book book = new Book();
book.setId( 1L );
book.setIsbn( "123-456-7890" );
entityManager.persist( book );
book.setAuthor( session.load( Person.class, personId ) );

The above works on the assumption that the entity is defined to allow lazy loading, generally through use of runtime proxies. In both cases an exception will be thrown later if the given entity does not refer to actual database state when the application attempts to use the returned proxy in any way that requires access to its data.

4.6. Obtain an entity with its data initialized

It is also quite common to want to obtain an entity along with its data (e.g. like when we need to display it in the UI).

Example 165. Obtaining an entity reference with its data initialized with JPA

Person person = entityManager.find( Person.class, personId );

Example 166. Obtaining an entity reference with its data initialized with Hibernate API

Person person = session.get( Person.class, personId );

Example 167. Obtaining an entity reference with its data initialized using the byId() Hibernate API

Person person = session.byId( Person.class ).load( personId );

In both cases null is returned if no matching database row was found.

4.7. Obtain an entity by natural-id

In addition to allowing to load by identifier, Hibernate allows applications to load by declared natural identifier.

Example 168. Natural-id mapping

@Entity(name = "Book")
public static class Book {

    @Id
    private Long id;

    private String title;

    @NaturalId
    private String isbn;

    @ManyToOne
    private Person author;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Person getAuthor() {
        return author;
    }

    public void setAuthor(Person author) {
        this.author = author;
    }

    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }
}

We can also opt to fetch the entity or just retrieve a reference to it when using the natural identifier loading methods.

Example 169. Get entity reference by simple natural-id

Book book = session.bySimpleNaturalId( Book.class ).getReference( isbn );

Example 170. Load entity by natural-id

Book book = session.byNaturalId( Book.class ).using( "isbn", isbn ).load( );

Hibernate offer a consistent API for accessing persistent data by identifier or by the natural-id. Each of these defines the same two data access methods:

  • getReference
  • Should be used in cases where the identifier is assumed to exist, where non-existence would be an actual error. Should never be used to test existence. That is because this method will prefer to create and return a proxy if the data is not already associated with the Session rather than hit the database. The quintessential use-case for using this method is to create foreign-key based associations.

  • load

  • Will return the persistent data associated with the given identifier value or null if that identifier does not exist.

Each of these two methods define an overloading variant accepting a org.hibernate.LockOptions argument. Locking is discussed in a separate chapter.

4.8. Modifying managed/persistent state

Entities in managed/persistent state may be manipulated by the application and any changes will be automatically detected and persisted when the persistence context is flushed. There is no need to call a particular method to make your modifications persistent.

Example 171. Modifying managed state with JPA

Person person = entityManager.find( Person.class, personId );
person.setName("John Doe");
entityManager.flush();

Example 172. Modifying managed state with Hibernate API

Person person = session.byId( Person.class ).load( personId );
person.setName("John Doe");
entityManager.flush();

4.9. Refresh entity state

You can reload an entity instance and its collections at any time.

Example 173. Refreshing entity state with JPA

Person person = entityManager.find( Person.class, personId );

entityManager.createQuery( "update Person set name = UPPER(name)" ).executeUpdate();

entityManager.refresh( person );
assertEquals("JOHN DOE", person.getName() );

Example 174. Refreshing entity state with Hibernate API

Person person = session.byId( Person.class ).load( personId );

session.doWork( connection -> {
    try(Statement statement = connection.createStatement()) {
        statement.executeUpdate( "UPDATE person SET name = UPPER(name)" );
    }
} );

session.refresh( person );
assertEquals("JOHN DOE", person.getName() );

One case where this is useful is when it is known that the database state has changed since the data was read. Refreshing allows the current database state to be pulled into the entity instance and the persistence context.

Another case where this might be useful is when database triggers are used to initialize some of the properties of the entity.

Only the entity instance and its value type collections are refreshed unless you specify REFRESH as a cascade style of any associations. However, please note that Hibernate has the capability to handle this automatically through its notion of generated properties. See the discussion of non-identifier generated attributes.

4.10. Working with detached data

Detachment is the process of working with data outside the scope of any persistence context. Data becomes detached in a number of ways. Once the persistence context is closed, all data that was associated with it becomes detached. Clearing the persistence context has the same effect. Evicting a particular entity from the persistence context makes it detached. And finally, serialization will make the deserialized form be detached (the original instance is still managed).

Detached data can still be manipulated, however the persistence context will no longer automatically know about these modification and the application will need to intervene to make the changes persistent again.

4.10.1. Reattaching detached data

Reattachment is the process of taking an incoming entity instance that is in detached state and re-associating it with the current persistence context.

JPA does not provide for this model. This is only available through Hibernate org.hibernate.Session.

Example 175. Reattaching a detached entity using lock

Person person = session.byId( Person.class ).load( personId );
//Clear the Session so the person entity becomes detached
session.clear();
person.setName( "Mr. John Doe" );

session.lock( person, LockMode.NONE );

Example 176. Reattaching a detached entity using saveOrUpdate

Person person = session.byId( Person.class ).load( personId );
//Clear the Session so the person entity becomes detached
session.clear();
person.setName( "Mr. John Doe" );

session.saveOrUpdate( person );
The method name update is a bit misleading here. It does not mean that an SQL UPDATE is immediately performed. It does, however, mean that an SQL UPDATE will be performed when the persistence context is flushed since Hibernate does not know its previous state against which to compare for changes. If the entity is mapped with select-before-update, Hibernate will pull the current state from the database and see if an update is needed.

Provided the entity is detached, update and saveOrUpdate operate exactly the same.

4.10.2. Merging detached data

Merging is the process of taking an incoming entity instance that is in detached state and copying its data over onto a new managed instance.

Although not exactly per se, the following example is a good visualization of the merge operation internals.

Example 177. Visualizing merge

public Person merge(Person detached) {
    Person newReference = session.byId( Person.class ).load( detached.getId() );
    newReference.setName( detached.getName() );
    return newReference;
}

Example 178. Merging a detached entity with JPA

Person person = entityManager.find( Person.class, personId );
//Clear the EntityManager so the person entity becomes detached
entityManager.clear();
person.setName( "Mr. John Doe" );

person = entityManager.merge( person );

Example 179. Merging a detached entity with Hibernate API

Person person = session.byId( Person.class ).load( personId );
//Clear the Session so the person entity becomes detached
session.clear();
person.setName( "Mr. John Doe" );

person = (Person) session.merge( person );

4.11. Checking persistent state

An application can verify the state of entities and collections in relation to the persistence context.

Example 180. Verifying managed state with JPA

boolean contained = entityManager.contains( person );

Example 181. Verifying managed state with Hibernate API

boolean contained = session.contains( person );

Example 182. Verifying laziness with JPA

PersistenceUnitUtil persistenceUnitUtil = entityManager.getEntityManagerFactory().getPersistenceUnitUtil();

boolean personInitialized = persistenceUnitUtil.isLoaded( person );

boolean personBooksInitialized = persistenceUnitUtil.isLoaded( person.getBooks() );

boolean personNameInitialized = persistenceUnitUtil.isLoaded( person, "name" );

Example 183. Verifying laziness with Hibernate API

boolean personInitialized = Hibernate.isInitialized( person );

boolean personBooksInitialized = Hibernate.isInitialized( person.getBooks() );

boolean personNameInitialized = Hibernate.isPropertyInitialized( person, "name" );

In JPA there is an alternative means to check laziness using the following javax.persistence.PersistenceUtil pattern (which is recommended wherever possible).

Example 184. Alternative JPA means to verify laziness

PersistenceUtil persistenceUnitUtil = Persistence.getPersistenceUtil();

boolean personInitialized = persistenceUnitUtil.isLoaded( person );

boolean personBooksInitialized = persistenceUnitUtil.isLoaded( person.getBooks() );

boolean personNameInitialized = persistenceUnitUtil.isLoaded( person, "name" );

4.12. Evicting entities

When the flush() method is called, the state of the entity is synchronized with the database. If you do not want this synchronization to occur, or if you are processing a huge number of objects and need to manage memory efficiently, the evict() method can be used to remove the object and its collections from the first-level cache.

Example 185. Detaching an entity from the EntityManager

for(Person person : entityManager.createQuery("select p from Person p", Person.class)
        .getResultList()) {
    dtos.add(toDTO(person));
    entityManager.detach( person );
}

Example 186. Evicting an entity from the Hibernate Session

Session session = entityManager.unwrap( Session.class );
for(Person person : (List<Person>) session.createQuery("select p from Person p").list()) {
    dtos.add(toDTO(person));
    session.evict( person );
}

To detach all entities from the current persistence context, both the EntityManager and the Hibernate Session define a clear() method.

Example 187. Clearing the persistence context

entityManager.clear();

session.clear();

To verify if an entity instance is currently attached to the running persistence context, both the EntityManager and the Hibernate Session define a contains(Object entity) method.

Example 188. Verify if an entity is contained in a persistence context

entityManager.contains( person );

session.contains( person );