Preface

Working with both Object-Oriented software and Relational Databases can be cumbersome and time consuming. Development costs are significantly higher due to a paradigm mismatch between how data is represented in objects versus relational databases. Hibernate is an Object/Relational Mapping solution for Java environments. The term Object/Relational Mapping refers to the technique of mapping data from an object model representation to a relational data model representation (and visa versa).

Hibernate not only takes care of the mapping from Java classes to database tables (and from Java data types to SQL data types), but also provides data query and retrieval facilities. It can significantly reduce development time otherwise spent with manual data handling in SQL and JDBC. Hibernate’s design goal is to relieve the developer from 95% of common data persistence-related programming tasks by eliminating the need for manual, hand-crafted data processing using SQL and JDBC. However, unlike many other persistence solutions, Hibernate does not hide the power of SQL from you and guarantees that your investment in relational technology and knowledge is as valid as always.

Hibernate may not be the best solution for data-centric applications that only use stored-procedures to implement the business logic in the database, it is most useful with object-oriented domain models and business logic in the Java-based middle-tier. However, Hibernate can certainly help you to remove or encapsulate vendor-specific SQL code and will help with the common task of result set translation from a tabular representation to a graph of objects.

Get Involved

  • Use Hibernate and report any bugs or issues you find. See Issue Tracker for details.

  • Try your hand at fixing some bugs or implementing enhancements. Again, see Issue Tracker.

  • Engage with the community using mailing lists, forums, IRC, or other ways listed in the Community section.

  • Help improve or translate this documentation. Contact us on the developer mailing list if you have interest.

  • Spread the word. Let the rest of your organization know about the benefits of Hibernate.

System Requirements

Hibernate 5.2 and later versions require at least Java 1.8 and JDBC 4.2.

Hibernate 5.1 and older versions require at least Java 1.6 and JDBC 4.0.

When building Hibernate 5.1 or older from sources, you need Java 1.7 due to a bug in the JDK 1.6 compiler.

Getting Started Guide

New users may want to first look through the Hibernate Getting Started Guide for basic information as well as tutorials. There is also a series of topical guides providing deep dives into various topics.

While having a strong background in SQL is not required to use Hibernate, it certainly helps a lot because it all boils down to SQL statements. Probably even more important is an understanding of data modeling principles. You might want to consider these resources as a good starting point:

Understanding the basics of transactions and design patterns such as Unit of Work PoEAA or Application Transaction are important as well. These topics will be discussed in the documentation, but a prior understanding will certainly help.

1. Architecture

1.1. Overview

Data Access Layers

Hibernate, as an ORM solution, effectively "sits between" the Java application data access layer and the Relational Database, as can be seen in the diagram above. The Java application makes use of the Hibernate APIs to load, store, query, etc its domain data. Here we will introduce the essential Hibernate APIs. This will be a brief introduction; we will discuss these contracts in detail later.

As a JPA provider, Hibernate implements the Java Persistence API specifications and the association between JPA interfaces and Hibernate specific implementations can be visualized in the following diagram:

image

SessionFactory (org.hibernate.SessionFactory)

A thread-safe (and immutable) representation of the mapping of the application domain model to a database. Acts as a factory for org.hibernate.Session instances. The EntityManagerFactory is the JPA equivalent of a SessionFactory and basically those two converge into the same SessionFactory implementation.

A SessionFactory is very expensive to create, so, for any given database, the application should have only one associated SessionFactory. The SessionFactory maintains services that Hibernate uses across all Session(s) such as second level caches, connection pools, transaction system integrations, etc.

Session (org.hibernate.Session)

A single-threaded, short-lived object conceptually modeling a "Unit of Work" PoEAA. In JPA nomenclature, the Session is represented by an EntityManager.

Behind the scenes, the Hibernate Session wraps a JDBC java.sql.Connection and acts as a factory for org.hibernate.Transaction instances. It maintains a generally "repeatable read" persistence context (first level cache) of the application domain model.

Transaction (org.hibernate.Transaction)

A single-threaded, short-lived object used by the application to demarcate individual physical transaction boundaries. EntityTransaction is the JPA equivalent and both act as an abstraction API to isolate the application from the underlying transaction system in use (JDBC or JTA).

2. Domain Model

The term domain model comes from the realm of data modeling. It is the model that ultimately describes the problem domain you are working in. Sometimes you will also hear the term persistent classes.

Ultimately the application domain model is the central character in an ORM. They make up the classes you wish to map. Hibernate works best if these classes follow the Plain Old Java Object (POJO) / JavaBean programming model. However, none of these rules are hard requirements. Indeed, Hibernate assumes very little about the nature of your persistent objects. You can express a domain model in other ways (using trees of java.util.Map instances, for example).

Historically applications using Hibernate would have used its proprietary XML mapping file format for this purpose. With the coming of JPA, most of this information is now defined in a way that is portable across ORM/JPA providers using annotations (and/or standardized XML format). This chapter will focus on JPA mapping where possible. For Hibernate mapping features not supported by JPA we will prefer Hibernate extension annotations.

2.1. Mapping types

Hibernate understands both the Java and JDBC representations of application data. The ability to read/write this data from/to the database is the function of a Hibernate type. A type, in this usage, is an implementation of the org.hibernate.type.Type interface. This Hibernate type also describes various aspects of behavior of the Java type such as how to check for equality, how to clone values, etc.

Usage of the word type

The Hibernate type is neither a Java type nor a SQL data type. It provides information about both of these as well as understanding marshalling between.

When you encounter the term type in discussions of Hibernate, it may refer to the Java type, the JDBC type, or the Hibernate type, depending on context.

To help understand the type categorizations, let’s look at a simple table and domain model that we wish to map.

Example 1. Simple table and domain model
create table Contact (
    id integer not null,
    first varchar(255),
    last varchar(255),
    middle varchar(255),
    notes varchar(255),
    starred boolean not null,
    website varchar(255),
    primary key (id)
)
@Entity(name = "Contact")
public static class Contact {

    @Id
    private Integer id;

    private Name name;

    private String notes;

    private URL website;

    private boolean starred;

    //Getters and setters are omitted for brevity
}

@Embeddable
public class Name {

    private String first;

    private String middle;

    private String last;

    // getters and setters omitted
}

In the broadest sense, Hibernate categorizes types into two groups:

2.1.1. Value types

A value type is a piece of data that does not define its own lifecycle. It is, in effect, owned by an entity, which defines its lifecycle.

Looked at another way, all the state of an entity is made up entirely of value types. These state fields or JavaBean properties are termed persistent attributes. The persistent attributes of the Contact class are value types.

Value types are further classified into three sub-categories:

Basic types

in mapping the Contact table, all attributes except for name would be basic types. Basic types are discussed in detail in Basic Types

Embeddable types

the name attribute is an example of an embeddable type, which is discussed in details in Embeddable Types

Collection types

although not featured in the aforementioned example, collection types are also a distinct category among value types. Collection types are further discussed in Collections

2.1.2. Entity types

Entities, by nature of their unique identifier, exist independently of other objects whereas values do not. Entities are domain model classes which correlate to rows in a database table, using a unique identifier. Because of the requirement for a unique identifier, entities exist independently and define their own lifecycle. The Contact class itself would be an example of an entity.

Mapping entities is discussed in detail in Entity.

2.2. Naming strategies

Part of the mapping of an object model to the relational database is mapping names from the object model to the corresponding database names. Hibernate looks at this as 2 stage process:

  • The first stage is determining a proper logical name from the domain model mapping. A logical name can be either explicitly specified by the user (using @Column or @Table e.g.) or it can be implicitly determined by Hibernate through an ImplicitNamingStrategy contract.

  • Second is the resolving of this logical name to a physical name which is defined by the PhysicalNamingStrategy contract.

Historical NamingStrategy contract

Historically Hibernate defined just a single org.hibernate.cfg.NamingStrategy. That singular NamingStrategy contract actually combined the separate concerns that are now modeled individually as ImplicitNamingStrategy and PhysicalNamingStrategy.

Also, the NamingStrategy contract was often not flexible enough to properly apply a given naming "rule", either because the API lacked the information to decide or because the API was honestly not well defined as it grew.

Due to these limitation, org.hibernate.cfg.NamingStrategy has been deprecated and then removed in favor of ImplicitNamingStrategy and PhysicalNamingStrategy.

At the core, the idea behind each naming strategy is to minimize the amount of repetitive information a developer must provide for mapping a domain model.

JPA Compatibility

JPA defines inherent rules about implicit logical name determination. If JPA provider portability is a major concern, or if you really just like the JPA-defined implicit naming rules, be sure to stick with ImplicitNamingStrategyJpaCompliantImpl (the default)

Also, JPA defines no separation between logical and physical name. Following the JPA specification, the logical name is the physical name. If JPA provider portability is important, applications should prefer not to specify a PhysicalNamingStrategy.

2.2.1. ImplicitNamingStrategy

When an entity does not explicitly name the database table that it maps to, we need to implicitly determine that table name. Or when a particular attribute does not explicitly name the database column that it maps to, we need to implicitly determine that column name. There are examples of the role of the org.hibernate.boot.model.naming.ImplicitNamingStrategy contract to determine a logical name when the mapping did not provide an explicit name.

Implicit Naming Strategy Diagram

Hibernate defines multiple ImplicitNamingStrategy implementations out-of-the-box. Applications are also free to plug-in custom implementations.

There are multiple ways to specify the ImplicitNamingStrategy to use. First, applications can specify the implementation using the hibernate.implicit_naming_strategy configuration setting which accepts:

  • pre-defined "short names" for the out-of-the-box implementations

    default

    for org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl - an alias for jpa

    jpa

    for org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl - the JPA 2.0 compliant naming strategy

    legacy-hbm

    for org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl - compliant with the original Hibernate NamingStrategy

    legacy-jpa

    for org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl - compliant with the legacy NamingStrategy developed for JPA 1.0, which was unfortunately unclear in many respects regarding implicit naming rules

    component-path

    for org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl - mostly follows ImplicitNamingStrategyJpaCompliantImpl rules, except that it uses the full composite paths, as opposed to just the ending property part

  • reference to a Class that implements the org.hibernate.boot.model.naming.ImplicitNamingStrategy contract

  • FQN of a class that implements the org.hibernate.boot.model.naming.ImplicitNamingStrategy contract

Secondly, applications and integrations can leverage org.hibernate.boot.MetadataBuilder#applyImplicitNamingStrategy to specify the ImplicitNamingStrategy to use. See Bootstrap for additional details on bootstrapping.

2.2.2. PhysicalNamingStrategy

Many organizations define rules around the naming of database objects (tables, columns, foreign-keys, etc). The idea of a PhysicalNamingStrategy is to help implement such naming rules without having to hard-code them into the mapping via explicit names.

While the purpose of an ImplicitNamingStrategy is to determine that an attribute named accountNumber maps to a logical column name of accountNumber when not explicitly specified, the purpose of a PhysicalNamingStrategy would be, for example, to say that the physical column name should instead be abbreviated acct_num.

It is true that the resolution to acct_num could have been handled in an ImplicitNamingStrategy in this case. But the point is separation of concerns. The PhysicalNamingStrategy will be applied regardless of whether the attribute explicitly specified the column name or whether we determined that implicitly. The ImplicitNamingStrategy would only be applied if an explicit name was not given. So it depends on needs and intent.

The default implementation is to simply use the logical name as the physical name. However applications and integrations can define custom implementations of this PhysicalNamingStrategy contract. Here is an example PhysicalNamingStrategy for a fictitious company named Acme Corp whose naming standards are to:

  • prefer underscore-delimited words rather than camel-casing

  • replace certain words with standard abbreviations

Example 2. Example PhysicalNamingStrategy implementation
/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.userguide.naming;

import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;

import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;

import org.apache.commons.lang3.StringUtils;

/**
 * An example PhysicalNamingStrategy that implements database object naming standards
 * for our fictitious company Acme Corp.
 * <p/>
 * In general Acme Corp prefers underscore-delimited words rather than camel casing.
 * <p/>
 * Additionally standards call for the replacement of certain words with abbreviations.
 *
 * @author Steve Ebersole
 */
public class AcmeCorpPhysicalNamingStrategy implements PhysicalNamingStrategy {
	private static final Map<String,String> ABBREVIATIONS = buildAbbreviationMap();

	@Override
	public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment jdbcEnvironment) {
		// Acme naming standards do not apply to catalog names
		return name;
	}

	@Override
	public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment jdbcEnvironment) {
		// Acme naming standards do not apply to schema names
		return name;
	}

	@Override
	public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) {
		final List<String> parts = splitAndReplace( name.getText() );
		return jdbcEnvironment.getIdentifierHelper().toIdentifier(
				join( parts ),
				name.isQuoted()
		);
	}

	@Override
	public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment jdbcEnvironment) {
		final LinkedList<String> parts = splitAndReplace( name.getText() );
		// Acme Corp says all sequences should end with _seq
		if ( !"seq".equalsIgnoreCase( parts.getLast() ) ) {
			parts.add( "seq" );
		}
		return jdbcEnvironment.getIdentifierHelper().toIdentifier(
				join( parts ),
				name.isQuoted()
		);
	}

	@Override
	public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment jdbcEnvironment) {
		final List<String> parts = splitAndReplace( name.getText() );
		return jdbcEnvironment.getIdentifierHelper().toIdentifier(
				join( parts ),
				name.isQuoted()
		);
	}

	private static Map<String, String> buildAbbreviationMap() {
		TreeMap<String,String> abbreviationMap = new TreeMap<> ( String.CASE_INSENSITIVE_ORDER );
		abbreviationMap.put( "account", "acct" );
		abbreviationMap.put( "number", "num" );
		return abbreviationMap;
	}

	private LinkedList<String> splitAndReplace(String name) {
		LinkedList<String> result = new LinkedList<>();
		for ( String part : StringUtils.splitByCharacterTypeCamelCase( name ) ) {
			if ( part == null || part.trim().isEmpty() ) {
				// skip null and space
				continue;
			}
			part = applyAbbreviationReplacement( part );
			result.add( part.toLowerCase( Locale.ROOT ) );
		}
		return result;
	}

	private String applyAbbreviationReplacement(String word) {
		if ( ABBREVIATIONS.containsKey( word ) ) {
			return ABBREVIATIONS.get( word );
		}

		return word;
	}

	private String join(List<String> parts) {
		boolean firstPass = true;
		String separator = "";
		StringBuilder joined = new StringBuilder();
		for ( String part : parts ) {
			joined.append( separator ).append( part );
			if ( firstPass ) {
				firstPass = false;
				separator = "_";
			}
		}
		return joined.toString();
	}
}

There are multiple ways to specify the PhysicalNamingStrategy to use. First, applications can specify the implementation using the hibernate.physical_naming_strategy configuration setting which accepts:

  • reference to a Class that implements the org.hibernate.boot.model.naming.PhysicalNamingStrategy contract

  • FQN of a class that implements the org.hibernate.boot.model.naming.PhysicalNamingStrategy contract

Secondly, applications and integrations can leverage org.hibernate.boot.MetadataBuilder#applyPhysicalNamingStrategy. See Bootstrap for additional details on bootstrapping.

2.3. Basic Types

Basic value types usually map a single database column, to a single, non-aggregated Java type. Hibernate provides a number of built-in basic types, which follow the natural mappings recommended by the JDBC specifications.

Internally Hibernate uses a registry of basic types when it needs to resolve a specific org.hibernate.type.Type.

2.3.1. Hibernate-provided BasicTypes

Table 1. Standard BasicTypes
Hibernate type (org.hibernate.type package) JDBC type Java type BasicTypeRegistry key(s)

StringType

VARCHAR

java.lang.String

string, java.lang.String

MaterializedClob

CLOB

java.lang.String

materialized_clob

TextType

LONGVARCHAR

java.lang.String

text

CharacterType

CHAR

char, java.lang.Character

char, java.lang.Character

BooleanType

BIT

boolean, java.lang.Boolean

boolean, java.lang.Boolean

NumericBooleanType

INTEGER, 0 is false, 1 is true

boolean, java.lang.Boolean

numeric_boolean

YesNoType

CHAR, 'N'/'n' is false, 'Y'/'y' is true. The uppercase value is written to the database.

boolean, java.lang.Boolean

yes_no

TrueFalseType

CHAR, 'F'/'f' is false, 'T'/'t' is true. The uppercase value is written to the database.

boolean, java.lang.Boolean

true_false

ByteType

TINYINT

byte, java.lang.Byte

byte, java.lang.Byte

ShortType

SMALLINT

short, java.lang.Short

short, java.lang.Short

IntegerTypes

INTEGER

int, java.lang.Integer

int, java.lang.Integer

LongType

BIGINT

long, java.lang.Long

long, java.lang.Long

FloatType

FLOAT

float, java.lang.Float

float, java.lang.Float

DoubleType

DOUBLE

double, java.lang.Double

double, java.lang.Double

BigIntegerType

NUMERIC

java.math.BigInteger

big_integer, java.math.BigInteger

BigDecimalType

NUMERIC

java.math.BigDecimal

big_decimal, java.math.bigDecimal

TimestampType

TIMESTAMP

java.sql.Timestamp

timestamp, java.sql.Timestamp

TimeType

TIME

java.sql.Time

time, java.sql.Time

DateType

DATE

java.sql.Date

date, java.sql.Date

CalendarType

TIMESTAMP

java.util.Calendar

calendar, java.util.Calendar

CalendarDateType

DATE

java.util.Calendar

calendar_date

CalendarTimeType

TIME

java.util.Calendar

calendar_time

CurrencyType

java.util.Currency

VARCHAR

currency, java.util.Currency

LocaleType

VARCHAR

java.util.Locale

locale, java.utility.locale

TimeZoneType

VARCHAR, using the TimeZone ID

java.util.TimeZone

timezone, java.util.TimeZone

UrlType

VARCHAR

java.net.URL

url, java.net.URL

ClassType

VARCHAR (class FQN)

java.lang.Class

class, java.lang.Class

BlobType

BLOB

java.sql.Blob

blob, java.sql.Blob

ClobType

CLOB

java.sql.Clob

clob, java.sql.Clob

BinaryType

VARBINARY

byte[]

binary, byte[]

MaterializedBlobType

BLOB

byte[]

materialized_blob

ImageType

LONGVARBINARY

byte[]

image

WrapperBinaryType

VARBINARY

java.lang.Byte[]

wrapper-binary, Byte[], java.lang.Byte[]

CharArrayType

VARCHAR

char[]

characters, char[]

CharacterArrayType

VARCHAR

java.lang.Character[]

wrapper-characters, Character[], java.lang.Character[]

UUIDBinaryType

BINARY

java.util.UUID

uuid-binary, java.util.UUID

UUIDCharType

CHAR, can also read VARCHAR

java.util.UUID

uuid-char

PostgresUUIDType

PostgreSQL UUID, through Types#OTHER, which complies to the PostgreSQL JDBC driver definition

java.util.UUID

pg-uuid

SerializableType

VARBINARY

implementors of java.lang.Serializable

Unlike the other value types, multiple instances of this type are registered. It is registered once under java.io.Serializable, and registered under the specific java.io.Serializable implementation class names.

StringNVarcharType

NVARCHAR

java.lang.String

nstring

NTextType

LONGNVARCHAR

java.lang.String

ntext

NClobType

NCLOB

java.sql.NClob

nclob, java.sql.NClob

MaterializedNClobType

NCLOB

java.lang.String

materialized_nclob

PrimitiveCharacterArrayNClobType

NCHAR

char[]

N/A

CharacterNCharType

NCHAR

java.lang.Character

ncharacter

CharacterArrayNClobType

NCLOB

java.lang.Character[]

N/A

Table 2. Java 8 BasicTypes
Hibernate type (org.hibernate.type package) JDBC type Java type BasicTypeRegistry key(s)

DurationType

BIGINT

java.time.Duration

Duration, java.time.Duration

InstantType

TIMESTAMP

java.time.Instant

Instant, java.time.Instant

LocalDateTimeType

TIMESTAMP

java.time.LocalDateTime

LocalDateTime, java.time.LocalDateTime

LocalDateType

DATE

java.time.LocalDate

LocalDate, java.time.LocalDate

LocalTimeType

TIME

java.time.LocalTime

LocalTime, java.time.LocalTime

OffsetDateTimeType

TIMESTAMP

java.time.OffsetDateTime

OffsetDateTime, java.time.OffsetDateTime

OffsetTimeType

TIME

java.time.OffsetTime

OffsetTime, java.time.OffsetTime

ZonedDateTimeType

TIMESTAMP

java.time.ZonedDateTime

ZonedDateTime, java.time.ZonedDateTime

Table 3. Hibernate Spatial BasicTypes
Hibernate type (org.hibernate.spatial package) JDBC type Java type BasicTypeRegistry key(s)

JTSGeometryType

depends on the dialect

com.vividsolutions.jts.geom.Geometry

jts_geometry, or the classname of Geometry or any of its subclasses

GeolatteGeometryType

depends on the dialect

org.geolatte.geom.Geometry

geolatte_geometry, or the classname of Geometry or any of its subclasses

To use these hibernate-spatial types, you must add the hibernate-spatial dependency to your classpath and use a org.hibernate.spatial.SpatialDialect implementation. See Spatial for more details about spatial types.

These mappings are managed by a service inside Hibernate called the org.hibernate.type.BasicTypeRegistry, which essentially maintains a map of org.hibernate.type.BasicType (a org.hibernate.type.Type specialization) instances keyed by a name. That is the purpose of the "BasicTypeRegistry key(s)" column in the previous tables.

2.3.2. The @Basic annotation

Strictly speaking, a basic type is denoted by the javax.persistence.Basic annotation. Generally speaking, the @Basic annotation can be ignored, as it is assumed by default. Both of the following examples are ultimately the same.

Example 3. @Basic declared explicitly
@Entity(name = "Product")
public class Product {

    @Id
    @Basic
    private Integer id;

    @Basic
    private String sku;

    @Basic
    private String name;

    @Basic
    private String description;
}
Example 4. @Basic being implicitly implied
@Entity(name = "Product")
public class Product {

    @Id
    private Integer id;

    private String sku;

    private String name;

    private String description;
}

The JPA specification strictly limits the Java types that can be marked as basic to the following listing:

  • Java primitive types (boolean, int, etc)

  • wrappers for the primitive types (java.lang.Boolean, java.lang.Integer, etc)

  • java.lang.String

  • java.math.BigInteger

  • java.math.BigDecimal

  • java.util.Date

  • java.util.Calendar

  • java.sql.Date

  • java.sql.Time

  • java.sql.Timestamp

  • byte[] or Byte[]

  • char[] or Character[]

  • enums

  • any other type that implements Serializable (JPA’s "support" for Serializable types is to directly serialize their state to the database).

If provider portability is a concern, you should stick to just these basic types. Note that JPA 2.1 did add the notion of a javax.persistence.AttributeConverter to help alleviate some of these concerns; see JPA 2.1 AttributeConverters for more on this topic.

The @Basic annotation defines 2 attributes.

optional - boolean (defaults to true)

Defines whether this attribute allows nulls. JPA defines this as "a hint", which essentially means that it effect is specifically required. As long as the type is not primitive, Hibernate takes this to mean that the underlying column should be NULLABLE.

fetch - FetchType (defaults to EAGER)

Defines whether this attribute should be fetched eagerly or lazily. JPA says that EAGER is a requirement to the provider (Hibernate) that the value should be fetched when the owner is fetched, while LAZY is merely a hint that the value be fetched when the attribute is accessed. Hibernate ignores this setting for basic types unless you are using bytecode enhancement. See the BytecodeEnhancement for additional information on fetching and on bytecode enhancement.

2.3.3. The @Column annotation

JPA defines rules for implicitly determining the name of tables and columns. For a detailed discussion of implicit naming see Naming.

For basic type attributes, the implicit naming rule is that the column name is the same as the attribute name. If that implicit naming rule does not meet your requirements, you can explicitly tell Hibernate (and other providers) the column name to use.

Example 5. Explicit column naming
@Entity(name = "Product")
public class Product {

    @Id
    private Integer id;

    private String sku;

    private String name;

    @Column( name = "NOTES" )
    private String description;
}

Here we use @Column to explicitly map the description attribute to the NOTES column, as opposed to the implicit column name description.

The @Column annotation defines other mapping information as well. See its Javadocs for details.

2.3.4. BasicTypeRegistry

We said before that a Hibernate type is not a Java type, nor a SQL type, but that it understands both and performs the marshalling between them. But looking at the basic type mappings from the previous examples, how did Hibernate know to use its org.hibernate.type.StringType for mapping for java.lang.String attributes, or its org.hibernate.type.IntegerType for mapping java.lang.Integer attributes?

The answer lies in a service inside Hibernate called the org.hibernate.type.BasicTypeRegistry, which essentially maintains a map of org.hibernate.type.BasicType (a org.hibernate.type.Type specialization) instances keyed by a name.

We will see later, in the Explicit BasicTypes section, that we can explicitly tell Hibernate which BasicType to use for a particular attribute. But first let’s explore how implicit resolution works and how applications can adjust implicit resolution.

A thorough discussion of the BasicTypeRegistry and all the different ways to contribute types to it is beyond the scope of this documentation. Please see Integrations Guide for complete details.

As an example, take a String attribute such as we saw before with Product#sku. Since there was no explicit type mapping, Hibernate looks to the BasicTypeRegistry to find the registered mapping for java.lang.String. This goes back to the "BasicTypeRegistry key(s)" column we saw in the tables at the start of this chapter.

As a baseline within BasicTypeRegistry, Hibernate follows the recommended mappings of JDBC for Java types. JDBC recommends mapping Strings to VARCHAR, which is the exact mapping that StringType handles. So that is the baseline mapping within BasicTypeRegistry for Strings.

Applications can also extend (add new BasicType registrations) or override (replace an existing BasicType registration) using one of the MetadataBuilder#applyBasicType methods or the MetadataBuilder#applyTypes method during bootstrap. For more details, see Custom BasicTypes section.

2.3.5. Explicit BasicTypes

Sometimes you want a particular attribute to be handled differently. Occasionally Hibernate will implicitly pick a BasicType that you do not want (and for some reason you do not want to adjust the BasicTypeRegistry).

In these cases you must explicitly tell Hibernate the BasicType to use, via the org.hibernate.annotations.Type annotation.

Example 6. Using @org.hibernate.annotations.Type
@Entity(name = "Product")
public class Product {

    @Id
    private Integer id;

    private String sku;

    @org.hibernate.annotations.Type( type = "nstring" )
    private String name;

    @org.hibernate.annotations.Type( type = "materialized_nclob" )
    private String description;
}

This tells Hibernate to store the Strings as nationalized data. This is just for illustration purposes; for better ways to indicate nationalized character data see Mapping Nationalized Character Data section.

Additionally, the description is to be handled as a LOB. Again, for better ways to indicate LOBs see Mapping LOBs section.

The org.hibernate.annotations.Type#type attribute can name any of the following:

  • Fully qualified name of any org.hibernate.type.Type implementation

  • Any key registered with BasicTypeRegistry

  • The name of any known type definitions

2.3.6. Custom BasicTypes

Hibernate makes it relatively easy for developers to create their own basic type mappings type. For example, you might want to persist properties of type java.util.BigInteger to VARCHAR columns, or support completely new types.

There are two approaches to developing a custom type:

  • implementing a BasicType and registering it

  • implementing a UserType which doesn’t require type registration

As a means of illustrating the different approaches, let’s consider a use case where we need to support a java.util.BitSet mapping that’s stored as a VARCHAR.

Implementing a BasicType

The first approach is to directly implement the BasicType interface.

Because the BasicType interface has a lot of methods to implement, it’s much more convenient to extend the AbstractStandardBasicType, or the AbstractSingleColumnStandardBasicType if the value is stored in a single database column.

First, we need to extend the AbstractSingleColumnStandardBasicType like this:

Example 7. Custom BasicType implementation
public class BitSetType
        extends AbstractSingleColumnStandardBasicType<BitSet>
        implements DiscriminatorType<BitSet> {

    public static final BitSetType INSTANCE = new BitSetType();

    public BitSetType() {
        super( VarcharTypeDescriptor.INSTANCE, BitSetTypeDescriptor.INSTANCE );
    }

    @Override
    public BitSet stringToObject(String xml) throws Exception {
        return fromString( xml );
    }

    @Override
    public String objectToSQLString(BitSet value, Dialect dialect) throws Exception {
        return toString( value );
    }

    @Override
    public String getName() {
        return "bitset";
    }

}

The AbstractSingleColumnStandardBasicType requires an sqlTypeDescriptor and a javaTypeDescriptor. The sqlTypeDescriptor is VarcharTypeDescriptor.INSTANCE because the database column is a VARCHAR. On the Java side, we need to use a BitSetTypeDescriptor instance which can be implemented like this:

Example 8. Custom AbstractTypeDescriptor implementation
public class BitSetTypeDescriptor extends AbstractTypeDescriptor<BitSet> {

    private static final String DELIMITER = ",";

    public static final BitSetTypeDescriptor INSTANCE = new BitSetTypeDescriptor();

    public BitSetTypeDescriptor() {
        super( BitSet.class );
    }

    @Override
    public String toString(BitSet value) {
        StringBuilder builder = new StringBuilder();
        for ( long token : value.toLongArray() ) {
            if ( builder.length() > 0 ) {
                builder.append( DELIMITER );
            }
            builder.append( Long.toString( token, 2 ) );
        }
        return builder.toString();
    }

    @Override
    public BitSet fromString(String string) {
        if ( string == null || string.isEmpty() ) {
            return null;
        }
        String[] tokens = string.split( DELIMITER );
        long[] values = new long[tokens.length];

        for ( int i = 0; i < tokens.length; i++ ) {
            values[i] = Long.valueOf( tokens[i], 2 );
        }
        return BitSet.valueOf( values );
    }

    @SuppressWarnings({"unchecked"})
    public <X> X unwrap(BitSet value, Class<X> type, WrapperOptions options) {
        if ( value == null ) {
            return null;
        }
        if ( BitSet.class.isAssignableFrom( type ) ) {
            return (X) value;
        }
        if ( String.class.isAssignableFrom( type ) ) {
            return (X) toString( value);
        }
        throw unknownUnwrap( type );
    }

    public <X> BitSet wrap(X value, WrapperOptions options) {
        if ( value == null ) {
            return null;
        }
        if ( String.class.isInstance( value ) ) {
            return fromString( (String) value );
        }
        if ( BitSet.class.isInstance( value ) ) {
            return (BitSet) value;
        }
        throw unknownWrap( value.getClass() );
    }
}

The unwrap method is used when passing a BitSet as a PreparedStatement bind parameter, while the wrap method is used to transform the JDBC column value object (e.g. String in our case) to the actual mapping object type (e.g. BitSet in this example).

The BasicType must be registered, and this can be done at bootstrapping time:

Example 9. Register a Custom BasicType implementation
configuration.registerTypeContributor( (typeContributions, serviceRegistry) -> {
    typeContributions.contributeType( BitSetType.INSTANCE );
} );

or using the MetadataBuilder

ServiceRegistry standardRegistry =
    new StandardServiceRegistryBuilder().build();

MetadataSources sources = new MetadataSources( standardRegistry );

MetadataBuilder metadataBuilder = sources.getMetadataBuilder();

metadataBuilder.applyBasicType( BitSetType.INSTANCE );

With the new BitSetType being registered as bitset, the entity mapping looks like this:

Example 10. Custom BasicType mapping
@Entity(name = "Product")
public static class Product {

    @Id
    private Integer id;

    @Type( type = "bitset" )
    private BitSet bitSet;

    public Integer getId() {
        return id;
    }

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

    public BitSet getBitSet() {
        return bitSet;
    }

    public void setBitSet(BitSet bitSet) {
        this.bitSet = bitSet;
    }
}

Alternatively, use can use a @TypeDef ans skip the registration phase:

Example 11. Using @TypeDef to register a custom Type
@Entity(name = "Product")
@TypeDef(
    name = "bitset",
    defaultForType = BitSet.class,
    typeClass = BitSetType.class
)
public static class Product {

    @Id
    private Integer id;

    private BitSet bitSet;

    public Integer getId() {
        return id;
    }

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

    public BitSet getBitSet() {
        return bitSet;
    }

    public void setBitSet(BitSet bitSet) {
        this.bitSet = bitSet;
    }
}

To validate this new BasicType implementation, we can test it as follows:

Example 12. Persisting the custom BasicType
BitSet bitSet = BitSet.valueOf( new long[] {1, 2, 3} );

doInHibernate( this::sessionFactory, session -> {
    Product product = new Product( );
    product.setId( 1 );
    product.setBitSet( bitSet );
    session.persist( product );
} );

doInHibernate( this::sessionFactory, session -> {
    Product product = session.get( Product.class, 1 );
    assertEquals(bitSet, product.getBitSet());
} );

When executing this unit test, Hibernate generates the following SQL statements:

Example 13. Persisting the custom BasicType
DEBUG SQL:92 -
    insert
    into
        Product
        (bitSet, id)
    values
        (?, ?)

TRACE BasicBinder:65 - binding parameter [1] as [VARCHAR] - [{0, 65, 128, 129}]
TRACE BasicBinder:65 - binding parameter [2] as [INTEGER] - [1]

DEBUG SQL:92 -
    select
        bitsettype0_.id as id1_0_0_,
        bitsettype0_.bitSet as bitSet2_0_0_
    from
        Product bitsettype0_
    where
        bitsettype0_.id=?

TRACE BasicBinder:65 - binding parameter [1] as [INTEGER] - [1]
TRACE BasicExtractor:61 - extracted value ([bitSet2_0_0_] : [VARCHAR]) - [{0, 65, 128, 129}]

As you can see, the BitSetType takes care of the Java-to-SQL and SQL-to-Java type conversion.

Implementing a UserType

The second approach is to implement the UserType interface.

Example 14. Custom UserType implementation
public class BitSetUserType implements UserType {

	public static final BitSetUserType INSTANCE = new BitSetUserType();

    private static final Logger log = Logger.getLogger( BitSetUserType.class );

    @Override
    public int[] sqlTypes() {
        return new int[] {StringType.INSTANCE.sqlType()};
    }

    @Override
    public Class returnedClass() {
        return BitSet.class;
    }

    @Override
    public boolean equals(Object x, Object y)
			throws HibernateException {
        return Objects.equals( x, y );
    }

    @Override
    public int hashCode(Object x)
			throws HibernateException {
        return Objects.hashCode( x );
    }

    @Override
    public Object nullSafeGet(
            ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
            throws HibernateException, SQLException {
        String columnName = names[0];
        String columnValue = (String) rs.getObject( columnName );
        log.debugv("Result set column {0} value is {1}", columnName, columnValue);
        return columnValue == null ? null :
				BitSetTypeDescriptor.INSTANCE.fromString( columnValue );
    }

    @Override
    public void nullSafeSet(
            PreparedStatement st, Object value, int index, SharedSessionContractImplementor session)
            throws HibernateException, SQLException {
        if ( value == null ) {
            log.debugv("Binding null to parameter {0} ",index);
            st.setNull( index, Types.VARCHAR );
        }
        else {
            String stringValue = BitSetTypeDescriptor.INSTANCE.toString( (BitSet) value );
            log.debugv("Binding {0} to parameter {1} ", stringValue, index);
            st.setString( index, stringValue );
        }
    }

    @Override
    public Object deepCopy(Object value)
			throws HibernateException {
        return value == null ? null :
            BitSet.valueOf( BitSet.class.cast( value ).toLongArray() );
    }

    @Override
    public boolean isMutable() {
        return true;
    }

    @Override
    public Serializable disassemble(Object value)
			throws HibernateException {
        return (BitSet) deepCopy( value );
    }

    @Override
    public Object assemble(Serializable cached, Object owner)
			throws HibernateException {
        return deepCopy( cached );
    }

    @Override
    public Object replace(Object original, Object target, Object owner)
			throws HibernateException {
        return deepCopy( original );
    }
}

The entity mapping looks as follows:

Example 15. Custom UserType mapping
@Entity(name = "Product")
public static class Product {

    @Id
    private Integer id;

    @Type( type = "bitset" )
    private BitSet bitSet;

    public Integer getId() {
        return id;
    }

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

    public BitSet getBitSet() {
        return bitSet;
    }

    public void setBitSet(BitSet bitSet) {
        this.bitSet = bitSet;
    }
}

In this example, the UserType is registered under the bitset name, and this is done like this:

Example 16. Register a Custom UserType implementation
configuration.registerTypeContributor( (typeContributions, serviceRegistry) -> {
    typeContributions.contributeType( BitSetUserType.INSTANCE, "bitset");
} );

or using the MetadataBuilder

ServiceRegistry standardRegistry =
    new StandardServiceRegistryBuilder().build();

MetadataSources sources = new MetadataSources( standardRegistry );

MetadataBuilder metadataBuilder = sources.getMetadataBuilder();

metadataBuilder.applyBasicType( BitSetUserType.INSTANCE, "bitset" );

Like BasicType, you can also register the UserType using a simple name.

Without registration, the UserType mapping requires the fully-classified name:

@Type( type = "org.hibernate.userguide.mapping.basic.BitSetUserType" )

When running the previous test case against the BitSetUserType entity mapping, Hibernate executed the following SQL statements:

Example 17. Persisting the custom BasicType
DEBUG SQL:92 -
    insert
    into
        Product
        (bitSet, id)
    values
        (?, ?)

DEBUG BitSetUserType:71 - Binding 1,10,11 to parameter 1
TRACE BasicBinder:65 - binding parameter [2] as [INTEGER] - [1]

DEBUG SQL:92 -
    select
        bitsetuser0_.id as id1_0_0_,
        bitsetuser0_.bitSet as bitSet2_0_0_
    from
        Product bitsetuser0_
    where
        bitsetuser0_.id=?

TRACE BasicBinder:65 - binding parameter [1] as [INTEGER] - [1]
DEBUG BitSetUserType:56 - Result set column bitSet2_0_0_ value is 1,10,11

2.3.7. Mapping enums

Hibernate supports the mapping of Java enums as basic value types in a number of different ways.

@Enumerated

The original JPA-compliant way to map enums was via the @Enumerated and @MapKeyEnumerated for map keys annotations which works on the principle that the enum values are stored according to one of 2 strategies indicated by javax.persistence.EnumType:

ORDINAL

stored according to the enum value’s ordinal position within the enum class, as indicated by java.lang.Enum#ordinal

STRING

stored according to the enum value’s name, as indicated by java.lang.Enum#name

Assuming the following enumeration:

Example 18. PhoneType enumeration
public enum PhoneType {
    LAND_LINE,
    MOBILE;
}

In the ORDINAL example, the phone_type column is defined as an (nullable) INTEGER type and would hold:

NULL

For null values

0

For the LAND_LINE enum

1

For the MOBILE enum

Example 19. @Enumerated(ORDINAL) example
@Entity(name = "Phone")
public static class Phone {

    @Id
    private Long id;

    @Column(name = "phone_number")
    private String number;

    @Enumerated(EnumType.ORDINAL)
    @Column(name = "phone_type")
    private PhoneType type;

    //Getters and setters are omitted for brevity

}

When persisting this entity, Hibernate generates the following SQL statement:

Example 20. Persisting an entity with an @Enumerated(ORDINAL) mapping
Phone phone = new Phone( );
phone.setId( 1L );
phone.setNumber( "123-456-78990" );
phone.setType( PhoneType.MOBILE );
entityManager.persist( phone );
INSERT INTO Phone (phone_number, phone_type, id)
VALUES ('123-456-78990', 2, 1)

In the STRING example, the phone_type column is defined as a (nullable) VARCHAR type and would hold:

NULL

For null values

LAND_LINE

For the LAND_LINE enum

MOBILE

For the MOBILE enum

Example 21. @Enumerated(STRING) example
@Entity(name = "Phone")
public static class Phone {

    @Id
    private Long id;

    @Column(name = "phone_number")
    private String number;

    @Enumerated(EnumType.STRING)
    @Column(name = "phone_type")
    private PhoneType type;

    //Getters and setters are omitted for brevity

}

Persisting the same entity like in the @Enumerated(ORDINAL) example, Hibernate generates the following SQL statement:

Example 22. Persisting an entity with an @Enumerated(STRING) mapping
INSERT INTO Phone (phone_number, phone_type, id)
VALUES ('123-456-78990', 'MOBILE', 1)
AttributeConverter

Let’s consider the following Gender enum which stores its values using the 'M' and 'F' codes.

Example 23. Enum with custom constructor
public enum Gender {

    MALE( 'M' ),
    FEMALE( 'F' );

    private final char code;

    Gender(char code) {
        this.code = code;
    }

    public static Gender fromCode(char code) {
        if ( code == 'M' || code == 'm' ) {
            return MALE;
        }
        if ( code == 'F' || code == 'f' ) {
            return FEMALE;
        }
        throw new UnsupportedOperationException(
            "The code " + code + " is not supported!"
        );
    }

    public char getCode() {
        return code;
    }
}

You can map enums in a JPA compliant way using a JPA 2.1 AttributeConverter.

Example 24. Enum mapping with AttributeConverter example
@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;

    private String name;

    @Convert( converter = GenderConverter.class )
    public Gender gender;

    //Getters and setters are omitted for brevity

}

@Converter
public static class GenderConverter
        implements AttributeConverter<Gender, Character> {

    public Character convertToDatabaseColumn( Gender value ) {
        if ( value == null ) {
            return null;
        }

        return value.getCode();
    }

    public Gender convertToEntityAttribute( Character value ) {
        if ( value == null ) {
            return null;
        }

        return Gender.fromCode( value );
    }
}

Here, the gender column is defined as a CHAR type and would hold:

NULL

For null values

'M'

For the MALE enum

'F'

For the FEMALE enum

For additional details on using AttributeConverters, see JPA 2.1 AttributeConverters section.

JPA explicitly disallows the use of an AttributeConverter with an attribute marked as @Enumerated. So if using the AttributeConverter approach, be sure not to mark the attribute as @Enumerated.

Custom type

You can also map enums using a Hibernate custom type mapping. Let’s again revisit the Gender enum example, this time using a custom Type to store the more standardized 'M' and 'F' codes.

Example 25. Enum mapping with custom Type example
@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;

    private String name;

    @Type( type = "org.hibernate.userguide.mapping.basic.GenderType" )
    public Gender gender;

    //Getters and setters are omitted for brevity

}

public class GenderType extends AbstractSingleColumnStandardBasicType<Gender> {

    public static final GenderType INSTANCE = new GenderType();

    public GenderType() {
        super(
            CharTypeDescriptor.INSTANCE,
            GenderJavaTypeDescriptor.INSTANCE
        );
    }

    public String getName() {
        return "gender";
    }

    @Override
    protected boolean registerUnderJavaType() {
        return true;
    }
}

public class GenderJavaTypeDescriptor extends AbstractTypeDescriptor<Gender> {

    public static final GenderJavaTypeDescriptor INSTANCE =
        new GenderJavaTypeDescriptor();

    protected GenderJavaTypeDescriptor() {
        super( Gender.class );
    }

    public String toString(Gender value) {
        return value == null ? null : value.name();
    }

    public Gender fromString(String string) {
        return string == null ? null : Gender.valueOf( string );
    }

    public <X> X unwrap(Gender value, Class<X> type, WrapperOptions options) {
        return CharacterTypeDescriptor.INSTANCE.unwrap(
            value == null ? null : value.getCode(),
            type,
            options
        );
    }

    public <X> Gender wrap(X value, WrapperOptions options) {
        return Gender.fromCode(
            CharacterTypeDescriptor.INSTANCE.wrap( value, options )
        );
    }
}

Again, the gender column is defined as a CHAR type and would hold:

NULL

For null values

'M'

For the MALE enum

'F'

For the FEMALE enum

For additional details on using custom types, see Custom BasicTypes section.

2.3.8. Mapping LOBs

Mapping LOBs (database Large Objects) come in 2 forms, those using the JDBC locator types and those materializing the LOB data.

JDBC LOB locators exist to allow efficient access to the LOB data. They allow the JDBC driver to stream parts of the LOB data as needed, potentially freeing up memory space. However they can be unnatural to deal with and have certain limitations. For example, a LOB locator is only portably valid during the duration of the transaction in which it was obtained.

The idea of materialized LOBs is to trade-off the potential efficiency (not all drivers handle LOB data efficiently) for a more natural programming paradigm using familiar Java types such as String or byte[], etc for these LOBs.

Materialized deals with the entire LOB contents in memory, whereas LOB locators (in theory) allow streaming parts of the LOB contents into memory as needed.

The JDBC LOB locator types include:

  • java.sql.Blob

  • java.sql.Clob

  • java.sql.NClob

Mapping materialized forms of these LOB values would use more familiar Java types such as String, char[], byte[], etc. The trade off for more familiar is usually performance.

For a first look, let’s assume we have a CLOB column that we would like to map (NCLOB character LOB data will be covered in Mapping Nationalized Character Data section).

Example 26. CLOB - SQL
CREATE TABLE Product (
  id INTEGER NOT NULL
  image clob
  name VARCHAR(255)
  PRIMARY KEY ( id )
)

Let’s first map this using the @Lob JPA annotation and the java.sql.Clob type:

Example 27. CLOB mapped to java.sql.Clob
@Entity(name = "Product")
public static class Product {

    @Id
    private Integer id;

    private String name;

    @Lob
    private Clob warranty;

    //Getters and setters are omitted for brevity

}

To persist such an entity, you have to create a Clob using plain JDBC:

Example 28. Persisting a java.sql.Clob entity
String warranty = "My product warranty";

final Product product = new Product();
product.setId( 1 );
product.setName( "Mobile phone" );

session.doWork( connection -> {
    product.setWarranty( ClobProxy.generateProxy( warranty ) );
} );

entityManager.persist( product );

To retrieve the Clob content, you need to transform the underlying java.io.Reader:

Example 29. Returning a java.sql.Clob entity
Product product = entityManager.find( Product.class, productId );
try (Reader reader = product.getWarranty().getCharacterStream()) {
    assertEquals( "My product warranty", toString( reader ) );
}

We could also map the CLOB in a materialized form. This way, we can either use a String or a char[].

Example 30. CLOB mapped to String
@Entity(name = "Product")
public static class Product {

    @Id
    private Integer id;

    private String name;

    @Lob
    private String warranty;

    //Getters and setters are omitted for brevity

}

How JDBC deals with LOB data varies from driver to driver, and Hibernate tries to handle all these variances on your behalf.

However, some drivers are trickier (e.g. PostgreSQL JDBC drivers), and, in such cases, you may have to do some extra to get LOBs working. Such discussions are beyond the scope of this guide.

We might even want the materialized data as a char array (for some crazy reason).

Example 31. CLOB - materialized char[] mapping
@Entity(name = "Product")
public static class Product {

    @Id
    private Integer id;

    private String name;

    @Lob
    private char[] warranty;

    //Getters and setters are omitted for brevity

}

BLOB data is mapped in a similar fashion.

Example 32. BLOB - SQL
CREATE TABLE Product (
    id INTEGER NOT NULL ,
    image blob ,
    name VARCHAR(255) ,
    PRIMARY KEY ( id )
)

Let’s first map this using the JDBC java.sql.Blob type.

Example 33. BLOB mapped to java.sql.Blob
@Entity(name = "Product")
public static class Product {

    @Id
    private Integer id;

    private String name;

    @Lob
    private Blob image;

    //Getters and setters are omitted for brevity

}

To persist such an entity, you have to create a Blob using plain JDBC:

Example 34. Persisting a java.sql.Blob entity
byte[] image = new byte[] {1, 2, 3};

final Product product = new Product();
product.setId( 1 );
product.setName( "Mobile phone" );

session.doWork( connection -> {
    product.setImage( BlobProxy.generateProxy( image ) );
} );

entityManager.persist( product );

To retrieve the Blob content, you need to transform the underlying java.io.Reader:

Example 35. Returning a java.sql.Blob entity
Product product = entityManager.find( Product.class, productId );

try (InputStream inputStream = product.getImage().getBinaryStream()) {
    assertArrayEquals(new byte[] {1, 2, 3}, toBytes( inputStream ) );
}

We could also map the BLOB in a materialized form (e.g. byte[]).

Example 36. BLOB mapped to byte[]
@Entity(name = "Product")
public static class Product {

    @Id
    private Integer id;

    private String name;

    @Lob
    private byte[] image;

    //Getters and setters are omitted for brevity

}

2.3.9. Mapping Nationalized Character Data

JDBC 4 added the ability to explicitly handle nationalized character data. To this end it added specific nationalized character data types.

  • NCHAR

  • NVARCHAR

  • LONGNVARCHAR

  • NCLOB

Example 37. NVARCHAR - SQL
CREATE TABLE Product (
    id INTEGER NOT NULL ,
    name VARCHAR(255) ,
    warranty NVARCHAR(255) ,
    PRIMARY KEY ( id )
)

To map a specific attribute to a nationalized variant data type, Hibernate defines the @Nationalized annotation.

Example 38. NVARCHAR mapping
@Entity(name = "Product")
public static class Product {

    @Id
    private Integer id;

    private String name;

    @Nationalized
    private String warranty;

    //Getters and setters are omitted for brevity

}

Just like with CLOB, Hibernate can also deal with NCLOB SQL data types:

Example 39. NCLOB - SQL
CREATE TABLE Product (
    id INTEGER NOT NULL ,
    name VARCHAR(255) ,
    warranty nclob ,
    PRIMARY KEY ( id )
)

Hibernate can map the NCLOB to a java.sql.NClob

Example 40. NCLOB mapped to java.sql.NClob
@Entity(name = "Product")
public static class Product {

    @Id
    private Integer id;

    private String name;

    @Lob
    @Nationalized
    // Clob also works, because NClob extends Clob.
    // The database type is still NCLOB either way and handled as such.
    private NClob warranty;

    //Getters and setters are omitted for brevity

}

To persist such an entity, you have to create a NClob using plain JDBC:

Example 41. Persisting a java.sql.NClob entity
String warranty = "My product warranty";

final Product product = new Product();
product.setId( 1 );
product.setName( "Mobile phone" );

session.doWork( connection -> {
    product.setWarranty( connection.createNClob() );
    product.getWarranty().setString( 1, warranty );
} );

entityManager.persist( product );

To retrieve the NClob content, you need to transform the underlying java.io.Reader:

Example 42. Returning a java.sql.NClob entity
Product product = entityManager.find( Product.class, productId );
try (Reader reader = product.getWarranty().getCharacterStream()) {
    assertEquals( "My product warranty", toString( reader ) );
}

We could also map the NCLOB in a materialized form. This way, we can either use a String or a char[].

Example 43. NCLOB mapped to String
@Entity(name = "Product")
public static class Product {

    @Id
    private Integer id;

    private String name;

    @Lob
    @Nationalized
    private String warranty;

    //Getters and setters are omitted for brevity

}

We might even want the materialized data as a char array.

Example 44. NCLOB - materialized char[] mapping
@Entity(name = "Product")
public static class Product {

    @Id
    private Integer id;

    private String name;

    @Lob
    @Nationalized
    private char[] warranty;

    //Getters and setters are omitted for brevity

}

If you application and database are entirely nationalized you may instead want to enable nationalized character data as the default. You can do this via the hibernate.use_nationalized_character_data setting or by calling MetadataBuilder#enableGlobalNationalizedCharacterDataSupport during bootstrap.

2.3.10. Mapping UUID Values

Hibernate also allows you to map UUID values, again in a number of ways.

The default UUID mapping is as binary because it represents more efficient storage. However many applications prefer the readability of character storage. To switch the default mapping, simply call MetadataBuilder.applyBasicType( UUIDCharType.INSTANCE, UUID.class.getName() ).

2.3.11. UUID as binary

As mentioned, the default mapping for UUID attributes. Maps the UUID to a byte[] using java.util.UUID#getMostSignificantBits and java.util.UUID#getLeastSignificantBits and stores that as BINARY data.

Chosen as the default simply because it is generally more efficient from storage perspective.

2.3.12. UUID as (var)char

Maps the UUID to a String using java.util.UUID#toString and java.util.UUID#fromString and stores that as CHAR or VARCHAR data.

2.3.13. PostgeSQL-specific UUID

When using one of the PostgreSQL Dialects, this becomes the default UUID mapping.

Maps the UUID using PostgreSQL’s specific UUID data type. The PostgreSQL JDBC driver chooses to map its UUID type to the OTHER code. Note that this can cause difficulty as the driver chooses to map many different data types to OTHER.

2.3.14. UUID as identifier

Hibernate supports using UUID values as identifiers, and they can even be generated on user’s behalf. For details, see the discussion of generators in Identifier generators.

2.3.15. Mapping Date/Time Values

Hibernate allows various Java Date/Time classes to be mapped as persistent domain model entity properties. The SQL standard defines three Date/Time types:

DATE

Represents a calendar date by storing years, months and days. The JDBC equivalent is java.sql.Date

TIME

Represents the time of a day and it stores hours, minutes and seconds. The JDBC equivalent is java.sql.Time

TIMESTAMP

It stores both a DATE and a TIME plus nanoseconds. The JDBC equivalent is java.sql.Timestamp

To avoid dependencies on the java.sql package, it’s common to use the java.util or java.time Date/Time classes instead.

While the java.sql classes define a direct association to the SQL Date/Time data types, the java.util or java.time properties need to explicitly mark the SQL type correlation with the @Temporal annotation. This way, a java.util.Date or a java.util.Calendar can be mapped to either an SQL DATE, TIME or TIMESTAMP type.

Considering the following entity:

Example 45. java.util.Date mapped as DATE
@Entity(name = "DateEvent")
public static class DateEvent {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "`timestamp`")
    @Temporal(TemporalType.DATE)
    private Date timestamp;

    //Getters and setters are omitted for brevity

}

When persisting such entity:

Example 46. Persisting a java.util.Date mapping
DateEvent dateEvent = new DateEvent( new Date() );
entityManager.persist( dateEvent );

Hibernate generates the following INSERT statement:

INSERT INTO DateEvent ( timestamp, id )
VALUES ( '2015-12-29', 1 )

Only the year, month and the day field were saved into the database.

If we change the @Temporal type to TIME:

Example 47. java.util.Date mapped as TIME
@Column(name = "`timestamp`")
@Temporal(TemporalType.TIME)
private Date timestamp;

Hibernate will issue an INSERT statement containing the hour, minutes and seconds.

INSERT INTO DateEvent ( timestamp, id )
VALUES ( '16:51:58', 1 )

When the @Temporal type is set to TIMESTAMP:

Example 48. java.util.Date mapped as TIMESTAMP
@Column(name = "`timestamp`")
@Temporal(TemporalType.TIMESTAMP)
private Date timestamp;

Hibernate will include both the DATE, the TIME and the nanoseconds in the INSERT statement:

INSERT INTO DateEvent ( timestamp, id )
VALUES ( '2015-12-29 16:54:04.544', 1 )

Just like the java.util.Date, the java.util.Calendar requires the @Temporal annotation in order to know what JDBC data type to be chosen: DATE, TIME or TIMESTAMP. If the java.util.Date marks a point in time, the java.util.Calendar takes into consideration the default Time Zone.

Mapping Java 8 Date/Time Values

Java 8 came with a new Date/Time API, offering support for instant dates, intervals, local and zoned Date/Time immutable instances, bundled in the java.time package.

The mapping between the standard SQL Date/Time types and the supported Java 8 Date/Time class types looks as follows;

DATE

java.time.LocalDate

TIME

java.time.LocalTime, java.time.OffsetTime

TIMESTAMP

java.time.Instant, java.time.LocalDateTime, java.time.OffsetDateTime and java.time.ZonedDateTime

Because the mapping between Java 8 Date/Time classes and the SQL types is implicit, there is not need to specify the @Temporal annotation. Setting it on the java.time classes throws the following exception:

org.hibernate.AnnotationException: @Temporal should only be set on a java.util.Date or java.util.Calendar property
Using a specific time zone

By default, Hibernate is going to use the PreparedStatement.setTimestamp(int parameterIndex, java.sql.Timestamp) or PreparedStatement.setTime(int parameterIndex, java.sql.Time x) when saving a java.sql.Timestamp or a java.sql.Time property.

When the time zone is not specified, the JDBC driver is going to use the underlying JVM default time zone, which might not be suitable if the application is used from all across the globe. For this reason, it is very common to use a single reference time zone (e.g. UTC) whenever saving/loading data from the database.

One alternative would be to configure all JVMs to use the reference time zone:

Declaratively
java -Duser.timezone=UTC ...
Programmatically
TimeZone.setDefault( TimeZone.getTimeZone( "UTC" ) );

However, as explained in this article, this is not always practical especially for front-end nodes. For this reason, Hibernate offers the hibernate.jdbc.time_zone configuration property which can be configured:

Declaratively, at the SessionFactory level
settings.put(
    AvailableSettings.JDBC_TIME_ZONE,
    TimeZone.getTimeZone( "UTC" )
);
Programmatically, on a per Session basis
Session session = sessionFactory()
    .withOptions()
    .jdbcTimeZone( TimeZone.getTimeZone( "UTC" ) )
    .openSession();

With this configuration property in place, Hibernate is going to call the PreparedStatement.setTimestamp(int parameterIndex, java.sql.Timestamp, Calendar cal) or PreparedStatement.setTime(int parameterIndex, java.sql.Time x, Calendar cal), where the java.util.Calendar references the time zone provided via the hibernate.jdbc.time_zone property.

2.3.16. JPA 2.1 AttributeConverters

Although Hibernate has long been offering custom types, as a JPA 2.1 provider, it also supports AttributeConverter as well.

With a custom AttributeConverter, the application developer can map a given JDBC type to an entity basic type.

In the following example, the java.util.Period is going to be mapped to a VARCHAR database column.

Example 49. java.util.Period custom AttributeConverter
@Converter
public class PeriodStringConverter
        implements AttributeConverter<Period, String> {

    @Override
    public String convertToDatabaseColumn(Period attribute) {
        return attribute.toString();
    }

    @Override
    public Period convertToEntityAttribute(String dbData) {
        return Period.parse( dbData );
    }
}

To make use of this custom converter, the @Convert annotation must decorate the entity attribute.

Example 50. Entity using the custom java.util.Period AttributeConverter mapping
@Entity(name = "Event")
public static class Event {

    @Id
    @GeneratedValue
    private Long id;

    @Convert(converter = PeriodStringConverter.class)
    @Column(columnDefinition = "")
    private Period span;

    //Getters and setters are omitted for brevity

}

When persisting such entity, Hibernate will do the type conversion based on the AttributeConverter logic:

Example 51. Persisting entity using the custom AttributeConverter
INSERT INTO Event ( span, id )
VALUES ( 'P1Y2M3D', 1 )
AttributeConverter Java and JDBC types

In cases when the Java type specified for the "database side" of the conversion (the second AttributeConverter bind parameter) is not known, Hibernate will fallback to a java.io.Serializable type.

If the Java type is not know to Hibernate, you will encounter the following message:

HHH000481: Encountered Java type for which we could not locate a JavaTypeDescriptor and which does not appear to implement equals and/or hashCode. This can lead to significant performance problems when performing equality/dirty checking involving this Java type. Consider registering a custom JavaTypeDescriptor or at least implementing equals/hashCode.

Whether a Java type is "known" means it has an entry in the JavaTypeDescriptorRegistry. While by default Hibernate loads many JDK types into the JavaTypeDescriptorRegistry, an application can also expand the JavaTypeDescriptorRegistry by adding new JavaTypeDescriptor entries.

This way, Hibernate will also know how to handle a specific Java Object type at the JDBC level.

JPA 2.1 AttributeConverter Mutability Plan

A basic type that’s converted by a JPA AttributeConverter is immutable if the underlying Java type is immutable and is mutable if the associated attribute type is mutable as well.

Therefore, mutability is given by the JavaTypeDescriptor#getMutabilityPlan of the associated entity attribute type.

Immutable types

If the entity attribute is a String, a primitive wrapper (e.g. Integer, Long) an Enum type, or any other immutable Object type, then you can only change the entity attribute value by reassigning it to a new value.

Considering we have the same Period entity attribute as illustrated in the JPA 2.1 AttributeConverters section:

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

    @Id
    @GeneratedValue
    private Long id;

    @Convert(converter = PeriodStringConverter.class)
    @Column(columnDefinition = "")
    private Period span;

    //Getters and setters are omitted for brevity

}

The only way to change the span attribute is to reassign it to a different value:

Event event = entityManager.createQuery( "from Event", Event.class ).getSingleResult();
event.setSpan(Period
    .ofYears( 3 )
    .plusMonths( 2 )
    .plusDays( 1 )
);
Mutable types

On the other hand, consider the following example where the Money type is a mutable.

public static class Money {

    private long cents;

    public Money(long cents) {
        this.cents = cents;
    }

    public long getCents() {
        return cents;
    }

    public void setCents(long cents) {
        this.cents = cents;
    }
}

public static class MoneyConverter
        implements AttributeConverter<Money, Long> {

    @Override
    public Long convertToDatabaseColumn(Money attribute) {
        return attribute == null ? null : attribute.getCents();
    }

    @Override
    public Money convertToEntityAttribute(Long dbData) {
        return dbData == null ? null : new Money( dbData );
    }
}

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

    @Id
    private Long id;

    private String owner;

    @Convert(converter = MoneyConverter.class)
    private Money balance;

    //Getters and setters are omitted for brevity

}

A mutable Object allows you to modify its internal structure, and Hibernate dirty checking mechanism is going to propagate the change to the database:

Account account = entityManager.find( Account.class, 1L );
account.getBalance().setCents( 150 * 100L );
entityManager.persist( account );

Although the AttributeConverter types can be mutable so that dirty checking, deep copying and second-level caching work properly, treating these as immutable (when they really are) is more efficient.

For this reason, prefer immutable types over mutable ones whenever possible.

2.3.17. SQL quoted identifiers

You can force Hibernate to quote an identifier in the generated SQL by enclosing the table or column name in backticks in the mapping document. While traditionally, Hibernate used backticks for escaping SQL reserved keywords, JPA uses double quotes instead.

Once the reserved keywords are escaped, Hibernate will use the correct quotation style for the SQL Dialect. This is usually double quotes, but SQL Server uses brackets and MySQL uses backticks.

Example 52. Hibernate legacy quoting
@Entity(name = "Product")
public static class Product {

    @Id
    private Long id;

    @Column(name = "`name`")
    private String name;

    @Column(name = "`number`")
    private String number;

    //Getters and setters are omitted for brevity

}
Example 53. JPA quoting
@Entity(name = "Product")
public static class Product {

    @Id
    private Long id;

    @Column(name = "\"name\"")
    private String name;

    @Column(name = "\"number\"")
    private String number;

    //Getters and setters are omitted for brevity

}

Because name and number are reserved words, the Product entity mapping uses backtricks to quote these column names.

When saving the following Product entity, Hibernate generates the following SQL insert statement:

Example 54. Persisting a quoted column name
Product product = new Product();
product.setId( 1L );
product.setName( "Mobile phone" );
product.setNumber( "123-456-7890" );
entityManager.persist( product );
INSERT INTO Product ("name", "number", id)
VALUES ('Mobile phone', '123-456-7890', 1)
Global quoting

Hibernate can also quote all identifiers (e.g. table, columns) using the following configuration property:

<property
    name="hibernate.globally_quoted_identifiers"
    value="true"
/>

This way, we don’t need to manually quote any identifier:

Example 55. JPA quoting
@Entity(name = "Product")
public static class Product {

    @Id
    private Long id;

    private String name;

    private String number;

    //Getters and setters are omitted for brevity

}

When persisting a Product entity, Hibernate is going to quote all identifiers as in the following example:

INSERT INTO "Product" ("name", "number", "id")
VALUES ('Mobile phone', '123-456-7890', 1)

As you can see, both the table name and all the column have been quoted.

For more about quoting-related configuration properties, check out the Mapping configurations section as well.

2.3.18. Generated properties

Generated properties are properties that have their values generated by the database. Typically, Hibernate applications needed to refresh objects that contain any properties for which the database was generating values. Marking properties as generated, however, lets the application delegate this responsibility to Hibernate. When Hibernate issues an SQL INSERT or UPDATE for an entity that has defined generated properties, it immediately issues a select to retrieve the generated values.

Properties marked as generated must additionally be non-insertable and non-updateable. Only @Version and @Basic types can be marked as generated.

NEVER (the default)

the given property value is not generated within the database.

INSERT

the given property value is generated on insert, but is not regenerated on subsequent updates. Properties like creationTimestamp fall into this category.

ALWAYS

the property value is generated both on insert and on update.

To mark a property as generated, use The Hibernate specific @Generated annotation.

@Generated annotation

The @Generated annotation is used so that Hibernate can fetch the currently annotated property after the entity has been persisted or updated. For this reason, the @Generated annotation accepts a GenerationTime enum value.

Considering the following entity:

Example 56. @Generated mapping example
@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;

    private String firstName;

    private String lastName;

    private String middleName1;

    private String middleName2;

    private String middleName3;

    private String middleName4;

    private String middleName5;

    @Generated( value = GenerationTime.ALWAYS )
    @Column(columnDefinition =
        "AS CONCAT(" +
        "    COALESCE(firstName, ''), " +
        "    COALESCE(' ' + middleName1, ''), " +
        "    COALESCE(' ' + middleName2, ''), " +
        "    COALESCE(' ' + middleName3, ''), " +
        "    COALESCE(' ' + middleName4, ''), " +
        "    COALESCE(' ' + middleName5, ''), " +
        "    COALESCE(' ' + lastName, '') " +
        ")")
    private String fullName;

}

When the Person entity is persisted, Hibernate is going to fetch the calculated fullName column from the database, which concatenates the first, middle, and last name.

Example 57. @Generated persist example
Person person = new Person();
person.setId( 1L );
person.setFirstName( "John" );
person.setMiddleName1( "Flávio" );
person.setMiddleName2( "André" );
person.setMiddleName3( "Frederico" );
person.setMiddleName4( "Rúben" );
person.setMiddleName5( "Artur" );
person.setLastName( "Doe" );

entityManager.persist( person );
entityManager.flush();

assertEquals("John Flávio André Frederico Rúben Artur Doe", person.getFullName());
INSERT INTO Person
(
    firstName,
    lastName,
    middleName1,
    middleName2,
    middleName3,
    middleName4,
    middleName5,
    id
)
values
(?, ?, ?, ?, ?, ?, ?, ?)

-- binding parameter [1] as [VARCHAR] - [John]
-- binding parameter [2] as [VARCHAR] - [Doe]
-- binding parameter [3] as [VARCHAR] - [Flávio]
-- binding parameter [4] as [VARCHAR] - [André]
-- binding parameter [5] as [VARCHAR] - [Frederico]
-- binding parameter [6] as [VARCHAR] - [Rúben]
-- binding parameter [7] as [VARCHAR] - [Artur]
-- binding parameter [8] as [BIGINT]  - [1]

SELECT
    p.fullName as fullName3_0_
FROM
    Person p
WHERE
    p.id=?

-- binding parameter [1] as [BIGINT] - [1]
-- extracted value ([fullName3_0_] : [VARCHAR]) - [John Flávio André Frederico Rúben Artur Doe]

The same goes when the Person entity is updated. Hibernate is going to fetch the calculated fullName column from the database after the entity is modified.

Example 58. @Generated update example
Person person = entityManager.find( Person.class, 1L );
person.setLastName( "Doe Jr" );

entityManager.flush();
assertEquals("John Flávio André Frederico Rúben Artur Doe Jr", person.getFullName());
UPDATE
    Person
SET
    firstName=?,
    lastName=?,
    middleName1=?,
    middleName2=?,
    middleName3=?,
    middleName4=?,
    middleName5=?
WHERE
    id=?

-- binding parameter [1] as [VARCHAR] - [John]
-- binding parameter [2] as [VARCHAR] - [Doe Jr]
-- binding parameter [3] as [VARCHAR] - [Flávio]
-- binding parameter [4] as [VARCHAR] - [André]
-- binding parameter [5] as [VARCHAR] - [Frederico]
-- binding parameter [6] as [VARCHAR] - [Rúben]
-- binding parameter [7] as [VARCHAR] - [Artur]
-- binding parameter [8] as [BIGINT]  - [1]

SELECT
    p.fullName as fullName3_0_
FROM
    Person p
WHERE
    p.id=?

-- binding parameter [1] as [BIGINT] - [1]
-- extracted value ([fullName3_0_] : [VARCHAR]) - [John Flávio André Frederico Rúben Artur Doe Jr]
@GeneratorType annotation

The @GeneratorType annotation is used so that you can provide a custom generator to set the value of the currently annotated property.

For this reason, the @GeneratorType annotation accepts a GenerationTime enum value and a custom ValueGenerator class type.

Considering the following entity:

Example 59. @GeneratorType mapping example
public static class CurrentUser {

    public static final CurrentUser INSTANCE = new CurrentUser();

    private static final ThreadLocal<String> storage = new ThreadLocal<>();

    public void logIn(String user) {
        storage.set( user );
    }

    public void logOut() {
        storage.remove();
    }

    public String get() {
        return storage.get();
    }
}

public static class LoggedUserGenerator implements ValueGenerator<String> {

    @Override
    public String generateValue(
            Session session, Object owner) {
        return CurrentUser.INSTANCE.get();
    }
}

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

    @Id
    private Long id;

    private String firstName;

    private String lastName;

    @GeneratorType( type = LoggedUserGenerator.class, when = GenerationTime.INSERT)
    private String createdBy;

    @GeneratorType( type = LoggedUserGenerator.class, when = GenerationTime.ALWAYS)
    private String updatedBy;

}

When the Person entity is persisted, Hibernate is going to populate the createdBy column with the currently logged user.

Example 60. @Generated persist example
CurrentUser.INSTANCE.logIn( "Alice" );

doInJPA( this::entityManagerFactory, entityManager -> {

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

    entityManager.persist( person );
} );

CurrentUser.INSTANCE.logOut();
INSERT INTO Person
(
    createdBy,
    firstName,
    lastName,
    updatedBy,
    id
)
VALUES
(?, ?, ?, ?, ?)

-- binding parameter [1] as [VARCHAR] - [Alice]
-- binding parameter [2] as [VARCHAR] - [John]
-- binding parameter [3] as [VARCHAR] - [Doe]
-- binding parameter [4] as [VARCHAR] - [Alice]
-- binding parameter [5] as [BIGINT]  - [1]

The same goes when the Person entity is updated. Hibernate is going to populate the updatedBy column with the currently logged user.

Example 61. @Generated update example
CurrentUser.INSTANCE.logIn( "Bob" );

doInJPA( this::entityManagerFactory, entityManager -> {
    Person person = entityManager.find( Person.class, 1L );
    person.setFirstName( "Mr. John" );
} );

CurrentUser.INSTANCE.logOut();
UPDATE Person
SET
    createdBy = ?,
    firstName = ?,
    lastName = ?,
    updatedBy = ?
WHERE
    id = ?

-- binding parameter [1] as [VARCHAR] - [Alice]
-- binding parameter [2] as [VARCHAR] - [Mr. John]
-- binding parameter [3] as [VARCHAR] - [Doe]
-- binding parameter [4] as [VARCHAR] - [Bob]
-- binding parameter [5] as [BIGINT]  - [1]
@CreationTimestamp annotation

The @CreationTimestamp annotation instructs Hibernate to set the annotated entity attribute with the current timestamp value of the JVM when the entity is being persisted.

The supported property types are:

  • java.util.Date

  • java.util.Calendar

  • java.sql.Date

  • java.sql.Time

  • java.sql.Timestamp

Example 62. @CreationTimestamp mapping example
@Entity(name = "Event")
public static class Event {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "`timestamp`")
    @CreationTimestamp
    private Date timestamp;

    public Event() {}

    public Long getId() {
        return id;
    }

    public Date getTimestamp() {
        return timestamp;
    }
}

When the Event entity is persisted, Hibernate is going to populate the underlying timestamp column with the current JVM timestamp value:

Example 63. @CreationTimestamp persist example
Event dateEvent = new Event( );
entityManager.persist( dateEvent );
INSERT INTO Event ("timestamp", id)
VALUES (?, ?)

-- binding parameter [1] as [TIMESTAMP] - [Tue Nov 15 16:24:20 EET 2016]
-- binding parameter [2] as [BIGINT]    - [1]
@UpdateTimestamp annotation

The @UpdateTimestamp annotation instructs Hibernate to set the annotated entity attribute with the current timestamp value of the JVM when the entity is being persisted.

The supported property types are:

  • java.util.Date

  • java.util.Calendar

  • java.sql.Date

  • java.sql.Time

  • java.sql.Timestamp

Example 64. @UpdateTimestamp mapping example
@Entity(name = "Bid")
public static class Bid {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "updated_on")
    @UpdateTimestamp
    private Date updatedOn;

    @Column(name = "updated_by")
    private String updatedBy;

    private Long cents;

    //Getters and setters are omitted for brevity

}

When the Bid entity is persisted, Hibernate is going to populate the underlying updated_on column with the current JVM timestamp value:

Example 65. @UpdateTimestamp persist example
Bid bid = new Bid();
bid.setUpdatedBy( "John Doe" );
bid.setCents( 150 * 100L );
entityManager.persist( bid );
INSERT INTO Bid (cents, updated_by, updated_on, id)
VALUES (?, ?, ?, ?)

-- binding parameter [1] as [BIGINT]    - [15000]
-- binding parameter [2] as [VARCHAR]   - [John Doe]
-- binding parameter [3] as [TIMESTAMP] - [Tue Apr 18 17:21:46 EEST 2017]
-- binding parameter [4] as [BIGINT]    - [1]

When updating the Bid entity, Hibernate is going to modify the updated_on column with the current JVM timestamp value:

Example 66. @UpdateTimestamp update example
Bid bid = entityManager.find( Bid.class, 1L );

bid.setUpdatedBy( "John Doe Jr." );
bid.setCents( 160 * 100L );
entityManager.persist( bid );
UPDATE Bid SET
    cents = ?,
    updated_by = ?,
    updated_on = ?
where
    id = ?

-- binding parameter [1] as [BIGINT]    - [16000]
-- binding parameter [2] as [VARCHAR]   - [John Doe Jr.]
-- binding parameter [3] as [TIMESTAMP] - [Tue Apr 18 17:49:24 EEST 2017]
-- binding parameter [4] as [BIGINT]    - [1]
@ValueGenerationType meta-annotation

Hibernate 4.3 introduced the @ValueGenerationType meta-annotation, which is a new approach to declaring generated attributes or customizing generators.

@Generated has been retrofitted to use the @ValueGenerationType meta-annotation. But @ValueGenerationType exposes more features than what @Generated currently supports, and, to leverage some of those features, you’d simply wire up a new generator annotation.

As you’ll see in the following examples, the @ValueGenerationType meta-annotation is used when declaring the custom annotation used to mark the entity properties that need a specific generation strategy. The actual generation logic must be implemented in class that implements the AnnotationValueGeneration interface.

Database-generated values

For example, let’s say we want the timestamps to be generated by calls to the standard ANSI SQL function current_timestamp (rather than triggers or DEFAULT values):

Example 67. A ValueGenerationType mapping for database generation
@Entity(name = "Event")
public static class Event {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "`timestamp`")
    @FunctionCreationTimestamp
    private Date timestamp;

    public Event() {}

    public Long getId() {
        return id;
    }

    public Date getTimestamp() {
        return timestamp;
    }
}

@ValueGenerationType(generatedBy = FunctionCreationValueGeneration.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface FunctionCreationTimestamp {}

public static class FunctionCreationValueGeneration
        implements AnnotationValueGeneration<FunctionCreationTimestamp> {

    @Override
    public void initialize(FunctionCreationTimestamp annotation, Class<?> propertyType) {
    }

    /**
     * Generate value on INSERT
     * @return when to generate the value
     */
    public GenerationTiming getGenerationTiming() {
        return GenerationTiming.INSERT;
    }

    /**
     * Returns null because the value is generated by the database.
     * @return null
     */
    public ValueGenerator<?> getValueGenerator() {
        return null;
    }

    /**
     * Returns true because the value is generated by the database.
     * @return true
     */
    public boolean referenceColumnInSql() {
        return true;
    }

    /**
     * Returns the database-generated value
     * @return database-generated value
     */
    public String getDatabaseGeneratedReferencedColumnValue() {
        return "current_timestamp";
    }
}

When persisting an Event entity, Hibernate generates the following SQL statement:

INSERT INTO Event ("timestamp", id)
VALUES (current_timestamp, 1)

As you can see, the current_timestamp value was used for assigning the timestamp column value.

In-memory-generated values

If the timestamp value needs to be generated in-memory, the following mapping must be used instead:

Example 68. A ValueGenerationType mapping for in-memory value generation
@Entity(name = "Event")
public static class Event {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "`timestamp`")
    @FunctionCreationTimestamp
    private Date timestamp;

    public Event() {}

    public Long getId() {
        return id;
    }

    public Date getTimestamp() {
        return timestamp;
    }
}

@ValueGenerationType(generatedBy = FunctionCreationValueGeneration.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface FunctionCreationTimestamp {}

public static class FunctionCreationValueGeneration
        implements AnnotationValueGeneration<FunctionCreationTimestamp> {

    @Override
    public void initialize(FunctionCreationTimestamp annotation, Class<?> propertyType) {
    }

    /**
     * Generate value on INSERT
     * @return when to generate the value
     */
    public GenerationTiming getGenerationTiming() {
        return GenerationTiming.INSERT;
    }

    /**
     * Returns the in-memory generated value
     * @return {@code true}
     */
    public ValueGenerator<?> getValueGenerator() {
        return (session, owner) -> new Date( );
    }

    /**
     * Returns false because the value is generated by the database.
     * @return false
     */
    public boolean referenceColumnInSql() {
        return false;
    }

    /**
     * Returns null because the value is generated in-memory.
     * @return null
     */
    public String getDatabaseGeneratedReferencedColumnValue() {
        return null;
    }
}

When persisting an Event entity, Hibernate generates the following SQL statement:

INSERT INTO Event ("timestamp", id)
VALUES ('Tue Mar 01 10:58:18 EET 2016', 1)

As you can see, the new Date() object value was used for assigning the timestamp column value.

2.3.19. Column transformers: read and write expressions

Hibernate allows you to customize the SQL it uses to read and write the values of columns mapped to @Basic types. For example, if your database provides a set of data encryption functions, you can invoke them for individual columns like in the following example.

Example 69. @ColumnTransformer example
@Entity(name = "Employee")
public static class Employee {

    @Id
    private Long id;

    @NaturalId
    private String username;

    @Column(name = "pswd")
    @ColumnTransformer(
        read = "decrypt( 'AES', '00', pswd  )",
        write = "encrypt('AES', '00', ?)"
    )
    private String password;

    private int accessLevel;

    @ManyToOne(fetch = FetchType.LAZY)
    private Department department;

    @ManyToMany(mappedBy = "employees")
    private List<Project> projects = new ArrayList<>();

    //Getters and setters omitted for brevity
}

You can use the plural form @ColumnTransformers if more than one columns need to define either of these rules.

If a property uses more than one column, you must use the forColumn attribute to specify which column, the expressions are targeting.

Example 70. @ColumnTransformer forColumn attribute usage
@Entity(name = "Savings")
public static class Savings {

    @Id
    private Long id;

    @Type(type = "org.hibernate.userguide.mapping.basic.MonetaryAmountUserType")
    @Columns(columns = {
        @Column(name = "money"),
        @Column(name = "currency")
    })
    @ColumnTransformer(
        forColumn = "money",
        read = "money / 100",
        write = "? * 100"
    )
    private MonetaryAmount wallet;

    //Getters and setters omitted for brevity

}

Hibernate applies the custom expressions automatically whenever the property is referenced in a query. This functionality is similar to a derived-property @Formula with two differences:

  • The property is backed by one or more columns that are exported as part of automatic schema generation.

  • The property is read-write, not read-only.

The write expression, if specified, must contain exactly one '?' placeholder for the value.

Example 71. Persisting an entity with a @ColumnTransformer and a composite type
doInJPA( this::entityManagerFactory, entityManager -> {
    Savings savings = new Savings( );
    savings.setId( 1L );
    savings.setWallet( new MonetaryAmount( BigDecimal.TEN, Currency.getInstance( Locale.US ) ) );
    entityManager.persist( savings );
} );

doInJPA( this::entityManagerFactory, entityManager -> {
    Savings savings = entityManager.find( Savings.class, 1L );
    assertEquals( 10, savings.getWallet().getAmount().intValue());
} );
INSERT INTO Savings (money, currency, id)
VALUES (10 * 100, 'USD', 1)

SELECT
    s.id as id1_0_0_,
    s.money / 100 as money2_0_0_,
    s.currency as currency3_0_0_
FROM
    Savings s
WHERE
    s.id = 1

2.3.20. @Formula

Sometimes, you want the Database to do some computation for you rather than in the JVM, you might also create some kind of virtual column. You can use a SQL fragment (aka formula) instead of mapping a property into a column. This kind of property is read only (its value is calculated by your formula fragment)

You should be aware that the @Formula annotation takes a native SQL clause which can affect database portability.

Example 72. @Formula mapping usage
@Entity(name = "Account")
public static class Account {

    @Id
    private Long id;

    private Double credit;

    private Double rate;

    @Formula(value = "credit * rate")
    private Double interest;

    //Getters and setters omitted for brevity

}

When loading the Account entity, Hibernate is going to calculate the interest property using the configured @Formula:

Example 73. Persisting an entity with a @Formula mapping
doInJPA( this::entityManagerFactory, entityManager -> {
    Account account = new Account( );
    account.setId( 1L );
    account.setCredit( 5000d );
    account.setRate( 1.25 / 100 );
    entityManager.persist( account );
} );

doInJPA( this::entityManagerFactory, entityManager -> {
    Account account = entityManager.find( Account.class, 1L );
    assertEquals( Double.valueOf( 62.5d ), account.getInterest());
} );
INSERT INTO Account (credit, rate, id)
VALUES (5000.0, 0.0125, 1)

SELECT
    a.id as id1_0_0_,
    a.credit as credit2_0_0_,
    a.rate as rate3_0_0_,
    a.credit * a.rate as formula0_0_
FROM
    Account a
WHERE
    a.id = 1

The SQL fragment can be as complex as you want and even include subselects.

2.3.21. @Where

Sometimes, you want to filter out entities or collections using a custom SQL criteria. This can be achieved using the @Where annotation, which can be applied to entities and collections.

Example 74. @Where mapping usage
public enum AccountType {
    DEBIT,
    CREDIT
}

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

    @Id
    private Long id;

    private String name;

    @Where( clause = "account_type = 'DEBIT'")
    @OneToMany(mappedBy = "client")
    private List<Account> debitAccounts = new ArrayList<>( );

    @Where( clause = "account_type = 'CREDIT'")
    @OneToMany(mappedBy = "client")
    private List<Account> creditAccounts = new ArrayList<>( );

    //Getters and setters omitted for brevity

}

@Entity(name = "Account")
@Where( clause = "active = true" )
public static class Account {

    @Id
    private Long id;

    @ManyToOne
    private Client client;

    @Column(name = "account_type")
    @Enumerated(EnumType.STRING)
    private AccountType type;

    private Double amount;

    private Double rate;

    private boolean active;

    //Getters and setters omitted for brevity

}

If the database contains the following entities:

Example 75. Persisting an fetching entities with a @Where mapping
doInJPA( this::entityManagerFactory, entityManager -> {

    Client client = new Client();
    client.setId( 1L );
    client.setName( "John Doe" );
    entityManager.persist( client );

    Account account1 = new Account( );
    account1.setId( 1L );
    account1.setType( AccountType.CREDIT );
    account1.setAmount( 5000d );
    account1.setRate( 1.25 / 100 );
    account1.setActive( true );
    account1.setClient( client );
    client.getCreditAccounts().add( account1 );
    entityManager.persist( account1 );

    Account account2 = new Account( );
    account2.setId( 2L );
    account2.setType( AccountType.DEBIT );
    account2.setAmount( 0d );
    account2.setRate( 1.05 / 100 );
    account2.setActive( false );
    account2.setClient( client );
    client.getDebitAccounts().add( account2 );
    entityManager.persist( account2 );

    Account account3 = new Account( );
    account3.setType( AccountType.DEBIT );
    account3.setId( 3L );
    account3.setAmount( 250d );
    account3.setRate( 1.05 / 100 );
    account3.setActive( true );
    account3.setClient( client );
    client.getDebitAccounts().add( account3 );
    entityManager.persist( account3 );
} );
INSERT INTO Client (name, id)
VALUES ('John Doe', 1)

INSERT INTO Account (active, amount, client_id, rate, account_type, id)
VALUES (true, 5000.0, 1, 0.0125, 'CREDIT', 1)

INSERT INTO Account (active, amount, client_id, rate, account_type, id)
VALUES (false, 0.0, 1, 0.0105, 'DEBIT', 2)

INSERT INTO Account (active, amount, client_id, rate, account_type, id)
VALUES (true, 250.0, 1, 0.0105, 'DEBIT', 3)

When executing an Account entity query, Hibernate is going to filter out all records that are not active.

Example 76. Query entities mapped with @Where
doInJPA( this::entityManagerFactory, entityManager -> {
    List<Account> accounts = entityManager.createQuery(
        "select a from Account a", Account.class)
    .getResultList();
    assertEquals( 2, accounts.size());
} );
SELECT
    a.id as id1_0_,
    a.active as active2_0_,
    a.amount as amount3_0_,
    a.client_id as client_i6_0_,
    a.rate as rate4_0_,
    a.account_type as account_5_0_
FROM
    Account a
WHERE ( a.active = true )

When fetching the debitAccounts or the creditAccounts collections, Hibernate is going to apply the @Where clause filtering criteria to the associated child entities.

Example 77. Traversing collections mapped with @Where
doInJPA( this::entityManagerFactory, entityManager -> {
    Client client = entityManager.find( Client.class, 1L );
    assertEquals( 1, client.getCreditAccounts().size() );
    assertEquals( 1, client.getDebitAccounts().size() );
} );
SELECT
    c.client_id as client_i6_0_0_,
    c.id as id1_0_0_,
    c.id as id1_0_1_,
    c.active as active2_0_1_,
    c.amount as amount3_0_1_,
    c.client_id as client_i6_0_1_,
    c.rate as rate4_0_1_,
    c.account_type as account_5_0_1_
FROM
    Account c
WHERE ( c.active = true and c.account_type = 'CREDIT' ) AND c.client_id = 1

SELECT
    d.client_id as client_i6_0_0_,
    d.id as id1_0_0_,
    d.id as id1_0_1_,
    d.active as active2_0_1_,
    d.amount as amount3_0_1_,
    d.client_id as client_i6_0_1_,
    d.rate as rate4_0_1_,
    d.account_type as account_5_0_1_
FROM
    Account d
WHERE ( d.active = true and d.account_type = 'DEBIT' ) AND d.client_id = 1

2.3.22. @WhereJoinTable

Just like @Where annotation, @WhereJoinTable is used to filter out collections using a joined table (e.g. @ManyToMany association).

Example 78. @WhereJoinTable mapping example
@Entity(name = "Book")
public static class Book {

    @Id
    private Long id;

    private String title;

    private String author;

    @ManyToMany
    @JoinTable(
        name = "Book_Reader",
        joinColumns = @JoinColumn(name = "book_id"),
        inverseJoinColumns = @JoinColumn(name = "reader_id")
    )
    @WhereJoinTable( clause = "created_on > DATEADD( 'DAY', -7, CURRENT_TIMESTAMP() )")
    private List<Reader> currentWeekReaders = new ArrayList<>( );

    //Getters and setters omitted for brevity

}

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

    @Id
    private Long id;

    private String name;

    //Getters and setters omitted for brevity

}
create table Book (
    id bigint not null,
    author varchar(255),
    title varchar(255),
    primary key (id)
)

create table Book_Reader (
    book_id bigint not null,
    reader_id bigint not null
)

create table Reader (
    id bigint not null,
    name varchar(255),
    primary key (id)
)

alter table Book_Reader
    add constraint FKsscixgaa5f8lphs9bjdtpf9g
    foreign key (reader_id)
    references Reader

alter table Book_Reader
    add constraint FKoyrwu9tnwlukd1616qhck21ra
    foreign key (book_id)
    references Book

alter table Book_Reader
    add created_on timestamp
    default current_timestamp

In the example above, the current week Reader entities are included in the currentWeekReaders collection which uses the @WhereJoinTable annotation to filter the joined table rows according to the provided SQL clause.

Considering that the following two Book_Reader entries are added into our system:

Example 79. @WhereJoinTable test data
Book book = new Book();
book.setId( 1L );
book.setTitle( "High-Performance Java Persistence" );
book.setAuthor( "Vad Mihalcea" );
entityManager.persist( book );

Reader reader1 = new Reader();
reader1.setId( 1L );
reader1.setName( "John Doe" );
entityManager.persist( reader1 );

Reader reader2 = new Reader();
reader2.setId( 2L );
reader2.setName( "John Doe Jr." );
entityManager.persist( reader2 );

statement.executeUpdate(
    "INSERT INTO Book_Reader " +
    "    (book_id, reader_id) " +
    "VALUES " +
    "    (1, 1) "
);
statement.executeUpdate(
    "INSERT INTO Book_Reader " +
    "    (book_id, reader_id, created_on) " +
    "VALUES " +
    "    (1, 2, DATEADD( 'DAY', -10, CURRENT_TIMESTAMP() )) "
);

When fetching the currentWeekReaders collection, Hibernate is going to find one one entry:

Example 80. @WhereJoinTable fetch example
Book book = entityManager.find( Book.class, 1L );
assertEquals( 1, book.getCurrentWeekReaders().size() );

2.3.23. @Filter

The @Filter annotation is another way to filter out entities or collections using a custom SQL criteria, for both entities and collections. Unlike the @Where annotation, @Filter allows you to parameterize the filter clause at runtime.

Example 81. @Filter mapping usage
public enum AccountType {
    DEBIT,
    CREDIT
}

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

    @Id
    private Long id;

    private String name;

    @OneToMany(mappedBy = "client")
    @Filter(name="activeAccount", condition="active = :active")
    private List<Account> accounts = new ArrayList<>( );

    //Getters and setters omitted for brevity

}

@Entity(name = "Account")
@FilterDef(name="activeAccount", parameters=@ParamDef( name="active", type="boolean" ) )
@Filter(name="activeAccount", condition="active = :active")
public static class Account {

    @Id
    private Long id;

    @ManyToOne
    private Client client;

    @Column(name = "account_type")
    @Enumerated(EnumType.STRING)
    private AccountType type;

    private Double amount;

    private Double rate;

    private boolean active;

    //Getters and setters omitted for brevity

}

If the database contains the following entities:

Example 82. Persisting an fetching entities with a @Filter mapping
doInJPA( this::entityManagerFactory, entityManager -> {

    Client client = new Client();
    client.setId( 1L );
    client.setName( "John Doe" );
    entityManager.persist( client );

    Account account1 = new Account( );
    account1.setId( 1L );
    account1.setType( AccountType.CREDIT );
    account1.setAmount( 5000d );
    account1.setRate( 1.25 / 100 );
    account1.setActive( true );
    account1.setClient( client );
    client.getAccounts().add( account1 );
    entityManager.persist( account1 );

    Account account2 = new Account( );
    account2.setId( 2L );
    account2.setType( AccountType.DEBIT );
    account2.setAmount( 0d );
    account2.setRate( 1.05 / 100 );
    account2.setActive( false );
    account2.setClient( client );
    client.getAccounts().add( account2 );
    entityManager.persist( account2 );

    Account account3 = new Account( );
    account3.setType( AccountType.DEBIT );
    account3.setId( 3L );
    account3.setAmount( 250d );
    account3.setRate( 1.05 / 100 );
    account3.setActive( true );
    account3.setClient( client );
    client.getAccounts().add( account3 );
    entityManager.persist( account3 );
} );
INSERT INTO Client (name, id)
VALUES ('John Doe', 1)

INSERT INTO Account (active, amount, client_id, rate, account_type, id)
VALUES (true, 5000.0, 1, 0.0125, 'CREDIT', 1)

INSERT INTO Account (active, amount, client_id, rate, account_type, id)
VALUES (false, 0.0, 1, 0.0105, 'DEBIT', 2)

INSERT INTO Account (active, amount, client_id, rate, account_type, id)
VALUES (true, 250.0, 1, 0.0105, 'DEBIT', 3)

By default, without explicitly enabling the filter, Hibernate is going to fetch all Account entities. If the filter is enabled and the filter parameter value is provided, then Hibernate is going to apply the filtering criteria to the associated Account entities.

Example 83. Query entities mapped with @Filter
doInJPA( this::entityManagerFactory, entityManager -> {
    List<Account> accounts = entityManager.createQuery(
        "select a from Account a", Account.class)
    .getResultList();
    assertEquals( 3, accounts.size());
} );

doInJPA( this::entityManagerFactory, entityManager -> {
    log.infof( "Activate filter [%s]", "activeAccount");

    entityManager
        .unwrap( Session.class )
        .enableFilter( "activeAccount" )
        .setParameter( "active", true);

    List<Account> accounts = entityManager.createQuery(
        "select a from Account a", Account.class)
    .getResultList();
    assertEquals( 2, accounts.size());
} );
SELECT
    a.id as id1_0_,
    a.active as active2_0_,
    a.amount as amount3_0_,
    a.client_id as client_i6_0_,
    a.rate as rate4_0_,
    a.account_type as account_5_0_
FROM
    Account a

-- Activate filter [activeAccount]

SELECT
    a.id as id1_0_,
    a.active as active2_0_,
    a.amount as amount3_0_,
    a.client_id as client_i6_0_,
    a.rate as rate4_0_,
    a.account_type as account_5_0_
FROM
    Account a
WHERE
    a.active = true

Filters apply to entity queries, but not to direct fetching. Therefore, in the following example, the filter is not taken into consideration when fetching an entity from the Persistence Context.

Fetching entities mapped with @Filter
doInJPA( this::entityManagerFactory, entityManager -> {
    log.infof( "Activate filter [%s]", "activeAccount");

    entityManager
        .unwrap( Session.class )
        .enableFilter( "activeAccount" )
        .setParameter( "active", true);

    Account account = entityManager.find( Account.class, 2L );
    assertFalse( account.isActive() );
} );
SELECT
    a.id as id1_0_0_,
    a.active as active2_0_0_,
    a.amount as amount3_0_0_,
    a.client_id as client_i6_0_0_,
    a.rate as rate4_0_0_,
    a.account_type as account_5_0_0_,
    c.id as id1_1_1_,
    c.name as name2_1_1_
FROM
    Account a
LEFT OUTER JOIN
    Client c
        ON a.client_id=c.id
WHERE
    a.id = 2

As you can see from the example above, contrary to an entity query, the filter does not prevent the entity from being loaded.

Just like with entity queries, collections can be filtered as well, but only if the filter is explicitly enabled on the currently running Hibernate Session. This way, when fetching the accounts collections, Hibernate is going to apply the @Filter clause filtering criteria to the associated collection entries.

Example 84. Traversing collections mapped with @Filter
doInJPA( this::entityManagerFactory, entityManager -> {
    Client client = entityManager.find( Client.class, 1L );
    assertEquals( 3, client.getAccounts().size() );
} );

doInJPA( this::entityManagerFactory, entityManager -> {
    log.infof( "Activate filter [%s]", "activeAccount");

    entityManager
        .unwrap( Session.class )
        .enableFilter( "activeAccount" )
        .setParameter( "active", true);

    Client client = entityManager.find( Client.class, 1L );
    assertEquals( 2, client.getAccounts().size() );
} );
SELECT
    c.id as id1_1_0_,
    c.name as name2_1_0_
FROM
    Client c
WHERE
    c.id = 1

SELECT
    a.id as id1_0_,
    a.active as active2_0_,
    a.amount as amount3_0_,
    a.client_id as client_i6_0_,
    a.rate as rate4_0_,
    a.account_type as account_5_0_
FROM
    Account a
WHERE
    a.client_id = 1

-- Activate filter [activeAccount]

SELECT
    c.id as id1_1_0_,
    c.name as name2_1_0_
FROM
    Client c
WHERE
    c.id = 1

SELECT
    a.id as id1_0_,
    a.active as active2_0_,
    a.amount as amount3_0_,
    a.client_id as client_i6_0_,
    a.rate as rate4_0_,
    a.account_type as account_5_0_
FROM
    Account a
WHERE
    accounts0_.active = true
    and a.client_id = 1

The main advantage of @Filter over the @Where clause is that the filtering criteria can be customized at runtime.

It’s not possible to combine the @Filter and @Cache collection annotations. This limitation is due to ensuring consistency and because the filtering information is not stored in the second-level cache.

If caching was allowed for a currently filtered collection, then the second-level cache would store only a subset of the whole collection. Afterward, every other Session will get the filtered collection from the cache, even if the Session-level filters have not been explicitly activated.

For this reason, the second-level collection cache is limited to storing whole collections, and not subsets.

2.3.24. @FilterJoinTable

When using the @Filter annotation with collections, the filtering is done against the child entries (entities or embeddables). However, if you have a link table between the parent entity and the child table, then you need to use the @FilterJoinTable to filter child entries according to some column contained in the join table.

The @FilterJoinTable annotation can be, therefore, applied to a unidirectional @OneToMany collection as illustrate din the following mapping:

Example 85. @FilterJoinTable mapping usage
public enum AccountType {
    DEBIT,
    CREDIT
}

@Entity(name = "Client")
@FilterDef(name="firstAccounts", parameters=@ParamDef( name="maxOrderId", type="int" ) )
@Filter(name="firstAccounts", condition="order_id <= :maxOrderId")
public static class Client {

    @Id
    private Long id;

    private String name;

    @OneToMany
    @OrderColumn(name = "order_id")
    @FilterJoinTable(name="firstAccounts", condition="order_id <= :maxOrderId")
    private List<Account> accounts = new ArrayList<>( );

    //Getters and setters omitted for brevity

}

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

    @Id
    private Long id;

    @Column(name = "account_type")
    @Enumerated(EnumType.STRING)
    private AccountType type;

    private Double amount;

    private Double rate;

    private boolean active;

    //Getters and setters omitted for brevity

}

If the database contains the following entities:

Example 86. Persisting an fetching entities with a @FilterJoinTable mapping
doInJPA( this::entityManagerFactory, entityManager -> {

    Client client = new Client();
    client.setId( 1L );
    client.setName( "John Doe" );
    entityManager.persist( client );

    Account account1 = new Account( );
    account1.setId( 1L );
    account1.setType( AccountType.CREDIT );
    account1.setAmount( 5000d );
    account1.setRate( 1.25 / 100 );
    account1.setActive( true );
    client.getAccounts().add( account1 );
    entityManager.persist( account1 );

    Account account2 = new Account( );
    account2.setId( 2L );
    account2.setType( AccountType.DEBIT );
    account2.setAmount( 0d );
    account2.setRate( 1.05 / 100 );
    account2.setActive( false );
    client.getAccounts().add( account2 );
    entityManager.persist( account2 );

    Account account3 = new Account( );
    account3.setType( AccountType.DEBIT );
    account3.setId( 3L );
    account3.setAmount( 250d );
    account3.setRate( 1.05 / 100 );
    account3.setActive( true );
    client.getAccounts().add( account3 );
    entityManager.persist( account3 );
} );
INSERT INTO Client (name, id)
VALUES ('John Doe', 1)

INSERT INTO Account (active, amount, client_id, rate, account_type, id)
VALUES (true, 5000.0, 1, 0.0125, 'CREDIT', 1)

INSERT INTO Account (active, amount, client_id, rate, account_type, id)
VALUES (false, 0.0, 1, 0.0105, 'DEBIT', 2)

INSERT INTO Account (active, amount, client_id, rate, account_type, id)
VALUES (true, 250.0, 1, 0.0105, 'DEBIT', 3)

INSERT INTO Client_Account (Client_id, order_id, accounts_id)
VALUES (1, 0, 1)

INSERT INTO Client_Account (Client_id, order_id, accounts_id)
VALUES (1, 0, 1)

INSERT INTO Client_Account (Client_id, order_id, accounts_id)
VALUES (1, 1, 2)

INSERT INTO Client_Account (Client_id, order_id, accounts_id)
VALUES (1, 2, 3)

The collections can be filtered if the associated filter is enabled on the currently running Hibernate Session. This way, when fetching the accounts collections, Hibernate is going to apply the @FilterJoinTable clause filtering criteria to the associated collection entries.

Example 87. Traversing collections mapped with @FilterJoinTable
doInJPA( this::entityManagerFactory, entityManager -> {
    Client client = entityManager.find( Client.class, 1L );
    assertEquals( 3, client.getAccounts().size());
} );

doInJPA( this::entityManagerFactory, entityManager -> {
    log.infof( "Activate filter [%s]", "firstAccounts");

    Client client = entityManager.find( Client.class, 1L );

    entityManager
        .unwrap( Session.class )
        .enableFilter( "firstAccounts" )
        .setParameter( "maxOrderId", 1);

    assertEquals( 2, client.getAccounts().size());
} );
SELECT
    ca.Client_id as Client_i1_2_0_,
    ca.accounts_id as accounts2_2_0_,
    ca.order_id as order_id3_0_,
    a.id as id1_0_1_,
    a.active as active2_0_1_,
    a.amount as amount3_0_1_,
    a.rate as rate4_0_1_,
    a.account_type as account_5_0_1_
FROM
    Client_Account ca
INNER JOIN
    Account a
ON  ca.accounts_id=a.id
WHERE
    ca.Client_id = ?

-- binding parameter [1] as [BIGINT] - [1]

-- Activate filter [firstAccounts]

SELECT
    ca.Client_id as Client_i1_2_0_,
    ca.accounts_id as accounts2_2_0_,
    ca.order_id as order_id3_0_,
    a.id as id1_0_1_,
    a.active as active2_0_1_,
    a.amount as amount3_0_1_,
    a.rate as rate4_0_1_,
    a.account_type as account_5_0_1_
FROM
    Client_Account ca
INNER JOIN
    Account a
ON  ca.accounts_id=a.id
WHERE
    ca.order_id <= ?
    AND ca.Client_id = ?

-- binding parameter [1] as [INTEGER] - [1]
-- binding parameter [2] as [BIGINT] - [1]

2.3.25. @Filter with @SqlFragmentAlias

When using the @Filter annotation and working with entities that are mapped onto multiple database tables, you will need to use the @SqlFragmentAlias annotation if the @Filter defines a condition that uses predicates across multiple tables.

Example 88. @SqlFragmentAlias mapping usage
@Entity(name = "Account")
@Table(name = "account")
@SecondaryTable(
    name = "account_details"
)
@SQLDelete(
    sql = "UPDATE account_details SET deleted = true WHERE id = ? "
)
@FilterDef(
    name="activeAccount",
    parameters = @ParamDef(
        name="active",
        type="boolean"
    )
)
@Filter(
    name="activeAccount",
    condition="{a}.active = :active and {ad}.deleted = false",
    aliases = {
        @SqlFragmentAlias( alias = "a", table= "account"),
        @SqlFragmentAlias( alias = "ad", table= "account_details"),
    }
)
public static class Account {

    @Id
    private Long id;

    private Double amount;

    private Double rate;

    private boolean active;

    @Column(table = "account_details")
    private boolean deleted;

    //Getters and setters omitted for brevity

}

Now, when fetching the Account entities and activating the filter, Hibernate is going to apply the right table aliases to the filter predicates:

Example 89. Fetching a collection filtered with @SqlFragmentAlias
entityManager
    .unwrap( Session.class )
    .enableFilter( "activeAccount" )
    .setParameter( "active", true);

List<Account> accounts = entityManager.createQuery(
    "select a from Account a", Account.class)
.getResultList();
select
    filtersqlf0_.id as id1_0_,
    filtersqlf0_.active as active2_0_,
    filtersqlf0_.amount as amount3_0_,
    filtersqlf0_.rate as rate4_0_,
    filtersqlf0_1_.deleted as deleted1_1_
from
    account filtersqlf0_
left outer join
    account_details filtersqlf0_1_
        on filtersqlf0_.id=filtersqlf0_1_.id
where
    filtersqlf0_.active = ?
    and filtersqlf0_1_.deleted = false

-- binding parameter [1] as [BOOLEAN] - [true]

2.3.26. @Any mapping

There is one more type of property mapping. The @Any mapping defines a polymorphic association to classes from multiple tables. This type of mapping requires more than one column. The first column contains the type of the associated entity. The remaining columns contain the identifier.

It is impossible to specify a foreign key constraint for this kind of association. This is not the usual way of mapping polymorphic associations and you should use this only in special cases (e.g. audit logs, user session data, etc).

The @Any annotation describes the column holding the metadata information. To link the value of the metadata information and an actual entity type, the @AnyDef and @AnyDefs annotations are used. The metaType attribute allows the application to specify a custom type that maps database column values to persistent classes that have identifier properties of the type specified by idType. You must specify the mapping from values of the metaType to class names.

For the next examples, consider the following Property class hierarchy:

Example 90. Property class hierarchy
public interface Property<T> {

    String getName();

    T getValue();
}


@Entity
@Table(name="integer_property")
public class IntegerProperty implements Property<Integer> {

    @Id
    private Long id;

    @Column(name = "`name`")
    private String name;

    @Column(name = "`value`")
    private Integer value;

    public Long getId() {
        return id;
    }

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

    @Override
    public String getName() {
        return name;
    }

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

    public Integer getValue() {
        return value;
    }

    public void setValue(Integer value) {
        this.value = value;
    }
}


@Entity
@Table(name="string_property")
public class StringProperty implements Property<String> {

    @Id
    private Long id;

    @Column(name = "`name`")
    private String name;

    @Column(name = "`value`")
    private String value;

    public Long getId() {
        return id;
    }

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

    @Override
    public String getName() {
        return name;
    }

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

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

A PropertyHolder can reference any such property, and, because each Property belongs to a separate table, the @Any annotation is, therefore, required.

Example 91. @Any mapping usage
@Entity
@Table( name = "property_holder" )
public class PropertyHolder {

    @Id
    private Long id;

    @Any(
        metaDef = "PropertyMetaDef",
        metaColumn = @Column( name = "property_type" )
    )
    @JoinColumn( name = "property_id" )
    private Property property;

	//Getters and setters are omitted for brevity

}
CREATE TABLE property_holder (
    id BIGINT NOT NULL,
    property_type VARCHAR(255),
    property_id BIGINT,
    PRIMARY KEY ( id )
)

As you can see, there are two columns used to reference a Property instance: property_id and property_type. The property_id is used to match the id column of either the string_property or integer_property tables, while the property_type is used to match the string_property or the integer_property table.

The table resolving mapping is defined by the metaDef attribute which references an @AnyMetaDef mapping. Although the @AnyMetaDef mapping could be set right next to the @Any annotation, it’s good practice to reuse it, therefore it makes sense to configure it on a class or package-level basis.

The package-info.java contains the @AnyMetaDef mapping:

Example 92. @Any mapping usage
@AnyMetaDef( name= "PropertyMetaDef", metaType = "string", idType = "long",
    metaValues = {
            @MetaValue(value = "S", targetEntity = StringProperty.class),
            @MetaValue(value = "I", targetEntity = IntegerProperty.class)
        }
    )
package org.hibernate.userguide.mapping.basic.any;

import org.hibernate.annotations.AnyMetaDef;
import org.hibernate.annotations.MetaValue;

It is recommended to place the @AnyMetaDef mapping as a package metadata.

To see how the @Any annotation in action, consider the following example:

Example 93. @Any mapping usage
doInHibernate( this::sessionFactory, session -> {
    IntegerProperty ageProperty = new IntegerProperty();
    ageProperty.setId( 1L );
    ageProperty.setName( "age" );
    ageProperty.setValue( 23 );

    StringProperty nameProperty = new StringProperty();
    nameProperty.setId( 1L );
    nameProperty.setName( "name" );
    nameProperty.setValue( "John Doe" );

    session.persist( ageProperty );
    session.persist( nameProperty );

    PropertyHolder namePropertyHolder = new PropertyHolder();
    namePropertyHolder.setId( 1L );
    namePropertyHolder.setProperty( nameProperty );
    session.persist( namePropertyHolder );
} );

doInHibernate( this::sessionFactory, session -> {
    PropertyHolder propertyHolder = session.get( PropertyHolder.class, 1L );
    assertEquals("name", propertyHolder.getProperty().getName());
    assertEquals("John Doe", propertyHolder.getProperty().getValue());
} );
INSERT INTO integer_property
       ( "name", "value", id )
VALUES ( 'age', 23, 1 )

INSERT INTO string_property
       ( "name", "value", id )
VALUES ( 'name', 'John Doe', 1 )

INSERT INTO property_holder
       ( property_type, property_id, id )
VALUES ( 'S', 1, 1 )


SELECT ph.id AS id1_1_0_,
       ph.property_type AS property2_1_0_,
       ph.property_id AS property3_1_0_
FROM   property_holder ph
WHERE  ph.id = 1


SELECT sp.id AS id1_2_0_,
       sp."name" AS name2_2_0_,
       sp."value" AS value3_2_0_
FROM   string_property sp
WHERE  sp.id = 1
@ManyToAny mapping

The @Any mapping is useful to emulate a @ManyToOne association when there can be multiple target entities. To emulate a @OneToMany association, the @ManyToAny annotation must be used.

In the following example, the PropertyRepository entity has a collection of Property entities. The repository_properties link table holds the associations between PropertyRepository and Property entities.

Example 94. @ManyToAny mapping usage
@Entity
@Table( name = "property_repository" )
public class PropertyRepository {

    @Id
    private Long id;

    @ManyToAny(
        metaDef = "PropertyMetaDef",
        metaColumn = @Column( name = "property_type" )
    )
    @Cascade( { org.hibernate.annotations.CascadeType.ALL })
    @JoinTable(name = "repository_properties",
        joinColumns = @JoinColumn(name = "repository_id"),
        inverseJoinColumns = @JoinColumn(name = "property_id")
    )
    private List<Property<?>> properties = new ArrayList<>(  );

	//Getters and setters are omitted for brevity

}
CREATE TABLE property_repository (
    id BIGINT NOT NULL,
    PRIMARY KEY ( id )
)

CREATE TABLE repository_properties (
    repository_id BIGINT NOT NULL,
    property_type VARCHAR(255),
    property_id BIGINT NOT NULL
)

To see how the @ManyToAny annotation works, consider the following example:

Example 95. @Any mapping usage
doInHibernate( this::sessionFactory, session -> {
    IntegerProperty ageProperty = new IntegerProperty();
    ageProperty.setId( 1L );
    ageProperty.setName( "age" );
    ageProperty.setValue( 23 );

    StringProperty nameProperty = new StringProperty();
    nameProperty.setId( 1L );
    nameProperty.setName( "name" );
    nameProperty.setValue( "John Doe" );

    session.persist( ageProperty );
    session.persist( nameProperty );

    PropertyRepository propertyRepository = new PropertyRepository();
    propertyRepository.setId( 1L );
    propertyRepository.getProperties().add( ageProperty );
    propertyRepository.getProperties().add( nameProperty );
    session.persist( propertyRepository );
} );

doInHibernate( this::sessionFactory, session -> {
    PropertyRepository propertyRepository = session.get( PropertyRepository.class, 1L );
    assertEquals(2, propertyRepository.getProperties().size());
    for(Property property : propertyRepository.getProperties()) {
        assertNotNull( property.getValue() );
    }
} );
INSERT INTO integer_property
       ( "name", "value", id )
VALUES ( 'age', 23, 1 )

INSERT INTO string_property
       ( "name", "value", id )
VALUES ( 'name', 'John Doe', 1 )

INSERT INTO property_repository ( id )
VALUES ( 1 )

INSERT INTO repository_properties
    ( repository_id , property_type , property_id )
VALUES
    ( 1 , 'I' , 1 )

INSERT INTO repository_properties
    ( repository_id , property_type , property_id )
VALUES
    ( 1 , 'S' , 1 )

SELECT pr.id AS id1_1_0_
FROM   property_repository pr
WHERE  pr.id = 1

SELECT ip.id AS id1_0_0_ ,
       integerpro0_."name" AS name2_0_0_ ,
       integerpro0_."value" AS value3_0_0_
FROM   integer_property integerpro0_
WHERE  integerpro0_.id = 1

SELECT sp.id AS id1_3_0_ ,
       sp."name" AS name2_3_0_ ,
       sp."value" AS value3_3_0_
FROM   string_property sp
WHERE  sp.id = 1

2.3.27. @JoinFormula mapping

The @JoinFormula annotation is used to customize the join between a child Foreign Key and a parent row Primary Key.

Example 96. @JoinFormula mapping usage
@Entity(name = "User")
@Table(name = "users")
public static class User {

    @Id
    private Long id;

    private String firstName;

    private String lastName;

    private String phoneNumber;

    @ManyToOne
    @JoinFormula( "REGEXP_REPLACE(phoneNumber, '\\+(\\d+)-.*', '\\1')::int" )
    private Country country;

    //Getters and setters omitted for brevity

}

@Entity(name = "Country")
@Table(name = "countries")
public static class Country {

    @Id
    private Integer id;

    private String name;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( !( o instanceof Country ) ) {
            return false;
        }
        Country country = (Country) o;
        return Objects.equals( getId(), country.getId() );
    }

    @Override
    public int hashCode() {
        return Objects.hash( getId() );
    }
}
CREATE TABLE countries (
    id int4 NOT NULL,
    name VARCHAR(255),
    PRIMARY KEY ( id )
)

CREATE TABLE users (
    id int8 NOT NULL,
    firstName VARCHAR(255),
    lastName VARCHAR(255),
    phoneNumber VARCHAR(255),
    PRIMARY KEY ( id )
)

The country association in the User entity is mapped by the country identifier provided by the phoneNumber property.

Considering we have the following entities:

Example 97. @JoinFormula mapping usage
Country US = new Country();
US.setId( 1 );
US.setName( "United States" );

Country Romania = new Country();
Romania.setId( 40 );
Romania.setName( "Romania" );

doInJPA( this::entityManagerFactory, entityManager -> {
    entityManager.persist( US );
    entityManager.persist( Romania );
} );

doInJPA( this::entityManagerFactory, entityManager -> {
    User user1 = new User( );
    user1.setId( 1L );
    user1.setFirstName( "John" );
    user1.setLastName( "Doe" );
    user1.setPhoneNumber( "+1-234-5678" );
    entityManager.persist( user1 );

    User user2 = new User( );
    user2.setId( 2L );
    user2.setFirstName( "Vlad" );
    user2.setLastName( "Mihalcea" );
    user2.setPhoneNumber( "+40-123-4567" );
    entityManager.persist( user2 );
} );

When fetching the User entities, the country property is mapped by the @JoinFormula expression:

Example 98. @JoinFormula mapping usage
doInJPA( this::entityManagerFactory, entityManager -> {
    log.info( "Fetch User entities" );

    User john = entityManager.find( User.class, 1L );
    assertEquals( US, john.getCountry());

    User vlad = entityManager.find( User.class, 2L );
    assertEquals( Romania, vlad.getCountry());
} );
-- Fetch User entities

SELECT
    u.id as id1_1_0_,
    u.firstName as firstNam2_1_0_,
    u.lastName as lastName3_1_0_,
    u.phoneNumber as phoneNum4_1_0_,
    REGEXP_REPLACE(u.phoneNumber, '\+(\d+)-.*', '\1')::int as formula1_0_,
    c.id as id1_0_1_,
    c.name as name2_0_1_
FROM
    users u
LEFT OUTER JOIN
    countries c
        ON REGEXP_REPLACE(u.phoneNumber, '\+(\d+)-.*', '\1')::int = c.id
WHERE
    u.id=?

-- binding parameter [1] as [BIGINT] - [1]

SELECT
    u.id as id1_1_0_,
    u.firstName as firstNam2_1_0_,
    u.lastName as lastName3_1_0_,
    u.phoneNumber as phoneNum4_1_0_,
    REGEXP_REPLACE(u.phoneNumber, '\+(\d+)-.*', '\1')::int as formula1_0_,
    c.id as id1_0_1_,
    c.name as name2_0_1_
FROM
    users u
LEFT OUTER JOIN
    countries c
        ON REGEXP_REPLACE(u.phoneNumber, '\+(\d+)-.*', '\1')::int = c.id
WHERE
    u.id=?

-- binding parameter [1] as [BIGINT] - [2]

Therefore, the @JoinFormula annotation is used to define a custom join association between the parent-child association.

2.3.28. @JoinColumnOrFormula mapping

The @JoinColumnOrFormula annotation is used to customize the join between a child Foreign Key and a parent row Primary Key when we need to take into consideration a column value as well as a @JoinFormula.

Example 99. @JoinColumnOrFormula mapping usage
@Entity(name = "User")
@Table(name = "users")
public static class User {

    @Id
    private Long id;

    private String firstName;

    private String lastName;

    private String language;

    @ManyToOne
    @JoinColumnOrFormula( column =
        @JoinColumn(
            name = "language",
            referencedColumnName = "primaryLanguage",
            insertable = false,
            updatable = false
        )
    )
    @JoinColumnOrFormula( formula =
        @JoinFormula(
            value = "true",
            referencedColumnName = "is_default"
        )
    )
    private Country country;

    //Getters and setters omitted for brevity

}

@Entity(name = "Country")
@Table(name = "countries")
public static class Country implements Serializable {

    @Id
    private Integer id;

    private String name;

    private String primaryLanguage;

    @Column(name = "is_default")
    private boolean _default;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getPrimaryLanguage() {
        return primaryLanguage;
    }

    public void setPrimaryLanguage(String primaryLanguage) {
        this.primaryLanguage = primaryLanguage;
    }

    public boolean isDefault() {
        return _default;
    }

    public void setDefault(boolean _default) {
        this._default = _default;
    }

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( !( o instanceof Country ) ) {
            return false;
        }
        Country country = (Country) o;
        return Objects.equals( getId(), country.getId() );
    }

    @Override
    public int hashCode() {
        return Objects.hash( getId() );
    }
}
CREATE TABLE countries (
    id INTEGER NOT NULL,
    is_default boolean,
    name VARCHAR(255),
    primaryLanguage VARCHAR(255),
    PRIMARY KEY ( id )
)

CREATE TABLE users (
    id BIGINT NOT NULL,
    firstName VARCHAR(255),
    language VARCHAR(255),
    lastName VARCHAR(255),
    PRIMARY KEY ( id )
)

The country association in the User entity is mapped by the language property value and the associated Country is_default column value.

Considering we have the following entities:

Example 100. @JoinColumnOrFormula persist example
Country US = new Country();
US.setId( 1 );
US.setDefault( true );
US.setPrimaryLanguage( "English" );
US.setName( "United States" );

Country Romania = new Country();
Romania.setId( 40 );
Romania.setDefault( true );
Romania.setName( "Romania" );
Romania.setPrimaryLanguage( "Romanian" );

doInJPA( this::entityManagerFactory, entityManager -> {
    entityManager.persist( US );
    entityManager.persist( Romania );
} );

doInJPA( this::entityManagerFactory, entityManager -> {
    User user1 = new User( );
    user1.setId( 1L );
    user1.setFirstName( "John" );
    user1.setLastName( "Doe" );
    user1.setLanguage( "English" );
    entityManager.persist( user1 );

    User user2 = new User( );
    user2.setId( 2L );
    user2.setFirstName( "Vlad" );
    user2.setLastName( "Mihalcea" );
    user2.setLanguage( "Romanian" );
    entityManager.persist( user2 );

} );

When fetching the User entities, the country property is mapped by the @JoinColumnOrFormula expression:

Example 101. @JoinColumnOrFormula fetching example
doInJPA( this::entityManagerFactory, entityManager -> {
    log.info( "Fetch User entities" );

    User john = entityManager.find( User.class, 1L );
    assertEquals( US, john.getCountry());

    User vlad = entityManager.find( User.class, 2L );
    assertEquals( Romania, vlad.getCountry());
} );
SELECT
    u.id as id1_1_0_,
    u.language as language3_1_0_,
    u.firstName as firstNam2_1_0_,
    u.lastName as lastName4_1_0_,
    1 as formula1_0_,
    c.id as id1_0_1_,
    c.is_default as is_defau2_0_1_,
    c.name as name3_0_1_,
    c.primaryLanguage as primaryL4_0_1_
FROM
    users u
LEFT OUTER JOIN
    countries c
        ON u.language = c.primaryLanguage
        AND 1 = c.is_default
WHERE
    u.id = ?

-- binding parameter [1] as [BIGINT] - [1]

SELECT
    u.id as id1_1_0_,
    u.language as language3_1_0_,
    u.firstName as firstNam2_1_0_,
    u.lastName as lastName4_1_0_,
    1 as formula1_0_,
    c.id as id1_0_1_,
    c.is_default as is_defau2_0_1_,
    c.name as name3_0_1_,
    c.primaryLanguage as primaryL4_0_1_
FROM
    users u
LEFT OUTER JOIN
    countries c
        ON u.language = c.primaryLanguage
        AND 1 = c.is_default
WHERE
    u.id = ?

-- binding parameter [1] as [BIGINT] - [2]

Therefore, the @JoinColumnOrFormula annotation is used to define a custom join association between the parent-child association.

2.3.29. @Target mapping

The @Target annotation is used to specify the implementation class of a given association that is mapped via an interface. The @ManyToOne, @OneToOne, @OneToMany, and @ManyToMany feature a targetEntity attribute to specify the actual class of the entiity association when an interface is used for the mapping.

The @ElementCollection association has a targetClass attribute for the same purpose.

However, for simple embeddable types, there is no such construct and so you need to use the Hibernate-specific @Target annotation instead.

Example 102. @Target mapping usage
public interface Coordinates {
    double x();
    double y();
}

@Embeddable
public static class GPS implements Coordinates {

    private double latitude;

    private double longitude;

    private GPS() {
    }

    public GPS(double latitude, double longitude) {
        this.latitude = latitude;
        this.longitude = longitude;
    }

    @Override
    public double x() {
        return latitude;
    }

    @Override
    public double y() {
        return longitude;
    }
}

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

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @Embedded
    @Target( GPS.class )
    private Coordinates coordinates;

    //Getters and setters omitted for brevity

}

The coordinates embeddable type is mapped as the Coordinates interface. However, Hibernate needs to know the actual implementation tye, which is GPS in this case, hence the @Target annotation is used to provide this information.

Assuming we have persisted the following City entity:

Example 103. @Target persist example
doInJPA( this::entityManagerFactory, entityManager -> {

    City cluj = new City();
    cluj.setName( "Cluj" );
    cluj.setCoordinates( new GPS( 46.77120, 23.62360 ) );

    entityManager.persist( cluj );
} );

When fetching the City entity, the coordinates property is mapped by the @Target expression:

Example 104. @Target fetching example
doInJPA( this::entityManagerFactory, entityManager -> {

    City cluj = entityManager.find( City.class, 1L );

    assertEquals( 46.77120, cluj.getCoordinates().x(), 0.00001 );
    assertEquals( 23.62360, cluj.getCoordinates().y(), 0.00001 );
} );
SELECT
    c.id as id1_0_0_,
    c.latitude as latitude2_0_0_,
    c.longitude as longitud3_0_0_,
    c.name as name4_0_0_
FROM
    City c
WHERE
    c.id = ?

-- binding parameter [1] as [BIGINT] - [1]

-- extracted value ([latitude2_0_0_] : [DOUBLE])  - [46.7712]
-- extracted value ([longitud3_0_0_] : [DOUBLE])  - [23.6236]
-- extracted value ([name4_0_0_]     : [VARCHAR]) - [Cluj]

Therefore, the @Target annotation is used to define a custom join association between the parent-child association.

2.3.30. @Parent mapping

The Hibernate-specific @Parent annotation allows you to reference the owner entity from within an embeddable.

Example 105. @Parent mapping usage
@Embeddable
public static class GPS {

    private double latitude;

    private double longitude;

    @Parent
    private City city;

    private GPS() {
    }

    public GPS(double latitude, double longitude) {
        this.latitude = latitude;
        this.longitude = longitude;
    }

    public double getLatitude() {
        return latitude;
    }

    public double getLongitude() {
        return longitude;
    }

    public City getCity() {
        return city;
    }

    public void setCity(City city) {
        this.city = city;
    }
}

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

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @Embedded
    @Target( GPS.class )
    private GPS coordinates;

    //Getters and setters omitted for brevity

}

Assuming we have persisted the following City entity:

Example 106. @Parent persist example
doInJPA( this::entityManagerFactory, entityManager -> {

    City cluj = new City();
    cluj.setName( "Cluj" );
    cluj.setCoordinates( new GPS( 46.77120, 23.62360 ) );

    entityManager.persist( cluj );
} );

When fetching the City entity, the city property of the embeddable type acts as a back reference to the owning parent entity:

Example 107. @Parent fetching example
doInJPA( this::entityManagerFactory, entityManager -> {

    City cluj = entityManager.find( City.class, 1L );

    assertSame( cluj, cluj.getCoordinates().getCity() );
} );

Therefore, the @Parent annotation is used to define the association between an embeddable type and the owning entity.

2.4. Embeddable types

Historically Hibernate called these components. JPA calls them embeddables. Either way the concept is the same: a composition of values.

For example we might have a Publisher class that is a composition of name and country, or a Location class that is a composition of country and city.

Usage of the word embeddable

To avoid any confusion with the annotation that marks a given embeddable type, the annotation will be further referred as @Embeddable.

Throughout this chapter and thereafter, for brevity sake, embeddable types may also be referred as embeddable.

Example 108. Embeddable type example
@Embeddable
public static class Publisher {

    private String name;

    private Location location;

    public Publisher(String name, Location location) {
        this.name = name;
        this.location = location;
    }

    private Publisher() {}

    //Getters and setters are omitted for brevity
}

@Embeddable
public static class Location {

    private String country;

    private String city;

    public Location(String country, String city) {
        this.country = country;
        this.city = city;
    }

    private Location() {}

    //Getters and setters are omitted for brevity
}

An embeddable type is another form of value type, and its lifecycle is bound to a parent entity type, therefore inheriting the attribute access from its parent (for details on attribute access, see Access strategies).

Embeddable types can be made up of basic values as well as associations, with the caveat that, when used as collection elements, they cannot define collections themselves.

2.4.1. Component / Embedded

Most often, embeddable types are used to group multiple basic type mappings and reuse them across several entities.

Example 109. Simple Embeddedable
@Entity(name = "Book")
public static class Book {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    private String author;

    private Publisher publisher;

    //Getters and setters are omitted for brevity
}

@Embeddable
public static class Publisher {

    @Column(name = "publisher_name")
    private String name;

    @Column(name = "publisher_country")
    private String country;

    public Publisher(String name, String country) {
        this.name = name;
        this.country = country;
    }

    private Publisher() {}

    //Getters and setters are omitted for brevity
}
create table Book (
    id bigint not null,
    author varchar(255),
    publisher_country varchar(255),
    publisher_name varchar(255),
    title varchar(255),
    primary key (id)
)

JPA defines two terms for working with an embeddable type: @Embeddable and @Embedded.

@Embeddable is used to describe the mapping type itself (e.g. Publisher).

@Embedded is for referencing a given embeddable type (e.g. book#publisher).

So, the embeddable type is represented by the Publisher class and the parent entity makes use of it through the book#publisher object composition.

The composed values are mapped to the same table as the parent table. Composition is part of good Object-oriented data modeling (idiomatic Java). In fact, that table could also be mapped by the following entity type instead.

Example 110. Alternative to embeddable type composition
@Entity(name = "Book")
public static class Book {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    private String author;

    @Column(name = "publisher_name")
    private String publisherName;

    @Column(name = "publisher_country")
    private String publisherCountry;

    //Getters and setters are omitted for brevity
}

The composition form is certainly more Object-oriented, and that becomes more evident as we work with multiple embeddable types.

2.4.2. Multiple embeddable types

Although from an object-oriented perspective, it’s much more convenient to work with embeddable types, this example doesn’t work as-is. When the same embeddable type is included multiple times in the same parent entity type, the JPA specification demands setting the associated column names explicitly.

This requirement is due to how object properties are mapped to database columns. By default, JPA expects a database column having the same name with its associated object property. When including multiple embeddables, the implicit name-based mapping rule doesn’t work anymore because multiple object properties could end-up being mapped to the same database column.

We have a few options to handle this issue.

2.4.3. Overriding Embeddable types

JPA defines the @AttributeOverride annotation to handle this scenario. This way, the mapping conflict is resolved by setting up explicit name-based property-column type mappings.

If an Embeddabe type is used multiple times in some entity, you need to use the @AttributeOverride and @AssociationOverride annotations to override the default column names definied by the Embeddable.

Considering you have the following Publisher embeddable type which defines a @ManyToOne association with the Country entity:

Example 111. Embeddable type with a @ManyToOne association
@Embeddable
public static class Publisher {

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    private Country country;

    public Publisher(String name, Country country) {
        this.name = name;
        this.country = country;
    }

    private Publisher() {}

    //Getters and setters are omitted for brevity
}

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

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    private String name;

    //Getters and setters are omitted for brevity
}
create table Country (
    id bigint not null,
    name varchar(255),
    primary key (id)
)

alter table Country
    add constraint UK_p1n05aafu73sbm3ggsxqeditd
    unique (name)

Now, if you have a Book entity which declares two Publisher embeddable types for the ebook and paperback version, you cannot use the default Publisher embeddable mapping since there will be a conflict between the two embeddable column mappings.

Therefore, the Book entity needs to override the embeddable type mappings for each Publisher attribute:

Example 112. Overriding embeddable type attributes
@Entity(name = "Book")
@AttributeOverrides({
    @AttributeOverride(
        name = "ebookPublisher.name",
        column = @Column(name = "ebook_publisher_name")
    ),
    @AttributeOverride(
        name = "paperBackPublisher.name",
        column = @Column(name = "paper_back_publisher_name")
    )
})
@AssociationOverrides({
    @AssociationOverride(
        name = "ebookPublisher.country",
        joinColumns = @JoinColumn(name = "ebook_publisher_country_id")
    ),
    @AssociationOverride(
        name = "paperBackPublisher.country",
        joinColumns = @JoinColumn(name = "paper_back_publisher_country_id")
    )
})
public static class Book {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    private String author;

    private Publisher ebookPublisher;

    private Publisher paperBackPublisher;

    //Getters and setters are omitted for brevity
}
create table Book (
    id bigint not null,
    author varchar(255),
    ebook_publisher_name varchar(255),
    paper_back_publisher_name varchar(255),
    title varchar(255),
    ebook_publisher_country_id bigint,
    paper_back_publisher_country_id bigint,
    primary key (id)
)

alter table Book
    add constraint FKm39ibh5jstybnslaoojkbac2g
    foreign key (ebook_publisher_country_id)
    references Country

alter table Book
    add constraint FK7kqy9da323p7jw7wvqgs6aek7
    foreign key (paper_back_publisher_country_id)
    references Country

2.4.4. Embeddables and ImplicitNamingStrategy

This is a Hibernate specific feature. Users concerned with JPA provider portability should instead prefer explicit column naming with @AttributeOverride.

Hibernate naming strategies are covered in detail in Naming. However, for the purposes of this discussion, Hibernate has the capability to interpret implicit column names in a way that is safe for use with multiple embeddable types.

Example 113. Implicit multiple embeddable type mapping
@Entity(name = "Book")
public static class Book {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    private String author;

    private Publisher ebookPublisher;

    private Publisher paperBackPublisher;

    //Getters and setters are omitted for brevity
}

@Embeddable
public static class Publisher {

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    private Country country;

    public Publisher(String name, Country country) {
        this.name = name;
        this.country = country;
    }

    private Publisher() {}

    //Getters and setters are omitted for brevity
}

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

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    private String name;

    //Getters and setters are omitted for brevity
}

To make it work, you need to use the ImplicitNamingStrategyComponentPathImpl naming strategy.

Example 114. Enabling implicit embeddable type mapping using the component path naming strategy
metadataBuilder.applyImplicitNamingStrategy(
    ImplicitNamingStrategyComponentPathImpl.INSTANCE
);

Now the "path" to attributes are used in the implicit column naming:

create table Book (
    id bigint not null,
    author varchar(255),
    ebookPublisher_name varchar(255),
    paperBackPublisher_name varchar(255),
    title varchar(255),
    ebookPublisher_country_id bigint,
    paperBackPublisher_country_id bigint,
    primary key (id)
)

You could even develop your own naming strategy to do other types of implicit naming strategies.

2.4.5. Collections of embeddable types

Collections of embeddable types are specifically value collections (as embeddable types are a value type). Value collections are covered in detail in Collections of value types.

2.4.6. Embeddable types as Map key

Embeddable types can also be used as Map keys. This topic is converted in detail in Map - key.

2.4.7. Embeddable types as identifiers

Embeddable types can also be used as entity type identifiers. This usage is covered in detail in Composite identifiers.

Embeddable types that are used as collection entries, map keys or entity type identifiers cannot include their own collection mappings.

2.5. Entity types

Usage of the word entity

The entity type describes the mapping between the actual persistable domain model object and a database table row. To avoid any confusion with the annotation that marks a given entity type, the annotation will be further referred as @Entity.

Throughout this chapter and thereafter, entity types will be simply referred as entity.

2.5.1. POJO Models

Section 2.1 The Entity Class of the JPA 2.1 specification defines its requirements for an entity class. Applications that wish to remain portable across JPA providers should adhere to these requirements.

  • The entity class must be annotated with the javax.persistence.Entity annotation (or be denoted as such in XML mapping)

  • The entity class must have a public or protected no-argument constructor. It may define additional constructors as well.

  • The entity class must be a top-level class.

  • An enum or interface may not be designated as an entity.

  • The entity class must not be final. No methods or persistent instance variables of the entity class may be final.

  • If an entity instance is to be used remotely as a detached object, the entity class must implement the Serializable interface.

  • Both abstract and concrete classes can be entities. Entities may extend non-entity classes as well as entity classes, and non-entity classes may extend entity classes.

  • The persistent state of an entity is represented by instance variables, which may correspond to JavaBean-style properties. An instance variable must be directly accessed only from within the methods of the entity by the entity instance itself. The state of the entity is available to clients only through the entity’s accessor methods (getter/setter methods) or other business methods.

Hibernate, however, is not as strict in its requirements. The differences from the list above include:

  • The entity class must have a no-argument constructor, which may be public, protected or package visibility. It may define additional constructors as well.

  • The entity class need not be a top-level class.

  • Technically Hibernate can persist final classes or classes with final persistent state accessor (getter/setter) methods. However, it is generally not a good idea as doing so will stop Hibernate from being able to generate proxies for lazy-loading the entity.

  • Hibernate does not restrict the application developer from exposing instance variables and reference them from outside the entity class itself. The validity of such a paradigm, however, is debatable at best.

Let’s look at each requirement in detail.

2.5.2. Prefer non-final classes

A central feature of Hibernate is the ability to load lazily certain entity instance variables (attributes) via runtime proxies. This feature depends upon the entity class being non-final or else implementing an interface that declares all the attribute getters/setters. You can still persist final classes that do not implement such an interface with Hibernate, but you will not be able to use proxies for fetching lazy associations, therefore limiting your options for performance tuning. For the very same reason, you should also avoid declaring persistent attribute getters and setters as final.

Starting in 5.0 Hibernate offers a more robust version of bytecode enhancement as another means for handling lazy loading. Hibernate had some bytecode re-writing capabilities prior to 5.0 but they were very rudimentary. See the BytecodeEnhancement for additional information on fetching and on bytecode enhancement.

2.5.3. Implement a no-argument constructor

The entity class should have a no-argument constructor. Both Hibernate and JPA require this.

JPA requires that this constructor be defined as public or protected. Hibernate, for the most part, does not care about the constructor visibility, as long as the system SecurityManager allows overriding the visibility setting. That said, the constructor should be defined with at least package visibility if you wish to leverage runtime proxy generation.

2.5.4. Declare getters and setters for persistent attributes

The JPA specification requires this, otherwise the model would prevent accessing the entity persistent state fields directly from outside the entity itself.

Although Hibernate does not require it, it is recommended to follow the JavaBean conventions and define getters and setters for entity persistent attributes. Nevertheless, you can still tell Hibernate to directly access the entity fields.

Attributes (whether fields or getters/setters) need not be declared public. Hibernate can deal with attributes declared with public, protected, package or private visibility. Again, if wanting to use runtime proxy generation for lazy loading, the getter/setter should grant access to at least package visibility.

2.5.5. Provide identifier attribute(s)

Historically this was considered optional. However, not defining identifier attribute(s) on the entity should be considered a deprecated feature that will be removed in an upcoming release.

The identifier attribute does not necessarily need to be mapped to the column(s) that physically define the primary key. However, it should map to column(s) that can uniquely identify each row.

We recommend that you declare consistently-named identifier attributes on persistent classes and that you use a nullable (i.e., non-primitive) type.

The placement of the @Id annotation marks the persistence state access strategy.

Example 115. Identifier mapping
@Id
private Long id;

Hibernate offers multiple identifier generation strategies, see the Identifier Generators chapter for more about this topic.

2.5.6. Mapping the entity

The main piece in mapping the entity is the javax.persistence.Entity annotation. The @Entity annotation defines just one attribute name which is used to give a specific entity name for use in JPQL queries. By default, the entity name represents the unqualified name of the entity class itself.

Example 116. Simple @Entity mapping
@Entity(name = "Book")
public static class Book {

    @Id
    private Long id;

    private String title;

    private String author;

    //Getters and setters are omitted for brevity
}

An entity models a database table. The identifier uniquely identifies each row in that table. By default, the name of the table is assumed to be the same as the name of the entity. To explicitly give the name of the table or to specify other information about the table, we would use the javax.persistence.Table annotation.

Example 117. Simple @Entity with @Table
@Entity(name = "Book")
@Table(
    catalog = "public",
    schema = "store",
    name = "book"
)
public static class Book {

    @Id
    private Long id;

    private String title;

    private String author;

    //Getters and setters are omitted for brevity
}

2.5.7. Implementing equals() and hashCode()

Much of the discussion in this section deals with the relation of an entity to a Hibernate Session, whether the entity is managed, transient or detached. If you are unfamiliar with these topics, they are explained in the Persistence Context chapter.

Whether to implement equals() and hashCode() methods in your domain model, let alone how to implement them, is a surprisingly tricky discussion when it comes to ORM.

There is really just one absolute case: a class that acts as an identifier must implement equals/hashCode based on the id value(s). Generally, this is pertinent for user-defined classes used as composite identifiers. Beyond this one very specific use case and few others we will discuss below, you may want to consider not implementing equals/hashCode altogether.

So what’s all the fuss? Normally, most Java objects provide a built-in equals() and hashCode() based on the object’s identity, so each new object will be different from all others. This is generally what you want in ordinary Java programming. Conceptually however this starts to break down when you start to think about the possibility of multiple instances of a class representing the same data.

This is, in fact, exactly the case when dealing with data coming from a database. Every time we load a specific Person from the database we would naturally get a unique instance. Hibernate, however, works hard to make sure that does not happen within a given Session. In fact, Hibernate guarantees equivalence of persistent identity (database row) and Java identity inside a particular session scope. So if we ask a Hibernate Session to load that specific Person multiple times we will actually get back the same instance:

Example 118. Scope of identity
Book book1 = entityManager.find( Book.class, 1L );
Book book2 = entityManager.find( Book.class, 1L );

assertTrue( book1 == book2 );

Consider we have a Library parent entity which contains a java.util.Set of Book entities:

Library entity mapping

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

    @Id
    private Long id;

    private String name;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "book_id")
    private Set<Book> books = new HashSet<>();

    //Getters and setters are omitted for brevity
}
Example 119. Set usage with Session-scoped identity
Library library = entityManager.find( Library.class, 1L );

Book book1 = entityManager.find( Book.class, 1L );
Book book2 = entityManager.find( Book.class, 1L );

library.getBooks().add( book1 );
library.getBooks().add( book2 );

assertEquals( 1, library.getBooks().size() );

However, the semantic changes when we mix instances loaded from different Sessions:

Example 120. Mixed Sessions
Book book1 = doInJPA( this::entityManagerFactory, entityManager -> {
    return entityManager.find( Book.class, 1L );
} );

Book book2 = doInJPA( this::entityManagerFactory, entityManager -> {
    return entityManager.find( Book.class, 1L );
} );

assertFalse( book1 == book2 );
doInJPA( this::entityManagerFactory, entityManager -> {
    Set<Book> books = new HashSet<>();

    books.add( book1 );
    books.add( book2 );

    assertEquals( 2, books.size() );
} );

Specifically the outcome in this last example will depend on whether the Book class implemented equals/hashCode, and, if so, how.

If the Book class did not override the default equals/hashCode, then the two Book object reference are not going to be equal since their references are different.

Consider yet another case:

Example 121. Sets with transient entities
Library library = entityManager.find( Library.class, 1L );

Book book1 = new Book();
book1.setId( 100L );
book1.setTitle( "High-Performance Java Persistence" );

Book book2 = new Book();
book2.setId( 101L );
book2.setTitle( "Java Persistence with Hibernate" );

library.getBooks().add( book1 );
library.getBooks().add( book2 );

assertEquals( 2, library.getBooks().size() );

In cases where you will be dealing with entities outside of a Session (whether they be transient or detached), especially in cases where you will be using them in Java collections, you should consider implementing equals/hashCode.

A common initial approach is to use the entity’s identifier attribute as the basis for equals/hashCode calculations:

Example 122. Naive equals/hashCode implementation
@Entity(name = "Library")
public static class Library {

    @Id
    private Long id;

    private String name;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "book_id")
    private Set<Book> books = new HashSet<>();

    //Getters and setters are omitted for brevity
}

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

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    private String author;

    //Getters and setters are omitted for brevity

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        Book book = (Book) o;
        return Objects.equals( id, book.id );
    }

    @Override
    public int hashCode() {
        return Objects.hash( id );
    }
}

It turns out that this still breaks when adding transient instance of Book to a set as we saw in the last example:

Example 123. Auto-generated identifiers with Sets and naive equals/hashCode
Book book1 = new Book();
book1.setTitle( "High-Performance Java Persistence" );

Book book2 = new Book();
book2.setTitle( "Java Persistence with Hibernate" );

Library library = doInJPA( this::entityManagerFactory, entityManager -> {
    Library _library = entityManager.find( Library.class, 1L );

    _library.getBooks().add( book1 );
    _library.getBooks().add( book2 );

    return _library;
} );

assertFalse( library.getBooks().contains( book1 ) );
assertFalse( library.getBooks().contains( book2 ) );

The issue here is a conflict between the use of generated identifier, the contract of Set and the equals/hashCode implementations. Set says that the equals/hashCode value for an object should not change while the object is part of the Set. But that is exactly what happened here because the equals/hasCode are based on the (generated) id, which was not set until the JPA transaction is committed.

Note that this is just a concern when using generated identifiers. If you are using assigned identifiers this will not be a problem, assuming the identifier value is assigned prior to adding to the Set.

Another option is to force the identifier to be generated and set prior to adding to the Set:

Example 124. Forcing the flush before adding to the Set
Book book1 = new Book();
book1.setTitle( "High-Performance Java Persistence" );

Book book2 = new Book();
book2.setTitle( "Java Persistence with Hibernate" );

Library library = doInJPA( this::entityManagerFactory, entityManager -> {
    Library _library = entityManager.find( Library.class, 1L );

    entityManager.persist( book1 );
    entityManager.persist( book2 );
    entityManager.flush();

    _library.getBooks().add( book1 );
    _library.getBooks().add( book2 );

    return _library;
} );

assertTrue( library.getBooks().contains( book1 ) );
assertTrue( library.getBooks().contains( book2 ) );

But this is often not feasible.

The final approach is to use a "better" equals/hashCode implementation, making use of a natural-id or business-key.

Example 125. Natural Id equals/hashCode
@Entity(name = "Library")
public static class Library {

    @Id
    private Long id;

    private String name;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "book_id")
    private Set<Book> books = new HashSet<>();

    //Getters and setters are omitted for brevity
}

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

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    private String author;

    @NaturalId
    private String isbn;

    //Getters and setters are omitted for brevity

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        Book book = (Book) o;
        return Objects.equals( isbn, book.isbn );
    }

    @Override
    public int hashCode() {
        return Objects.hash( isbn );
    }
}

This time, when adding a Book to the Library Set, you can retrieve the Book even after it’s being persisted:

Example 126. Natural Id equals/hashCode persist example
Book book1 = new Book();
book1.setTitle( "High-Performance Java Persistence" );
book1.setIsbn( "978-9730228236" );

Library library = doInJPA( this::entityManagerFactory, entityManager -> {
    Library _library = entityManager.find( Library.class, 1L );

    _library.getBooks().add( book1 );

    return _library;
} );

assertTrue( library.getBooks().contains( book1 ) );

As you can see the question of equals/hashCode is not trivial, nor is there a one-size-fits-all solution.

Although using a natural-id is best for equals and hashCode, sometimes you only have the entity identifier that provides a unique constraint.

It’s possible to use the entity identifier for equality check, but it needs a workaround:

  • you need to provide a constant value for hashCode so that the hash code value does not change before and after the entity is flushed.

  • you need to compare the entity identifier equality only for non-transient entities.

For details on mapping the identifier, see the Identifiers chapter.

2.5.8. Mapping the entity to a SQL query

You can map an entity to a SQL query using the @Subselect annotation.

Example 127. @Subselect entity mapping
@Entity(name = "Client")
@Table(name = "client")
public static class Client {

    @Id
    private Long id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    //Getters and setters omitted for brevity

}

@Entity(name = "Account")
@Table(name = "account")
public static class Account {

    @Id
    private Long id;

    @ManyToOne
    private Client client;

    private String description;

    //Getters and setters omitted for brevity

}

@Entity(name = "AccountTransaction")
@Table(name = "account_transaction")
public static class AccountTransaction {

    @Id
    @GeneratedValue
    private Long id;

    @ManyToOne
    private Account account;

    private Integer cents;

    private String description;

    //Getters and setters omitted for brevity

}

@Entity(name = "AccountSummary")
@Subselect(
    "select " +
    "    a.id as id, " +
    "    concat(concat(c.first_name, ' '), c.last_name) as clientName, " +
    "    sum(at.cents) as balance " +
    "from account a " +
    "join client c on c.id = a.client_id " +
    "join account_transaction at on a.id = at.account_id " +
    "group by a.id, concat(concat(c.first_name, ' '), c.last_name)"
)
@Synchronize( {"client", "account", "account_transaction"} )
public static class AccountSummary {

    @Id
    private Long id;

    private String clientName;

    private int balance;

    //Getters and setters omitted for brevity

}

In the example above, the Account entity does not retain any balance since every account operation is registered as an AccountTransaction. To find the Account balance, we need to query the AccountSummary which shares the same identifier with the Account entity.

However, the AccountSummary is not mapped to a physical table, but to an SQL query.

So, if we have the following AccountTransaction record, the AccountSummary balance will mach the proper amount of money in this Account.

Example 128. Finding a @Subselect entity
doInJPA( this::entityManagerFactory, entityManager -> {
    Client client = new Client();
    client.setId( 1L );
    client.setFirstName( "John" );
    client.setLastName( "Doe" );
    entityManager.persist( client );

    Account account = new Account();
    account.setId( 1L );
    account.setClient( client );
    account.setDescription( "Checking account" );
    entityManager.persist( account );

    AccountTransaction transaction = new AccountTransaction();
    transaction.setAccount( account );
    transaction.setDescription( "Salary" );
    transaction.setCents( 100 * 7000 );
    entityManager.persist( transaction );

    AccountSummary summary = entityManager.createQuery(
        "select s " +
        "from AccountSummary s " +
        "where s.id = :id", AccountSummary.class)
    .setParameter( "id", account.getId() )
    .getSingleResult();

    assertEquals( "John Doe", summary.getClientName() );
    assertEquals( 100 * 7000, summary.getBalance() );
} );

If we add a new AccountTransaction entity and refresh the AccountSummary entity, the balance is updated accordingly:

Example 129. Refreshing a @Subselect entity
doInJPA( this::entityManagerFactory, entityManager -> {
    AccountSummary summary = entityManager.find( AccountSummary.class, 1L );
    assertEquals( "John Doe", summary.getClientName() );
    assertEquals( 100 * 7000, summary.getBalance() );

    AccountTransaction transaction = new AccountTransaction();
    transaction.setAccount( entityManager.getReference( Account.class, 1L ) );
    transaction.setDescription( "Shopping" );
    transaction.setCents( -100 * 2200 );
    entityManager.persist( transaction );
    entityManager.flush();

    entityManager.refresh( summary );
    assertEquals( 100 * 4800, summary.getBalance() );
} );

The goal of the @Synchronize annotation in the AccountSummary entity mapping is to instruct Hibernate which database tables are needed by the underlying @Subselect SQL query. This way, when executing a HQL or JPQL which selects from the AccountSummary entity, Hibernate will trigger a Persistence Context flush if there are pending Account, Client or AccountTransaction entity state transitions.

2.5.9. Define a custom entity proxy

By default, when it needs to use a proxy instead of the actual Pojo, Hibernate is going to use a Bytecode manipulation library like Javassist or Byte Buddy.

However, if the entity class is final, Javassist will not create a Proxy and you will get a Pojo even when you only need a Proxy reference. In this case, you could proxy an interface that this particular entity implements, as illustrated by the following example.

Example 130. Final entity class implementing the Identifiable interface
public interface Identifiable {

    Long getId();

    void setId(Long id);
}

@Entity( name = "Book" )
@Proxy(proxyClass = Identifiable.class)
public static final class Book implements Identifiable {

    @Id
    private Long id;

    private String title;

    private String author;

    @Override
    public Long getId() {
        return id;
    }

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

    //Other getters and setters omitted for brevity
}

The @Proxy annotation is used to specify a custom Proxy implementation for the current annotated entity.

When loading the Book entity proxy, Hibernate is going to proxy the Identifiable interface instead as illustarted by the following example:

Example 131. Proxying the final entity class implementing the Identifiable interface
doInHibernate( this::sessionFactory, session -> {
    Book book = new Book();
    book.setId( 1L );
    book.setTitle( "High-Performance Java Persistence" );
    book.setAuthor( "Vlad Mihalcea" );

    session.persist( book );
} );

doInHibernate( this::sessionFactory, session -> {
    Identifiable book = session.getReference( Book.class, 1L );

    assertTrue(
        "Loaded entity is not an instance of the proxy interface",
        Identifiable.class.isInstance( book )
    );
    assertFalse(
        "Proxy class was not created",
        Book.class.isInstance( book )
    );
} );
into
    Book
    (author, title, id)
values
    (?, ?, ?)

-- binding parameter [1] as [VARCHAR] - [Vlad Mihalcea]
-- binding parameter [2] as [VARCHAR] - [High-Performance Java Persistence]
-- binding parameter [3] as [BIGINT]  - [1]

As you can see in the associated SQL snippet, Hibernate issues no SQL SELECT query since the Proxy can be constructed without needing to fetch the actual entity Pojo.

2.5.10. Dynamic entity proxies using the @Tuplizer annotation

It is possible to map your entities as dynamic proxies using the @Tuplizer annotation.

In the following entity mapping, both the embeddable and the entity are mapped as interfaces, not Pojos.

Example 132. Dynamic entity proxy mapping
@Entity
@Tuplizer(impl = DynamicEntityTuplizer.class)
public interface Cuisine {

    @Id
    @GeneratedValue
    Long getId();
    void setId(Long id);

    String getName();
    void setName(String name);

    @Tuplizer(impl = DynamicEmbeddableTuplizer.class)
    Country getCountry();
    void setCountry(Country country);
}
@Embeddable
public interface Country {

    @Column(name = "CountryName")
    String getName();

    void setName(String name);
}

The @Tuplizer instructs Hibernate to use the DynamicEntityTuplizer and DynamicEmbeddableTuplizer to handle the associated entity and embeddable object types.

Both the Cuising entity and the Country embeddable types are going to be instantiated as Java dynamic proxies, as you can see in the following DynamicInstantiator example:

Example 133. Instantiating entities and embeddables as dynamic proxies
public class DynamicEntityTuplizer extends PojoEntityTuplizer {

    public DynamicEntityTuplizer(
            EntityMetamodel entityMetamodel,
            PersistentClass mappedEntity) {
        super( entityMetamodel, mappedEntity );
    }

    @Override
    protected Instantiator buildInstantiator(
            EntityMetamodel entityMetamodel,
            PersistentClass persistentClass) {
        return new DynamicInstantiator(
            persistentClass.getClassName()
        );
    }

    @Override
    protected ProxyFactory buildProxyFactory(
            PersistentClass persistentClass,
            Getter idGetter,
            Setter idSetter) {
        return super.buildProxyFactory(
            persistentClass, idGetter,
            idSetter
        );
    }
}
public class DynamicEmbeddableTuplizer
        extends PojoComponentTuplizer {

    public DynamicEmbeddableTuplizer(Component embeddable) {
        super( embeddable );
    }

    protected Instantiator buildInstantiator(Component embeddable) {
        return new DynamicInstantiator(
            embeddable.getComponentClassName()
        );
    }
}
public class DynamicInstantiator
        implements Instantiator {

    private final Class targetClass;

    public DynamicInstantiator(String targetClassName) {
        try {
            this.targetClass = Class.forName( targetClassName );
        }
        catch (ClassNotFoundException e) {
            throw new HibernateException( e );
        }
    }

    public Object instantiate(Serializable id) {
        return ProxyHelper.newProxy( targetClass, id );
    }

    public Object instantiate() {
        return instantiate( null );
    }

    public boolean isInstance(Object object) {
        try {
            return targetClass.isInstance( object );
        }
        catch( Throwable t ) {
            throw new HibernateException(
                "could not get handle to entity as interface : " + t
            );
        }
    }
}
public class ProxyHelper {

    public static <T> T newProxy(Class<T> targetClass, Serializable id) {
        return ( T ) Proxy.newProxyInstance(
            targetClass.getClassLoader(),
            new Class[] {
                targetClass
            },
            new DataProxyHandler(
                targetClass.getName(),
                id
            )
        );
    }

    public static String extractEntityName(Object object) {
        if ( Proxy.isProxyClass( object.getClass() ) ) {
            InvocationHandler handler = Proxy.getInvocationHandler(
                object
            );
            if ( DataProxyHandler.class.isAssignableFrom( handler.getClass() ) ) {
                DataProxyHandler myHandler = (DataProxyHandler) handler;
                return myHandler.getEntityName();
            }
        }
        return null;
    }
}
public final class DataProxyHandler implements InvocationHandler {

    private String entityName;

    private Map<String, Object> data = new HashMap<>();

    public DataProxyHandler(String entityName, Serializable id) {
        this.entityName = entityName;
        data.put( "Id", id );
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if ( methodName.startsWith( "set" ) ) {
            String propertyName = methodName.substring( 3 );
            data.put( propertyName, args[0] );
        }
        else if ( methodName.startsWith( "get" ) ) {
            String propertyName = methodName.substring( 3 );
            return data.get( propertyName );
        }
        else if ( "toString".equals( methodName ) ) {
            return entityName + "#" + data.get( "Id" );
        }
        else if ( "hashCode".equals( methodName ) ) {
            return this.hashCode();
        }
        return null;
    }

    public String getEntityName() {
        return entityName;
    }
}

With the DynamicInstantiator in place, we can work with the dynamic proxy entities just like with Pojo entities.

Example 134. Persisting entities and embeddables as dynamic proxies
Cuisine _cuisine = doInHibernateSessionBuilder(
        () -> sessionFactory()
                .withOptions()
                .interceptor( new EntityNameInterceptor() ),
        session -> {
    Cuisine cuisine = ProxyHelper.newProxy( Cuisine.class, null );
    cuisine.setName( "Française" );

    Country country = ProxyHelper.newProxy( Country.class, null );
    country.setName( "France" );

    cuisine.setCountry( country );
    session.persist( cuisine );

    return cuisine;
} );

doInHibernateSessionBuilder(
        () -> sessionFactory()
                .withOptions()
                .interceptor( new EntityNameInterceptor() ),
        session -> {
    Cuisine cuisine = session.get( Cuisine.class, _cuisine.getId() );

    assertEquals( "Française", cuisine.getName() );
    assertEquals( "France", cuisine.getCountry().getName() );
} );

2.5.11. Define a custom entity persister

The @Persister annotation is used to specify a custom entity or collection persister.

For entities, the custom persister must implement the EntityPersister interface.

For collections, the custom persister must implement the CollectionPersister interface.

Example 135. Entity persister mapping
@Entity
@Persister( impl = EntityPersister.class )
public class Author {

    @Id
    public Integer id;

    @OneToMany( mappedBy = "author" )
    @Persister( impl = CollectionPersister.class )
    public Set<Book> books = new HashSet<>();

    //Getters and setters omitted for brevity
    public void addBook(Book book) {
        this.books.add( book );
        book.setAuthor( this );
    }
}
@Entity
@Persister( impl = EntityPersister.class )
public class Book {

    @Id
    public Integer id;

    private String title;

    @ManyToOne(fetch = FetchType.LAZY)
    public Author author;

    //Getters and setters omitted for brevity
}

By providing you own EntityPersister and CollectionPersister implementations, you can control how entities and collections are persisted in to the database.

2.5.12. Access strategies

As a JPA provider, Hibernate can introspect both the entity attributes (instance fields) or the accessors (instance properties). By default, the placement of the @Id annotation gives the default access strategy. When placed on a field, Hibernate will assume field-based access. Place on the identifier getter, Hibernate will use property-based access.

You should pay attention to Java Beans specification in regard to naming properties to avoid issues such as Property name beginning with at least two uppercase characters has odd functionality in HQL!

Embeddable types inherit the access strategy from their parent entities.

Field-based access
Example 136. Field-based access
@Entity(name = "Book")
public static class Book {

    @Id
    private Long id;

    private String title;

    private String author;

    //Getters and setters are omitted for brevity
}

When using field-based access, adding other entity-level methods is much more flexible because Hibernate won’t consider those part of the persistence state. To exclude a field from being part of the entity persistent state, the field must be marked with the @Transient annotation.

Another advantage of using field-based access is that some entity attributes can be hidden from outside the entity. An example of such attribute is the entity @Version field, which, usually, does not need to be manipulated by the data access layer. With field-based access, we can simply omit the getter and the setter for this version field, and Hibernate can still leverage the optimistic concurrency control mechanism.

Property-based access
Example 137. Property-based access
@Entity(name = "Book")
public static class Book {

    private Long id;

    private String title;

    private String author;

    @Id
    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 String getAuthor() {
        return author;
    }

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

When using property-based access, Hibernate uses the accessors for both reading and writing the entity state. Every other method that will be added to the entity (e.g. helper methods for synchronizing both ends of a bidirectional one-to-many association) will have to be marked with the @Transient annotation.

Overriding the default access strategy

The default access strategy mechanism can be overridden with the JPA @Access annotation. In the following example, the @Version attribute is accessed by its field and not by its getter, like the rest of entity attributes.

Example 138. Overriding access strategy
@Entity(name = "Book")
public static class Book {

    private Long id;

    private String title;

    private String author;

    @Access( AccessType.FIELD )
    @Version
    private int version;

    @Id
    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 String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
}
Embeddable types and access strategy

Because embeddables are managed by their owning entities, the access strategy is therefore inherited from the entity too. This applies to both simple embeddable types as well as for collection of embeddables.

The embeddable types can overrule the default implicit access strategy (inherited from the owning entity). In the following example, the embeddable uses property-based access, no matter what access strategy the owning entity is choosing:

Example 139. Embeddable with exclusive access strategy
@Embeddable
@Access( AccessType.PROPERTY )
public static class Author {

    private String firstName;

    private String lastName;

    public Author() {
    }

    public Author(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

The owning entity can use field-based access while the embeddable uses property-based access as it has chosen explicitly:

Example 140. Entity including a single embeddable type
@Entity(name = "Book")
public static class Book {

    @Id
    private Long id;

    private String title;

    @Embedded
    private Author author;

    //Getters and setters are omitted for brevity
}

This works also for collection of embeddable types:

Example 141. Entity including a collection of embeddable types
@Entity(name = "Book")
public static class Book {

    @Id
    private Long id;

    private String title;

    @ElementCollection
    @CollectionTable(
        name = "book_author",
        joinColumns = @JoinColumn(name = "book_id")
    )
    private List<Author> authors = new ArrayList<>();

    //Getters and setters are omitted for brevity
}

2.6. Identifiers

Identifiers model the primary key of an entity. They are used to uniquely identify each specific entity.

Hibernate and JPA both make the following assumptions about the corresponding database column(s):

UNIQUE

The values must uniquely identify each row.

NOT NULL

The values cannot be null. For composite ids, no part can be null.

IMMUTABLE

The values, once inserted, can never be changed. This is more a general guide, than a hard-fast rule as opinions vary. JPA defines the behavior of changing the value of the identifier attribute to be undefined; Hibernate simply does not support that. In cases where the values for the PK you have chosen will be updated, Hibernate recommends mapping the mutable value as a natural id, and use a surrogate id for the PK. See Natural Ids.

Technically the identifier does not have to map to the column(s) physically defined as the table primary key. They just need to map to column(s) that uniquely identify each row. However this documentation will continue to use the terms identifier and primary key interchangeably.

Every entity must define an identifier. For entity inheritance hierarchies, the identifier must be defined just on the entity that is the root of the hierarchy.

An identifier might be simple (single value) or composite (multiple values).

2.6.1. Simple identifiers

Simple identifiers map to a single basic attribute, and are denoted using the javax.persistence.Id annotation.

According to JPA only the following types should be used as identifier attribute types:

  • any Java primitive type

  • any primitive wrapper type

  • java.lang.String

  • java.util.Date (TemporalType#DATE)

  • java.sql.Date

  • java.math.BigDecimal

  • java.math.BigInteger

Any types used for identifier attributes beyond this list will not be portable.

Assigned identifiers

Values for simple identifiers can be assigned, as we have seen in the examples above. The expectation for assigned identifier values is that the application assigns (sets them on the entity attribute) prior to calling save/persist.

Example 142. Simple assigned entity identifier
@Entity(name = "Book")
public static class Book {

    @Id
    private Long id;

    private String title;

    private String author;

    //Getters and setters are omitted for brevity
}
Generated identifiers

Values for simple identifiers can be generated. To denote that an identifier attribute is generated, it is annotated with javax.persistence.GeneratedValue

Example 143. Simple generated identifier
@Entity(name = "Book")
public static class Book {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    private String author;

    //Getters and setters are omitted for brevity
}

Additionally, to the type restriction list above, JPA says that if using generated identifier values (see below) only integer types (short, int, long) will be portably supported.

The expectation for generated identifier values is that Hibernate will generate the value when the save/persist occurs.

Identifier value generations strategies are discussed in detail in the Generated identifier values section.

2.6.2. Composite identifiers

Composite identifiers correspond to one or more persistent attributes. Here are the rules governing composite identifiers, as defined by the JPA specification.

  • The composite identifier must be represented by a "primary key class". The primary key class may be defined using the javax.persistence.EmbeddedId annotation (see Composite identifiers with @EmbeddedId), or defined using the javax.persistence.IdClass annotation (see Composite identifiers with @IdClass).

  • The primary key class must be public and must have a public no-arg constructor.

  • The primary key class must be serializable.

  • The primary key class must define equals and hashCode methods, consistent with equality for the underlying database types to which the primary key is mapped.

The restriction that a composite identifier has to be represented by a "primary key class" is only JPA specific. Hibernate does allow composite identifiers to be defined without a "primary key class", although that modeling technique is deprecated and therefore omitted from this discussion.

The attributes making up the composition can be either basic, composite, ManyToOne. Note especially that collections and one-to-ones are never appropriate.

2.6.3. Composite identifiers with @EmbeddedId

Modeling a composite identifier using an EmbeddedId simply means defining an embeddable to be a composition for the one or more attributes making up the identifier, and then exposing an attribute of that embeddable type on the entity.

Example 144. Basic @EmbeddedId
@Entity(name = "SystemUser")
public static class SystemUser {

    @EmbeddedId
    private PK pk;

    private String name;

    //Getters and setters are omitted for brevity
}

@Embeddable
public static class PK implements Serializable {

    private String subsystem;

    private String username;

    public PK(String subsystem, String username) {
        this.subsystem = subsystem;
        this.username = username;
    }

    private PK() {
    }

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        PK pk = (PK) o;
        return Objects.equals( subsystem, pk.subsystem ) &&
                Objects.equals( username, pk.username );
    }

    @Override
    public int hashCode() {
        return Objects.hash( subsystem, username );
    }
}

As mentioned before, EmbeddedIds can even contain @ManyToOne attributes.

Example 145. @EmbeddedId with @ManyToOne
@Entity(name = "SystemUser")
public static class SystemUser {

    @EmbeddedId
    private PK pk;

    private String name;

    //Getters and setters are omitted for brevity
}

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

    @Id
    private String id;

    private String description;

    //Getters and setters are omitted for brevity
}

@Embeddable
public static class PK implements Serializable {

    @ManyToOne(fetch = FetchType.LAZY)
    private Subsystem subsystem;

    private String username;

    public PK(Subsystem subsystem, String username) {
        this.subsystem = subsystem;
        this.username = username;
    }

    private PK() {
    }

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        PK pk = (PK) o;
        return Objects.equals( subsystem, pk.subsystem ) &&
                Objects.equals( username, pk.username );
    }

    @Override
    public int hashCode() {
        return Objects.hash( subsystem, username );
    }
}

Hibernate supports directly modeling the ManyToOne in the PK class, whether @EmbeddedId or @IdClass.

However, that is not portably supported by the JPA specification. In JPA terms one would use "derived identifiers"; for details, see Derived Identifiers.

2.6.4. Composite identifiers with @IdClass

Modeling a composite identifier using an IdClass differs from using an EmbeddedId in that the entity defines each individual attribute making up the composition. The IdClass simply acts as a "shadow".

Example 146. Basic @IdClass
@Entity(name = "SystemUser")
@IdClass( PK.class )
public static class SystemUser {

    @Id
    private String subsystem;

    @Id
    private String username;

    private String name;

    public PK getId() {
        return new PK(
            subsystem,
            username
        );
    }

    public void setId(PK id) {
        this.subsystem = id.getSubsystem();
        this.username = id.getUsername();
    }

    //Getters and setters are omitted for brevity
}

public static class PK implements Serializable {

    private String subsystem;

    private String username;

    public PK(String subsystem, String username) {
        this.subsystem = subsystem;
        this.username = username;
    }

    private PK() {
    }

    //Getters and setters are omitted for brevity

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        PK pk = (PK) o;
        return Objects.equals( subsystem, pk.subsystem ) &&
                Objects.equals( username, pk.username );
    }

    @Override
    public int hashCode() {
        return Objects.hash( subsystem, username );
    }
}

Non-aggregated composite identifiers can also contain ManyToOne attributes as we saw with aggregated ones (still non-portably).

Example 147. IdClass with @ManyToOne
@Entity(name = "SystemUser")
@IdClass( PK.class )
public static class SystemUser {

    @Id
    @ManyToOne(fetch = FetchType.LAZY)
    private Subsystem subsystem;

    @Id
    private String username;

    private String name;

    //Getters and setters are omitted for brevity
}

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

    @Id
    private String id;

    private String description;

    //Getters and setters are omitted for brevity
}

public static class PK implements Serializable {

    private Subsystem subsystem;

    private String username;

    public PK(Subsystem subsystem, String username) {
        this.subsystem = subsystem;
        this.username = username;
    }

    private PK() {
    }

    //Getters and setters are omitted for brevity
}

With non-aggregated composite identifiers, Hibernate also supports "partial" generation of the composite values.

Example 148. @IdClass with partial identifier generation using @GeneratedValue
@Entity(name = "SystemUser")
@IdClass( PK.class )
public static class SystemUser {

    @Id
    private String subsystem;

    @Id
    private String username;

    @Id
    @GeneratedValue
    private Integer registrationId;

    private String name;

    public PK getId() {
        return new PK(
            subsystem,
            username,
            registrationId
        );
    }

    public void setId(PK id) {
        this.subsystem = id.getSubsystem();
        this.username = id.getUsername();
        this.registrationId = id.getRegistrationId();
    }

    //Getters and setters are omitted for brevity
}

public static class PK implements Serializable {

    private String subsystem;

    private String username;

    private Integer registrationId;

    public PK(String subsystem, String username) {
        this.subsystem = subsystem;
        this.username = username;
    }

    public PK(String subsystem, String username, Integer registrationId) {
        this.subsystem = subsystem;
        this.username = username;
        this.registrationId = registrationId;
    }

    private PK() {
    }

    //Getters and setters are omitted for brevity

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        PK pk = (PK) o;
        return Objects.equals( subsystem, pk.subsystem ) &&
                Objects.equals( username, pk.username ) &&
                Objects.equals( registrationId, pk.registrationId );
    }

    @Override
    public int hashCode() {
        return Objects.hash( subsystem, username, registrationId );
    }
}

This feature exists because of a highly questionable interpretation of the JPA specification made by the SpecJ committee.

Hibernate does not feel that JPA defines support for this, but added the feature simply to be usable in SpecJ benchmarks. Use of this feature may or may not be portable from a JPA perspective.

2.6.5. Composite identifiers with associations

Hibernate allows defining a composite identifier out of entity associations. In the following example, the PersonAddress entity identifier is formed of two @ManyToOne associations.

Example 149. Composite identifiers with associations
@Entity(name = "Book")
public static class Book implements Serializable {

    @Id
    @ManyToOne(fetch = FetchType.LAZY)
    private Author author;

    @Id
    @ManyToOne(fetch = FetchType.LAZY)
    private Publisher publisher;

    @Id
    private String title;

    public Book(Author author, Publisher publisher, String title) {
        this.author = author;
        this.publisher = publisher;
        this.title = title;
    }

    private Book() {
    }

    //Getters and setters are omitted for brevity


    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        Book book = (Book) o;
        return Objects.equals( author, book.author ) &&
                Objects.equals( publisher, book.publisher ) &&
                Objects.equals( title, book.title );
    }

    @Override
    public int hashCode() {
        return Objects.hash( author, publisher, title );
    }
}

@Entity(name = "Author")
public static class Author implements Serializable {

    @Id
    private String name;

    //Getters and setters are omitted for brevity

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        Author author = (Author) o;
        return Objects.equals( name, author.name );
    }

    @Override
    public int hashCode() {
        return Objects.hash( name );
    }
}

@Entity(name = "Publisher")
public static class Publisher implements Serializable {

    @Id
    private String name;

    //Getters and setters are omitted for brevity

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        Publisher publisher = (Publisher) o;
        return Objects.equals( name, publisher.name );
    }

    @Override
    public int hashCode() {
        return Objects.hash( name );
    }
}

Although the mapping is much simpler than using an @EmbeddedId or an @IdClass, there’s no separation between the entity instance and the actual identifier. To query this entity, an instance of the entity itself must be supplied to the persistence context.

Example 150. Fetchcing with composite identifiers
Book book = entityManager.find( Book.class, new Book(
    author,
    publisher,
    "High-Performance Java Persistence"
) );

assertEquals( "Vlad Mihalcea", book.getAuthor().getName() );

2.6.6. Generated identifier values

For discussion of generated values for non-identifier attributes, see Generated properties.

Hibernate supports identifier value generation across a number of different types. Remember that JPA portably defines identifier value generation just for integer types.

Identifier value generation is indicates using the javax.persistence.GeneratedValue annotation. The most important piece of information here is the specified javax.persistence.GenerationType which indicates how values will be generated.

The discussions below assume that the application is using Hibernate’s "new generator mappings" as indicated by the hibernate.id.new_generator_mappings setting or MetadataBuilder.enableNewIdentifierGeneratorSupport method during bootstrap. Starting with Hibernate 5, this is set to true by default. If applications set this to false the resolutions discussed here will be very different. The rest of the discussion here assumes this setting is enabled (true).

AUTO (the default)

Indicates that the persistence provider (Hibernate) should choose an appropriate generation strategy. See Interpreting AUTO.

IDENTITY

Indicates that database IDENTITY columns will be used for primary key value generation. See Using IDENTITY columns.

SEQUENCE

Indicates that database sequence should be used for obtaining primary key values. See Using sequences.

TABLE

Indicates that a database table should be used for obtaining primary key values. See Using table identifier generator.

2.6.7. Interpreting AUTO

How a persistence provider interprets the AUTO generation type is left up to the provider.

The default behavior is to look at the java type of the identifier attribute.

If the identifier type is UUID, Hibernate is going to use an UUID identifier.

If the identifier type is numerical (e.g. Long, Integer), then Hibernate is going to use the IdGeneratorStrategyInterpreter to resolve the identifier generator strategy. The IdGeneratorStrategyInterpreter has two implementations:

FallbackInterpreter

This is the default strategy since Hibernate 5.0. For older versions, this strategy is enabled through the hibernate.id.new_generator_mappings configuration property . When using this strategy, AUTO always resolves to SequenceStyleGenerator. If the underlying database supports sequences, then a SEQUENCE generator is used. Otherwise, a TABLE generator is going to be used instead.

LegacyFallbackInterpreter

This is a legacy mechanism that was used by Hibernate prior to version 5.0 or when the hibernate.id.new_generator_mappings configuration property is false. The legacy strategy maps AUTO to the native generator strategy which uses the Dialect#getNativeIdentifierGeneratorStrategy to resolve the actual identifier generator (e.g. identity or sequence).

2.6.8. Using sequences

For implementing database sequence-based identifier value generation Hibernate makes use of its org.hibernate.id.enhanced.SequenceStyleGenerator id generator. It is important to note that SequenceStyleGenerator is capable of working against databases that do not support sequences by switching to a table as the underlying backing. This gives Hibernate a huge degree of portability across databases while still maintaining consistent id generation behavior (versus say choosing between SEQUENCE and IDENTITY). This backing storage is completely transparent to the user.

The preferred (and portable) way to configure this generator is using the JPA-defined javax.persistence.SequenceGenerator annotation.

The simplest form is to simply request sequence generation; Hibernate will use a single, implicitly-named sequence (hibernate_sequence) for all such unnamed definitions.

Example 151. Unnamed sequence
@Entity(name = "Product")
public static class Product {

    @Id
    @GeneratedValue(
        strategy = GenerationType.SEQUENCE
    )
    private Long id;

    @Column(name = "product_name")
    private String name;

    //Getters and setters are omitted for brevity

}

Using javax.persistence.SequenceGenerator, you can specify a specific database sequence name.

Example 152. Named sequence
@Entity(name = "Product")
public static class Product {

    @Id
    @GeneratedValue(
        strategy = GenerationType.SEQUENCE,
        generator = "sequence-generator"
    )
    @SequenceGenerator(
        name = "sequence-generator",
        sequenceName = "product_sequence"
    )
    private Long id;

    @Column(name = "product_name")
    private String name;

    //Getters and setters are omitted for brevity

}

The javax.persistence.SequenceGenerator annotataion allows you to specify additional configurations as well.

Example 153. Configured sequence
@Entity(name = "Product")
public static class Product {

    @Id
    @GeneratedValue(
        strategy = GenerationType.SEQUENCE,
        generator = "sequence-generator"
    )
    @SequenceGenerator(
        name = "sequence-generator",
        sequenceName = "product_sequence",
        allocationSize = 5
    )
    private Long id;

    @Column(name = "product_name")
    private String name;

    //Getters and setters are omitted for brevity

}

2.6.9. Using IDENTITY columns

For implementing identifier value generation based on IDENTITY columns, Hibernate makes use of its org.hibernate.id.IdentityGenerator id generator which expects the identifier to generated by INSERT into the table. IdentityGenerator understands 3 different ways that the INSERT-generated value might be retrieved:

  • If Hibernate believes the JDBC environment supports java.sql.Statement#getGeneratedKeys, then that approach will be used for extracting the IDENTITY generated keys.

  • Otherwise, if Dialect#supportsInsertSelectIdentity reports true, Hibernate will use the Dialect specific INSERT+SELECT statement syntax.

  • Otherwise, Hibernate will expect that the database supports some form of asking for the most recently inserted IDENTITY value via a separate SQL command as indicated by Dialect#getIdentitySelectString.

It is important to realize that this imposes a runtime behavior where the entity row must be physically inserted prior to the identifier value being known. This can mess up extended persistence contexts (conversations). Because of the runtime imposition/inconsistency Hibernate suggest other forms of identifier value generation be used.

There is yet another important runtime impact of choosing IDENTITY generation: Hibernate will not be able to JDBC batching for inserts of the entities that use IDENTITY generation. The importance of this depends on the application specific use cases. If the application is not usually creating many new instances of a given type of entity that uses IDENTITY generation, then this is not an important impact since batching would not have been helpful anyway.

2.6.10. Using table identifier generator

Hibernate achieves table-based identifier generation based on its org.hibernate.id.enhanced.TableGenerator which defines a table capable of holding multiple named value segments for any number of entities.

The basic idea is that a given table-generator table (hibernate_sequences for example) can hold multiple segments of identifier generation values.

Example 154. Unnamed table generator
@Entity(name = "Product")
public static class Product {

    @Id
    @GeneratedValue(
        strategy = GenerationType.TABLE
    )
    private Long id;

    @Column(name = "product_name")
    private String name;

    //Getters and setters are omitted for brevity

}
create table hibernate_sequences (
    sequence_name varchar2(255 char) not null,
    next_val number(19,0),
    primary key (sequence_name)
)

If no table name is given Hibernate assumes an implicit name of hibernate_sequences.

Additionally, because no javax.persistence.TableGenerator#pkColumnValue is specified, Hibernate will use the default segment (sequence_name='default') from the hibernate_sequences table.

However, you can configure the table identifier generator using the @TableGenerator annotation.

Example 155. Configured table generator
@Entity(name = "Product")
public static class Product {

    @Id
    @GeneratedValue(
        strategy = GenerationType.TABLE,
        generator = "table-generator"
    )
    @TableGenerator(
        name =  "table-generator",
        table = "table_identifier",
        pkColumnName = "table_name",
        valueColumnName = "product_id",
        allocationSize = 5
    )
    private Long id;

    @Column(name = "product_name")
    private String name;

    //Getters and setters are omitted for brevity

}
create table table_identifier (
    table_name varchar2(255 char) not null,
    product_id number(19,0),
    primary key (table_name)
)

Now, when inserting 3 Product entities, Hibernate generates the following statements:

] .Configured table generator persist example

for ( long i = 1; i <= 3; i++ ) {
    Product product = new Product();
    product.setName( String.format( "Product %d", i ) );
    entityManager.persist( product );
}
select
    tbl.product_id
from
    table_identifier tbl
where
    tbl.table_name = ?
for update

-- binding parameter [1] - [Product]

insert
into
    table_identifier
    (table_name, product_id)
values
    (?, ?)

-- binding parameter [1] - [Product]
-- binding parameter [2] - [1]

update
    table_identifier
set
    product_id= ?
where
    product_id= ?
    and table_name= ?

-- binding parameter [1] - [6]
-- binding parameter [2] - [1]

select
    tbl.product_id
from
    table_identifier tbl
where
    tbl.table_name= ? for update

update
    table_identifier
set
    product_id= ?
where
    product_id= ?
    and table_name= ?

-- binding parameter [1] - [11]
-- binding parameter [2] - [6]

insert
into
    Product
    (product_name, id)
values
    (?, ?)

-- binding parameter [1] as [VARCHAR] - [Product 1]
-- binding parameter [2] as [BIGINT]  - [1]

insert
into
    Product
    (product_name, id)
values
    (?, ?)

-- binding parameter [1] as [VARCHAR] - [Product 2]
-- binding parameter [2] as [BIGINT]  - [2]

insert
into
    Product
    (product_name, id)
values
    (?, ?)

-- binding parameter [1] as [VARCHAR] - [Product 3]
-- binding parameter [2] as [BIGINT]  - [3]

2.6.11. Using UUID generation

As mentioned above, Hibernate supports UUID identifier value generation. This is supported through its org.hibernate.id.UUIDGenerator id generator.

UUIDGenerator supports pluggable strategies for exactly how the UUID is generated. These strategies are defined by the org.hibernate.id.UUIDGenerationStrategy contract. The default strategy is a version 4 (random) strategy according to IETF RFC 4122. Hibernate does ship with an alternative strategy which is a RFC 4122 version 1 (time-based) strategy (using ip address rather than mac address).

Example 156. Implicitly using the random UUID strategy
@Entity(name = "Book")
public static class Book {

    @Id
    @GeneratedValue
    private UUID id;

    private String title;

    private String author;

    //Getters and setters are omitted for brevity
}

To specify an alternative generation strategy, we’d have to define some configuration via @GenericGenerator. Here we choose the RFC 4122 version 1 compliant strategy named org.hibernate.id.uuid.CustomVersionOneStrategy.

Example 157. Implicitly using the random UUID strategy
@Entity(name = "Book")
public static class Book {

    @Id
    @GeneratedValue( generator = "custom-uuid" )
    @GenericGenerator(
        name = "custom-uuid",
        strategy = "org.hibernate.id.UUIDGenerator",
        parameters = {
            @Parameter(
                name = "uuid_gen_strategy_class",
                value = "org.hibernate.id.uuid.CustomVersionOneStrategy"
            )
        }
    )
    private UUID id;

    private String title;

    private String author;

    //Getters and setters are omitted for brevity
}

2.6.12. Optimizers

Most of the Hibernate generators that separately obtain identifier values from database structures support the use of pluggable optimizers. Optimizers help manage the number of times Hibernate has to talk to the database in order to generate identifier values. For example, with no optimizer applied to a sequence-generator, every time the application asked Hibernate to generate an identifier it would need to grab the next sequence value from the database. But if we can minimize the number of times we need to communicate with the database here, the application will be able to perform better. Which is, in fact, the role of these optimizers.

none

No optimization is performed. We communicate with the database each and every time an identifier value is needed from the generator.

pooled-lo

The pooled-lo optimizer works on the principle that the increment-value is encoded into the database table/sequence structure. In sequence-terms this means that the sequence is defined with a greater-that-1 increment size.

For example, consider a brand new sequence defined as create sequence m_sequence start with 1 increment by 20. This sequence essentially defines a "pool" of 20 usable id values each and every time we ask it for its next-value. The pooled-lo optimizer interprets the next-value as the low end of that pool.

So when we first ask it for next-value, we’d get 1. We then assume that the valid pool would be the values from 1-20 inclusive.

The next call to the sequence would result in 21, which would define 21-40 as the valid range. And so on. The "lo" part of the name indicates that the value from the database table/sequence is interpreted as the pool lo(w) end.

pooled

Just like pooled-lo, except that here the value from the table/sequence is interpreted as the high end of the value pool.

hilo; legacy-hilo

Define a custom algorithm for generating pools of values based on a single value from a table or sequence.

These optimizers are not recommended for use. They are maintained (and mentioned) here simply for use by legacy applications that used these strategies previously.

Applications can also implement and use their own optimizer strategies, as defined by the org.hibernate.id.enhanced.Optimizer contract.

2.6.13. Using @GenericGenerator

@GenericGenerator allows integration of any Hibernate org.hibernate.id.IdentifierGenerator implementation, including any of the specific ones discussed here and any custom ones.

To make use of the pooled or pooled-lo optimizers, the entity mapping must use the @GenericGenerator annotation:

Example 158. Pooled-lo optimizer mapping using @GenericGenerator mapping
@Entity(name = "Product")
public static class Product {

    @Id
    @GeneratedValue(
        strategy = GenerationType.SEQUENCE,
        generator = "product_generator"
    )
    @GenericGenerator(
        name = "product_generator",
        strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
        parameters = {
            @Parameter(name = "sequence_name", value = "product_sequence"),
            @Parameter(name = "initial_value", value = "1"),
            @Parameter(name = "increment_size", value = "3"),
            @Parameter(name = "optimizer", value = "pooled-lo")
        }
    )
    private Long id;

    @Column(name = "p_name")
    private String name;

    @Column(name = "p_number")
    private String number;

    //Getters and setters are omitted for brevity

}

Now, when saving 5 Person entities and flushing the Persistence Context after every 3 entities:

Example 159. Pooled-lo optimizer mapping using @GenericGenerator mapping
for ( long i = 1; i <= 5; i++ ) {
    if(i % 3 == 0) {
        entityManager.flush();
    }
    Product product = new Product();
    product.setName( String.format( "Product %d", i ) );
    product.setNumber( String.format( "P_100_%d", i ) );
    entityManager.persist( product );
}
CALL NEXT VALUE FOR product_sequence

INSERT INTO Product (p_name, p_number, id)
VALUES (?, ?, ?)

-- binding parameter [1] as [VARCHAR] - [Product 1]
-- binding parameter [2] as [VARCHAR] - [P_100_1]
-- binding parameter [3] as [BIGINT]  - [1]

INSERT INTO Product (p_name, p_number, id)
VALUES (?, ?, ?)

-- binding parameter [1] as [VARCHAR] - [Product 2]
-- binding parameter [2] as [VARCHAR] - [P_100_2]
-- binding parameter [3] as [BIGINT]  - [2]

CALL NEXT VALUE FOR product_sequence

INSERT INTO Product (p_name, p_number, id)
VALUES (?, ?, ?)

-- binding parameter [1] as [VARCHAR] - [Product 3]
-- binding parameter [2] as [VARCHAR] - [P_100_3]
-- binding parameter [3] as [BIGINT]  - [3]

INSERT INTO Product (p_name, p_number, id)
VALUES (?, ?, ?)

-- binding parameter [1] as [VARCHAR] - [Product 4]
-- binding parameter [2] as [VARCHAR] - [P_100_4]
-- binding parameter [3] as [BIGINT]  - [4]

INSERT INTO Product (p_name, p_number, id)
VALUES (?, ?, ?)

-- binding parameter [1] as [VARCHAR] - [Product 5]
-- binding parameter [2] as [VARCHAR] - [P_100_5]
-- binding parameter [3] as [BIGINT]  - [5]

As you can see from the list of generated SQL statements, you can insert 3 entities for one database sequence call. This way, the pooled and the pooled-lo optimizers allow you to reduce the number of database roundtrips, therefore reducing the overall transaction response time.

2.6.14. Derived Identifiers

JPA 2.0 added support for derived identifiers which allow an entity to borrow the identifier from a many-to-one or one-to-one association.

Example 160. Derived identifier with @MapsId
@Entity(name = "Person")
public static class Person  {

    @Id
    private Long id;

    @NaturalId
    private String registrationNumber;

    public Person() {}

    public Person(String registrationNumber) {
        this.registrationNumber = registrationNumber;
    }

    //Getters and setters are omitted for brevity
}

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

    @Id
    private Long id;

    private String nickName;

    @OneToOne
    @MapsId
    private Person person;

    //Getters and setters are omitted for brevity
}

In the example above, the PersonDetails entity uses the id column for both the entity identifier and for the one-to-one association to the Person entity. The value of the PersonDetails entity identifier is "derived" from the identifier of its parent Person entity.

Example 161. Derived identifier with @MapsId persist example
doInJPA( this::entityManagerFactory, entityManager -> {
    Person person = new Person( "ABC-123" );
    person.setId( 1L );
    entityManager.persist( person );

    PersonDetails personDetails = new PersonDetails();
    personDetails.setNickName( "John Doe" );
    personDetails.setPerson( person );

    entityManager.persist( personDetails );
} );

doInJPA( this::entityManagerFactory, entityManager -> {
    PersonDetails personDetails = entityManager.find( PersonDetails.class, 1L );

    assertEquals("John Doe", personDetails.getNickName());
} );

The @MapsId annotation can also reference columns from an @EmbeddedId identifier as well.

The previous example can also be mapped using @PrimaryKeyJoinColumn.

Example 162. Derived identifier @PrimaryKeyJoinColumn
@Entity(name = "Person")
public static class Person  {

    @Id
    private Long id;

    @NaturalId
    private String registrationNumber;

    public Person() {}

    public Person(String registrationNumber) {
        this.registrationNumber = registrationNumber;
    }

    //Getters and setters are omitted for brevity
}

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

    @Id
    private Long id;

    private String nickName;

    @OneToOne
    @PrimaryKeyJoinColumn
    private Person person;

    public void setPerson(Person person) {
        this.person = person;
        this.id = person.getId();
    }

    //Other getters and setters are omitted for brevity
}

Unlike @MapsId, the application developer is responsible for ensuring that the identifier and the many-to-one (or one-to-one) association are in sync as you can see in the PersonDetails#setPerson method.

2.6.15. @RowId

If you annotate a given entity with the @RowId annotation and the underlying database supports fetching a record by ROWID (e.g. Oracle), then Hibernate can use the ROWID pseudo-column for CRUD operations.

Example 163. @RowId entity maapping
@Entity(name = "Product")
@RowId("ROWID")
public static class Product {

    @Id
    private Long id;

    @Column(name = "`name`")
    private String name;

    @Column(name = "`number`")
    private String number;

    //Getters and setters are omitted for brevity

}

Now, when fetching an entity and modifying it, Hibernate uses the ROWID pseudo-column for the UPDATE SQL statement.

Example 164. @RowId example
Product product = entityManager.find( Product.class, 1L );

product.setName( "Smart phone" );
SELECT
    p.id as id1_0_0_,
    p."name" as name2_0_0_,
    p."number" as number3_0_0_,
    p.ROWID as rowid_0_
FROM
    Product p
WHERE
    p.id = ?

-- binding parameter [1] as [BIGINT] - [1]

-- extracted value ([name2_0_0_] : [VARCHAR]) - [Mobile phone]
-- extracted value ([number3_0_0_] : [VARCHAR]) - [123-456-7890]
-- extracted ROWID value: AAAwkBAAEAAACP3AAA

UPDATE
    Product
SET
    "name" = ?,
    "number" = ?
WHERE
    ROWID = ?

-- binding parameter [1] as [VARCHAR] - [Smart phone]
-- binding parameter [2] as [VARCHAR] - [123-456-7890]
-- binding parameter [3] as ROWID     - [AAAwkBAAEAAACP3AAA]

2.7. Associations

Associations describe how two or more entities form a relationship based on a database joining semantics.

2.7.1. @ManyToOne

@ManyToOne is the most common association, having a direct equivalent in the relational database as well (e.g. foreign key), and so it establishes a relationship between a child entity and a parent.

Example 165. @ManyToOne association
@Entity(name = "Person")
public static class Person {

    @Id
    @GeneratedValue
    private Long id;

    public Person() {
    }
}

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

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "`number`")
    private String number;

    @ManyToOne
    @JoinColumn(name = "person_id",
            foreignKey = @ForeignKey(name = "PERSON_ID_FK")
    )
    private Person person;

    public Phone() {
    }

    public Phone(String number) {
        this.number = number;
    }

    public Long getId() {
        return id;
    }

    public String getNumber() {
        return number;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }
}
CREATE TABLE Person (
    id BIGINT NOT NULL ,
    PRIMARY KEY ( id )
)

CREATE TABLE Phone (
    id BIGINT NOT NULL ,
    number VARCHAR(255) ,
    person_id BIGINT ,
    PRIMARY KEY ( id )
 )

ALTER TABLE Phone
ADD CONSTRAINT PERSON_ID_FK
FOREIGN KEY (person_id) REFERENCES Person

Each entity has a lifecycle of its own. Once the @ManyToOne association is set, Hibernate will set the associated database foreign key column.

Example 166. @ManyToOne association lifecycle
Person person = new Person();
entityManager.persist( person );

Phone phone = new Phone( "123-456-7890" );
phone.setPerson( person );
entityManager.persist( phone );

entityManager.flush();
phone.setPerson( null );
INSERT INTO Person ( id )
VALUES ( 1 )

INSERT INTO Phone ( number, person_id, id )
VALUES ( '123-456-7890', 1, 2 )

UPDATE Phone
SET    number = '123-456-7890',
       person_id = NULL
WHERE  id = 2

2.7.2. @OneToMany

The @OneToMany association links a parent entity with one or more child entities. If the @OneToMany doesn’t have a mirroring @ManyToOne association on the child side, the @OneToMany association is unidirectional. If there is a @ManyToOne association on the child side, the @OneToMany association is bidirectional and the application developer can navigate this relationship from both ends.

Unidirectional @OneToMany

When using a unidirectional @OneToMany association, Hibernate resorts to using a link table between the two joining entities.

Example 167. Unidirectional @OneToMany association
@Entity(name = "Person")
public static class Person {

    @Id
    @GeneratedValue
    private Long id;
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Phone> phones = new ArrayList<>();

    public Person() {
    }

    public List<Phone> getPhones() {
        return phones;
    }
}

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

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "`number`")
    private String number;

    public Phone() {
    }

    public Phone(String number) {
        this.number = number;
    }

    public Long getId() {
        return id;
    }

    public String getNumber() {
        return number;
    }
}
CREATE TABLE Person (
    id BIGINT NOT NULL ,
    PRIMARY KEY ( id )
)

CREATE TABLE Person_Phone (
    Person_id BIGINT NOT NULL ,
    phones_id BIGINT NOT NULL
)

CREATE TABLE Phone (
    id BIGINT NOT NULL ,
    number VARCHAR(255) ,
    PRIMARY KEY ( id )
)

ALTER TABLE Person_Phone
ADD CONSTRAINT UK_9uhc5itwc9h5gcng944pcaslf
UNIQUE (phones_id)

ALTER TABLE Person_Phone
ADD CONSTRAINT FKr38us2n8g5p9rj0b494sd3391
FOREIGN KEY (phones_id) REFERENCES Phone

ALTER TABLE Person_Phone
ADD CONSTRAINT FK2ex4e4p7w1cj310kg2woisjl2
FOREIGN KEY (Person_id) REFERENCES Person

The @OneToMany association is by definition a parent association, even if it’s a unidirectional or a bidirectional one. Only the parent side of an association makes sense to cascade its entity state transitions to children.

Example 168. Cascading @OneToMany association
Person person = new Person();
Phone phone1 = new Phone( "123-456-7890" );
Phone phone2 = new Phone( "321-654-0987" );

person.getPhones().add( phone1 );
person.getPhones().add( phone2 );
entityManager.persist( person );
entityManager.flush();

person.getPhones().remove( phone1 );
INSERT INTO Person
       ( id )
VALUES ( 1 )

INSERT INTO Phone
       ( number, id )
VALUES ( '123 - 456 - 7890', 2 )

INSERT INTO Phone
       ( number, id )
VALUES ( '321 - 654 - 0987', 3 )

INSERT INTO Person_Phone
       ( Person_id, phones_id )
VALUES ( 1, 2 )

INSERT INTO Person_Phone
       ( Person_id, phones_id )
VALUES ( 1, 3 )

DELETE FROM Person_Phone
WHERE  Person_id = 1

INSERT INTO Person_Phone
       ( Person_id, phones_id )
VALUES ( 1, 3 )

DELETE FROM Phone
WHERE  id = 2

When persisting the Person entity, the cascade will propagate the persist operation to the underlying Phone children as well. Upon removing a Phone from the phones collection, the association row is deleted from the link table, and the orphanRemoval attribute will trigger a Phone removal as well.

The unidirectional associations are not very efficient when it comes to removing child entities. In this particular example, upon flushing the persistence context, Hibernate deletes all database child entries and reinserts the ones that are still found in the in-memory persistence context.

On the other hand, a bidirectional @OneToMany association is much more efficient because the child entity controls the association.

Bidirectional @OneToMany

The bidirectional @OneToMany association also requires a @ManyToOne association on the child side. Although the Domain Model exposes two sides to navigate this association, behind the scenes, the relational database has only one foreign key for this relationship.

Every bidirectional association must have one owning side only (the child side), the other one being referred to as the inverse (or the mappedBy) side.

Example 169. @OneToMany association mappedBy the @ManyToOne side
@Entity(name = "Person")
public static class Person {

    @Id
    @GeneratedValue
    private Long id;
    @OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Phone> phones = new ArrayList<>();

    public Person() {
    }

    public Person(Long id) {
        this.id = id;
    }

    public List<Phone> getPhones() {
        return phones;
    }

    public void addPhone(Phone phone) {
        phones.add( phone );
        phone.setPerson( this );
    }

    public void removePhone(Phone phone) {
        phones.remove( phone );
        phone.setPerson( null );
    }
}

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

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    @Column(name = "`number`", unique = true)
    private String number;

    @ManyToOne
    private Person person;

    public Phone() {
    }

    public Phone(String number) {
        this.number = number;
    }

    public Long getId() {
        return id;
    }

    public String getNumber() {
        return number;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        Phone phone = (Phone) o;
        return Objects.equals( number, phone.number );
    }

    @Override
    public int hashCode() {
        return Objects.hash( number );
    }
}
CREATE TABLE Person (
    id BIGINT NOT NULL ,
    PRIMARY KEY ( id )
)

CREATE TABLE Phone (
    id BIGINT NOT NULL ,
    number VARCHAR(255) ,
    person_id BIGINT ,
    PRIMARY KEY ( id )
)

ALTER TABLE Phone
ADD CONSTRAINT UK_l329ab0g4c1t78onljnxmbnp6
UNIQUE (number)

ALTER TABLE Phone
ADD CONSTRAINT FKmw13yfsjypiiq0i1osdkaeqpg
FOREIGN KEY (person_id) REFERENCES Person

Whenever a bidirectional association is formed, the application developer must make sure both sides are in-sync at all times. The addPhone() and removePhone() are utilities methods that synchronize both ends whenever a child element is added or removed.

Because the Phone class has a @NaturalId column (the phone number being unique), the equals() and the hashCode() can make use of this property, and so the removePhone() logic is reduced to the remove() Java Collection method.

Example 170. Bidirectional @OneToMany with an owner @ManyToOne side lifecycle
Person person = new Person();
Phone phone1 = new Phone( "123-456-7890" );
Phone phone2 = new Phone( "321-654-0987" );

person.addPhone( phone1 );
person.addPhone( phone2 );
entityManager.persist( person );
entityManager.flush();

person.removePhone( phone1 );
INSERT INTO Phone
       ( number, person_id, id )
VALUES ( '123-456-7890', NULL, 2 )

INSERT INTO Phone
       ( number, person_id, id )
VALUES ( '321-654-0987', NULL, 3 )

DELETE FROM Phone
WHERE  id = 2

Unlike the unidirectional @OneToMany, the bidirectional association is much more efficient when managing the collection persistence state. Every element removal only requires a single update (in which the foreign key column is set to NULL), and, if the child entity lifecycle is bound to its owning parent so that the child cannot exist without its parent, then we can annotate the association with the orphan-removal attribute and disassociating the child will trigger a delete statement on the actual child table row as well.

2.7.3. @OneToOne

The @OneToOne association can either be unidirectional or bidirectional. A unidirectional association follows the relational database foreign key semantics, the client-side owning the relationship. A bidirectional association features a mappedBy @OneToOne parent side too.

Unidirectional @OneToOne
Example 171. Unidirectional @OneToOne
@Entity(name = "Phone")
public static class Phone {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "`number`")
    private String number;

    @OneToOne
    @JoinColumn(name = "details_id")
    private PhoneDetails details;

    public Phone() {
    }

    public Phone(String number) {
        this.number = number;
    }

    public Long getId() {
        return id;
    }

    public String getNumber() {
        return number;
    }

    public PhoneDetails getDetails() {
        return details;
    }

    public void setDetails(PhoneDetails details) {
        this.details = details;
    }
}

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

    @Id
    @GeneratedValue
    private Long id;

    private String provider;

    private String technology;

    public PhoneDetails() {
    }

    public PhoneDetails(String provider, String technology) {
        this.provider = provider;
        this.technology = technology;
    }

    public String getProvider() {
        return provider;
    }

    public String getTechnology() {
        return technology;
    }

    public void setTechnology(String technology) {
        this.technology = technology;
    }
}
CREATE TABLE Phone (
    id BIGINT NOT NULL ,
    number VARCHAR(255) ,
    details_id BIGINT ,
    PRIMARY KEY ( id )
)

CREATE TABLE PhoneDetails (
    id BIGINT NOT NULL ,
    provider VARCHAR(255) ,
    technology VARCHAR(255) ,
    PRIMARY KEY ( id )
)

ALTER TABLE Phone
ADD CONSTRAINT FKnoj7cj83ppfqbnvqqa5kolub7
FOREIGN KEY (details_id) REFERENCES PhoneDetails

From a relational database point of view, the underlying schema is identical to the unidirectional @ManyToOne association, as the client-side controls the relationship based on the foreign key column.

But then, it’s unusual to consider the Phone as a client-side and the PhoneDetails as the parent-side because the details cannot exist without an actual phone. A much more natural mapping would be if the Phone were the parent-side, therefore pushing the foreign key into the PhoneDetails table. This mapping requires a bidirectional @OneToOne association as you can see in the following example:

Bidirectional @OneToOne
Example 172. Bidirectional @OneToOne
@Entity(name = "Phone")
public static class Phone {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "`number`")
    private String number;

    @OneToOne(mappedBy = "phone", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
    private PhoneDetails details;

    public Phone() {
    }

    public Phone(String number) {
        this.number = number;
    }

    public Long getId() {
        return id;
    }

    public String getNumber() {
        return number;
    }

    public PhoneDetails getDetails() {
        return details;
    }

    public void addDetails(PhoneDetails details) {
        details.setPhone( this );
        this.details = details;
    }

    public void removeDetails() {
        if ( details != null ) {
            details.setPhone( null );
            this.details = null;
        }
    }
}

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

    @Id
    @GeneratedValue
    private Long id;

    private String provider;

    private String technology;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "phone_id")
    private Phone phone;

    public PhoneDetails() {
    }

    public PhoneDetails(String provider, String technology) {
        this.provider = provider;
        this.technology = technology;
    }

    public String getProvider() {
        return provider;
    }

    public String getTechnology() {
        return technology;
    }

    public void setTechnology(String technology) {
        this.technology = technology;
    }

    public Phone getPhone() {
        return phone;
    }

    public void setPhone(Phone phone) {
        this.phone = phone;
    }
}
CREATE TABLE Phone (
    id BIGINT NOT NULL ,
    number VARCHAR(255) ,
    PRIMARY KEY ( id )
)

CREATE TABLE PhoneDetails (
    id BIGINT NOT NULL ,
    provider VARCHAR(255) ,
    technology VARCHAR(255) ,
    phone_id BIGINT ,
    PRIMARY KEY ( id )
)

ALTER TABLE PhoneDetails
ADD CONSTRAINT FKeotuev8ja8v0sdh29dynqj05p
FOREIGN KEY (phone_id) REFERENCES Phone

This time, the PhoneDetails owns the association, and, like any bidirectional association, the parent-side can propagate its lifecycle to the child-side through cascading.

Example 173. Bidirectional @OneToOne lifecycle
Phone phone = new Phone( "123-456-7890" );
PhoneDetails details = new PhoneDetails( "T-Mobile", "GSM" );

phone.addDetails( details );
entityManager.persist( phone );
INSERT INTO Phone ( number, id )
VALUES ( '123 - 456 - 7890', 1 )

INSERT INTO PhoneDetails ( phone_id, provider, technology, id )
VALUES ( 1, 'T - Mobile, GSM', 2 )

When using a bidirectional @OneToOne association, Hibernate enforces the unique constraint upon fetching the child-side. If there are more than one children associated with the same parent, Hibernate will throw a org.hibernate.exception.ConstraintViolationException. Continuing the previous example, when adding another PhoneDetails, Hibernate validates the uniqueness constraint when reloading the Phone object.

Example 174. Bidirectional @OneToOne unique constraint
PhoneDetails otherDetails = new PhoneDetails( "T-Mobile", "CDMA" );
otherDetails.setPhone( phone );
entityManager.persist( otherDetails );
entityManager.flush();
entityManager.clear();

//throws javax.persistence.PersistenceException: org.hibernate.HibernateException: More than one row with the given identifier was found: 1
phone = entityManager.find( Phone.class, phone.getId() );
Bidirectional @OneToOne lazy association

Although you might annotate the parent-side association to be fetched lazily, Hibernate cannot honor this request since it cannot know whether the association is null or not.

The only way to figure out whether there is an associated record on the child side is to fetch the child association using a secondary query. Because this can lead to N+1 query issues, it’s much more efficient to use unidirectional @OneToOne associations with the @MapsId annotation in place.

However, if you really need to use a bidirectional association and want to make sure that this is always going to be fetched lazily, then you need to enable lazy state initialization bytecode enhancement and use the @LazyToOne annotation as well.

Example 175. Bidirectional @OneToOne lazy parent-side association
@Entity(name = "Phone")
public static class Phone {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "`number`")
    private String number;

    @OneToOne(
        mappedBy = "phone",
        cascade = CascadeType.ALL,
        orphanRemoval = true,
        fetch = FetchType.LAZY
    )
    @LazyToOne( LazyToOneOption.NO_PROXY )
    private PhoneDetails details;

    public Phone() {
    }

    public Phone(String number) {
        this.number = number;
    }
    //Getters and setters are omitted for brevity

}

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

    @Id
    @GeneratedValue
    private Long id;

    private String provider;

    private String technology;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "phone_id")
    private Phone phone;

    public PhoneDetails() {
    }

    public PhoneDetails(String provider, String technology) {
        this.provider = provider;
        this.technology = technology;
    }
    //Getters and setters are omitted for brevity

}

For more about how to enable Bytecode enhancement, see the BytecodeEnhancement chapter.

2.7.4. @ManyToMany

The @ManyToMany association requires a link table that joins two entities. Like the @OneToMany association, @ManyToMany can be a either unidirectional or bidirectional.

Unidirectional @ManyToMany
Example 176. Unidirectional @ManyToMany
@Entity(name = "Person")
public static class Person {

    @Id
    @GeneratedValue
    private Long id;

    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    private List<Address> addresses = new ArrayList<>();

    public Person() {
    }

    public List<Address> getAddresses() {
        return addresses;
    }
}

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

    @Id
    @GeneratedValue
    private Long id;

    private String street;

    @Column(name = "`number`")
    private String number;

    public Address() {
    }

    public Address(String street, String number) {
        this.street = street;
        this.number = number;
    }

    public Long getId() {
        return id;
    }

    public String getStreet() {
        return street;
    }

    public String getNumber() {
        return number;
    }
}
CREATE TABLE Address (
    id BIGINT NOT NULL ,
    number VARCHAR(255) ,
    street VARCHAR(255) ,
    PRIMARY KEY ( id )
)

CREATE TABLE Person (
    id BIGINT NOT NULL ,
    PRIMARY KEY ( id )
)

CREATE TABLE Person_Address (
    Person_id BIGINT NOT NULL ,
    addresses_id BIGINT NOT NULL
)

ALTER TABLE Person_Address
ADD CONSTRAINT FKm7j0bnabh2yr0pe99il1d066u
FOREIGN KEY (addresses_id) REFERENCES Address

ALTER TABLE Person_Address
ADD CONSTRAINT FKba7rc9qe2vh44u93u0p2auwti
FOREIGN KEY (Person_id) REFERENCES Person

Just like with unidirectional @OneToMany associations, the link table is controlled by the owning side.

When an entity is removed from the @ManyToMany collection, Hibernate simply deletes the joining record in the link table. Unfortunately, this operation requires removing all entries associated with a given parent and recreating the ones that are listed in the current running persistent context.

Example 177. Unidirectional @ManyToMany lifecycle
Person person1 = new Person();
Person person2 = new Person();

Address address1 = new Address( "12th Avenue", "12A" );
Address address2 = new Address( "18th Avenue", "18B" );

person1.getAddresses().add( address1 );
person1.getAddresses().add( address2 );

person2.getAddresses().add( address1 );

entityManager.persist( person1 );
entityManager.persist( person2 );

entityManager.flush();

person1.getAddresses().remove( address1 );
INSERT INTO Person ( id )
VALUES ( 1 )

INSERT INTO Address ( number, street, id )
VALUES ( '12A', '12th Avenue', 2 )

INSERT INTO Address ( number, street, id )
VALUES ( '18B', '18th Avenue', 3 )

INSERT INTO Person ( id )
VALUES ( 4 )

INSERT INTO Person_Address ( Person_id, addresses_id )
VALUES ( 1, 2 )
INSERT INTO Person_Address ( Person_id, addresses_id )
VALUES ( 1, 3 )
INSERT INTO Person_Address ( Person_id, addresses_id )
VALUES ( 4, 2 )

DELETE FROM Person_Address
WHERE  Person_id = 1

INSERT INTO Person_Address ( Person_id, addresses_id )
VALUES ( 1, 3 )

For @ManyToMany associations, the REMOVE entity state transition doesn’t make sense to be cascaded because it will propagate beyond the link table. Since the other side might be referenced by other entities on the parent-side, the automatic removal might end up in a ConstraintViolationException.

For example, if @ManyToMany(cascade = CascadeType.ALL) was defined and the first person would be deleted, Hibernate would throw an exception because another person is still associated with the address that’s being deleted.

Person person1 = entityManager.find(Person.class, personId);
entityManager.remove(person1);

Caused by: javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
Caused by: java.sql.SQLIntegrityConstraintViolationException: integrity constraint violation: foreign key no action; FKM7J0BNABH2YR0PE99IL1D066U table: PERSON_ADDRESS

By simply removing the parent-side, Hibernate can safely remove the associated link records as you can see in the following example:

Example 178. Unidirectional @ManyToMany entity removal
Person person1 = entityManager.find( Person.class, personId );
entityManager.remove( person1 );
DELETE FROM Person_Address
WHERE  Person_id = 1

DELETE FROM Person
WHERE  id = 1
Bidirectional @ManyToMany

A bidirectional @ManyToMany association has an owning and a mappedBy side. To preserve synchronicity between both sides, it’s good practice to provide helper methods for adding or removing child entities.

Example 179. Bidirectional @ManyToMany
@Entity(name = "Person")
public static class Person {

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    private String registrationNumber;
    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    private List<Address> addresses = new ArrayList<>();

    public Person() {
    }

    public Person(String registrationNumber) {
        this.registrationNumber = registrationNumber;
    }

    public List<Address> getAddresses() {
        return addresses;
    }

    public void addAddress(Address address) {
        addresses.add( address );
        address.getOwners().add( this );
    }

    public void removeAddress(Address address) {
        addresses.remove( address );
        address.getOwners().remove( this );
    }

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        Person person = (Person) o;
        return Objects.equals( registrationNumber, person.registrationNumber );
    }

    @Override
    public int hashCode() {
        return Objects.hash( registrationNumber );
    }
}

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

    @Id
    @GeneratedValue
    private Long id;

    private String street;

    @Column(name = "`number`")
    private String number;

    private String postalCode;

    @ManyToMany(mappedBy = "addresses")
    private List<Person> owners = new ArrayList<>();

    public Address() {
    }

    public Address(String street, String number, String postalCode) {
        this.street = street;
        this.number = number;
        this.postalCode = postalCode;
    }

    public Long getId() {
        return id;
    }

    public String getStreet() {
        return street;
    }

    public String getNumber() {
        return number;
    }

    public String getPostalCode() {
        return postalCode;
    }

    public List<Person> getOwners() {
        return owners;
    }

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        Address address = (Address) o;
        return Objects.equals( street, address.street ) &&
                Objects.equals( number, address.number ) &&
                Objects.equals( postalCode, address.postalCode );
    }

    @Override
    public int hashCode() {
        return Objects.hash( street, number, postalCode );
    }
}
CREATE TABLE Address (
    id BIGINT NOT NULL ,
    number VARCHAR(255) ,
    postalCode VARCHAR(255) ,
    street VARCHAR(255) ,
    PRIMARY KEY ( id )
)

CREATE TABLE Person (
    id BIGINT NOT NULL ,
    registrationNumber VARCHAR(255) ,
    PRIMARY KEY ( id )
)

CREATE TABLE Person_Address (
    owners_id BIGINT NOT NULL ,
    addresses_id BIGINT NOT NULL
)

ALTER TABLE Person
ADD CONSTRAINT UK_23enodonj49jm8uwec4i7y37f
UNIQUE (registrationNumber)

ALTER TABLE Person_Address
ADD CONSTRAINT FKm7j0bnabh2yr0pe99il1d066u
FOREIGN KEY (addresses_id) REFERENCES Address

ALTER TABLE Person_Address
ADD CONSTRAINT FKbn86l24gmxdv2vmekayqcsgup
FOREIGN KEY (owners_id) REFERENCES Person

With the helper methods in place, the synchronicity management can be simplified, as you can see in the following example:

Example 180. Bidirectional @ManyToMany lifecycle
Person person1 = new Person( "ABC-123" );
Person person2 = new Person( "DEF-456" );

Address address1 = new Address( "12th Avenue", "12A", "4005A" );
Address address2 = new Address( "18th Avenue", "18B", "4007B" );

person1.addAddress( address1 );
person1.addAddress( address2 );

person2.addAddress( address1 );

entityManager.persist( person1 );
entityManager.persist( person2 );

entityManager.flush();

person1.removeAddress( address1 );
INSERT INTO Person ( registrationNumber, id )
VALUES ( 'ABC-123', 1 )

INSERT INTO Address ( number, postalCode, street, id )
VALUES ( '12A', '4005A', '12th Avenue', 2 )

INSERT INTO Address ( number, postalCode, street, id )
VALUES ( '18B', '4007B', '18th Avenue', 3 )

INSERT INTO Person ( registrationNumber, id )
VALUES ( 'DEF-456', 4 )

INSERT INTO Person_Address ( owners_id, addresses_id )
VALUES ( 1, 2 )

INSERT INTO Person_Address ( owners_id, addresses_id )
VALUES ( 1, 3 )

INSERT INTO Person_Address ( owners_id, addresses_id )
VALUES ( 4, 2 )

DELETE FROM Person_Address
WHERE  owners_id = 1

INSERT INTO Person_Address ( owners_id, addresses_id )
VALUES ( 1, 3 )

If a bidirectional @OneToMany association performs better when removing or changing the order of child elements, the @ManyToMany relationship cannot benefit from such an optimization because the foreign key side is not in control. To overcome this limitation, the link table must be directly exposed and the @ManyToMany association split into two bidirectional @OneToMany relationships.

To most natural @ManyToMany association follows the same logic employed by the database schema, and the link table has an associated entity which controls the relationship for both sides that need to be joined.

Both the Person and the Address have a mappedBy @OneToMany side, while the PersonAddress owns the person and the address @ManyToOne associations. Because this mapping is formed out of two bidirectional associations, the helper methods are even more relevant.

The aforementioned example uses a Hibernate specific mapping for the link entity since JPA doesn’t allow building a composite identifier out of multiple @ManyToOne associations. For more details, see the Composite identifiers - associations section.

The entity state transitions are better managed than in the previous bidirectional @ManyToMany case.

There is only one delete statement executed because, this time, the association is controlled by the @ManyToOne side which only has to monitor the state of the underlying foreign key relationship to trigger the right DML statement.

2.7.5. @NotFound association mapping

When dealing with associations which are not enforced by a Foreign Key, it’s possible to bump into inconsistencies if the child record cannot reference a parent entity.

By default, Hibernate will complain whenever a child association references a non-existing parent record. However, you can configure this behavior so that Hibernate can ignore such an Exception and simply assign null as a parent object referenced.

To ignore non-existing parent entity references, even though not really recommended, it’s possible to use the annotation org.hibernate.annotation.NotFound annotation with a value of org.hibernate.annotations.NotFoundAction.IGNORE.

Considering the following City and Person entity mappings:

Example 183. @NotFound mapping example
@Entity
@Table( name = "Person" )
public static class Person {

    @Id
    private Long id;

    private String name;

    private String cityName;

    @ManyToOne( fetch = FetchType.LAZY )
    @NotFound ( action = NotFoundAction.IGNORE )
    @JoinColumn(
        name = "cityName",
        referencedColumnName = "name",
        insertable = false,
        updatable = false
    )
    private City city;

    //Getters and setters are omitted for brevity

}

@Entity
@Table( name = "City" )
public static class City implements Serializable {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    //Getters and setters are omitted for brevity

}

If we have the following entities in our database:

Example 184. @NotFound mapping example
City _NewYork = new City();
_NewYork.setName( "New York" );
entityManager.persist( _NewYork );

Person person = new Person();
person.setId( 1L );
person.setName( "John Doe" );
person.setCityName( "New York" );
entityManager.persist( person );

When loading the Person entity, Hibernate is able to locate the associated City parent entity:

Example 185. @NotFound find existing entity example
Person person = entityManager.find( Person.class, 1L );
assertEquals( "New York", person.getCity().getName() );

However, if we change the cityName attribute to a non-existing city:

Example 186. @NotFound change to non-existing City example
person.setCityName( "Atlantis" );

Hibernate is not going to throw any exception, and it will assign a value of null for the non-existing City entity reference:

Example 187. @NotFound find non-existing City example
Person person = entityManager.find( Person.class, 1L );

assertEquals( "Atlantis", person.getCityName() );
assertNull( null, person.getCity() );

2.8. Collections

Naturally Hibernate also allows to persist collections. These persistent collections can contain almost any other Hibernate type, including: basic types, custom types, components and references to other entities. In this context, the distinction between value and reference semantics is very important. An object in a collection might be handled with value semantics (its life cycle being fully depends on the collection owner), or it might be a reference to another entity with its own life cycle. In the latter case, only the link between the two objects is considered to be a state held by the collection.

The owner of the collection is always an entity, even if the collection is defined by an embeddable type. Collections form one/many-to-many associations between types so there can be:

  • value type collections

  • embeddable type collections

  • entity collections

Hibernate uses its own collection implementations which are enriched with lazy-loading, caching or state change detection semantics. For this reason, persistent collections must be declared as an interface type. The actual interface might be java.util.Collection, java.util.List, java.util.Set, java.util.Map, java.util.SortedSet, java.util.SortedMap or even other object types (meaning you will have to write an implementation of org.hibernate.usertype.UserCollectionType).

As the following example demonstrates, it’s important to use the interface type and not the collection implementation, as declared in the entity mapping.

Example 188. Hibernate uses its own collection implementations
@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;

    @ElementCollection
    private List<String> phones = new ArrayList<>();

    public List<String> getPhones() {
        return phones;
    }
}

Person person = entityManager.find( Person.class, 1L );
//Throws java.lang.ClassCastException: org.hibernate.collection.internal.PersistentBag cannot be cast to java.util.ArrayList
ArrayList<String> phones = (ArrayList<String>) person.getPhones();

It is important that collections be defined using the appropriate Java Collections Framework interface rather than a specific implementation. From a theoretical perspective, this just follows good design principles. From a practical perspective, Hibernate (like other persistence providers) will use their own collection implementations which conform to the Java Collections Framework interfaces.

The persistent collections injected by Hibernate behave like ArrayList, HashSet, TreeSet, HashMap or TreeMap, depending on the interface type.

2.8.1. Collections as a value type

Value and embeddable type collections have a similar behavior as simple value types because they are automatically persisted when referenced by a persistent object and automatically deleted when unreferenced. If a collection is passed from one persistent object to another, its elements might be moved from one table to another.

Two entities cannot share a reference to the same collection instance. Collection-valued properties do not support null value semantics because Hibernate does not distinguish between a null collection reference and an empty collection.

2.8.2. Collections of value types

Collections of value type include basic and embeddable types. Collections cannot be nested, and, when used in collections, embeddable types are not allowed to define other collections.

For collections of value types, JPA 2.0 defines the @ElementCollection annotation. The lifecycle of the value-type collection is entirely controlled by its owning entity.

Considering the previous example mapping, when clearing the phone collection, Hibernate deletes all the associated phones. When adding a new element to the value type collection, Hibernate issues a new insert statement.

Example 189. Value type collection lifecycle
person.getPhones().clear();
person.getPhones().add( "123-456-7890" );
person.getPhones().add( "456-000-1234" );
DELETE FROM Person_phones WHERE   Person_id = 1

INSERT INTO Person_phones ( Person_id, phones )
VALUES ( 1, '123-456-7890' )

INSERT INTO Person_phones  (Person_id, phones)
VALUES  ( 1, '456-000-1234' )

If removing all elements or adding new ones is rather straightforward, removing a certain entry actually requires reconstructing the whole collection from scratch.

Example 190. Removing collection elements
person.getPhones().remove( 0 );
DELETE FROM Person_phones WHERE Person_id = 1

INSERT INTO Person_phones ( Person_id, phones )
VALUES ( 1, '456-000-1234' )

Depending on the number of elements, this behavior might not be efficient, if many elements need to be deleted and reinserted back into the database table. A workaround is to use an @OrderColumn, which, although not as efficient as when using the actual link table primary key, might improve the efficiency of the remove operations.

Example 191. Removing collection elements using the order column
@ElementCollection
@OrderColumn(name = "order_id")
private List<String> phones = new ArrayList<>();

person.getPhones().remove( 0 );
DELETE FROM Person_phones
WHERE  Person_id = 1
       AND order_id = 1

UPDATE Person_phones
SET    phones = '456-000-1234'
WHERE  Person_id = 1
       AND order_id = 0

The @OrderColumn column works best when removing from the tail of the collection, as it only requires a single delete statement. Removing from the head or the middle of the collection requires deleting the extra elements and updating the remaining ones to preserve element order.

Embeddable type collections behave the same way as value type collections. Adding embeddables to the collection triggers the associated insert statements and removing elements from the collection will generate delete statements.

Example 192. Embeddable type collections
@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;

    @ElementCollection
    private List<Phone> phones = new ArrayList<>();

    public List<Phone> getPhones() {
        return phones;
    }
}

@Embeddable
public static class Phone {

    private String type;

    @Column(name = "`number`")
    private String number;

    public Phone() {
    }

    public Phone(String type, String number) {
        this.type = type;
        this.number = number;
    }

    public String getType() {
        return type;
    }

    public String getNumber() {
        return number;
    }
}

person.getPhones().add( new Phone( "landline", "028-234-9876" ) );
person.getPhones().add( new Phone( "mobile", "072-122-9876" ) );
INSERT INTO Person_phones ( Person_id, number, type )
VALUES ( 1, '028-234-9876', 'landline' )

INSERT INTO Person_phones ( Person_id, number, type )
VALUES ( 1, '072-122-9876', 'mobile' )

2.8.3. Collections of entities

If value type collections can only form a one-to-many association between an owner entity and multiple basic or embeddable types, entity collections can represent both @OneToMany and @ManyToMany associations.

From a relational database perspective, associations are defined by the foreign key side (the child-side). With value type collections, only the entity can control the association (the parent-side), but for a collection of entities, both sides of the association are managed by the persistence context.

For this reason, entity collections can be devised into two main categories: unidirectional and bidirectional associations. Unidirectional associations are very similar to value type collections since only the parent side controls this relationship. Bidirectional associations are more tricky since, even if sides need to be in-sync at all times, only one side is responsible for managing the association. A bidirectional association has an owning side and an inverse (mappedBy) side.

Another way of categorizing entity collections is by the underlying collection type, and so we can have:

  • bags

  • indexed lists

  • sets

  • sorted sets

  • maps

  • sorted maps

  • arrays

In the following sections, we will go through all these collection types and discuss both unidirectional and bidirectional associations.

2.8.4. Bags

Bags are unordered lists and we can have unidirectional bags or bidirectional ones.

Unidirectional bags

The unidirectional bag is mapped using a single @OneToMany annotation on the parent side of the association. Behind the scenes, Hibernate requires an association table to manage the parent-child relationship, as we can see in the following example:

Example 193. Unidirectional bag
@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;
    @OneToMany(cascade = CascadeType.ALL)
    private List<Phone> phones = new ArrayList<>();

    public Person() {
    }

    public Person(Long id) {
        this.id = id;
    }

    public List<Phone> getPhones() {
        return phones;
    }
}

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

    @Id
    private Long id;

    private String type;

    @Column(name = "`number`")
    private String number;

    public Phone() {
    }

    public Phone(Long id, String type, String number) {
        this.id = id;
        this.type = type;
        this.number = number;
    }

    public Long getId() {
        return id;
    }

    public String getType() {
        return type;
    }

    public String getNumber() {
        return number;
    }
}
CREATE TABLE Person (
    id BIGINT NOT NULL ,
    PRIMARY KEY ( id )
)

CREATE TABLE Person_Phone (
    Person_id BIGINT NOT NULL ,
    phones_id BIGINT NOT NULL
)

CREATE TABLE Phone (
    id BIGINT NOT NULL ,
    number VARCHAR(255) ,
    type VARCHAR(255) ,
    PRIMARY KEY ( id )
)

ALTER TABLE Person_Phone
ADD CONSTRAINT UK_9uhc5itwc9h5gcng944pcaslf
UNIQUE (phones_id)

ALTER TABLE Person_Phone
ADD CONSTRAINT FKr38us2n8g5p9rj0b494sd3391
FOREIGN KEY (phones_id) REFERENCES Phone

ALTER TABLE Person_Phone
ADD CONSTRAINT FK2ex4e4p7w1cj310kg2woisjl2
FOREIGN KEY (Person_id) REFERENCES Person

Because both the parent and the child sides are entities, the persistence context manages each entity separately. Cascades can propagate an entity state transition from a parent entity to its children.

By marking the parent side with the CascadeType.ALL attribute, the unidirectional association lifecycle becomes very similar to that of a value type collection.

Example 194. Unidirectional bag lifecycle
Person person = new Person( 1L );
person.getPhones().add( new Phone( 1L, "landline", "028-234-9876" ) );
person.getPhones().add( new Phone( 2L, "mobile", "072-122-9876" ) );
entityManager.persist( person );
INSERT INTO Person ( id )
VALUES ( 1 )

INSERT INTO Phone ( number, type, id )
VALUES ( '028-234-9876', 'landline', 1 )

INSERT INTO Phone ( number, type, id )
VALUES ( '072-122-9876', 'mobile', 2 )

INSERT INTO Person_Phone ( Person_id, phones_id )
VALUES ( 1, 1 )

INSERT INTO Person_Phone ( Person_id, phones_id )
VALUES ( 1, 2 )

In the example above, once the parent entity is persisted, the child entities are going to be persisted as well.

Just like value type collections, unidirectional bags are not as efficient when it comes to modifying the collection structure (removing or reshuffling elements). Because the parent-side cannot uniquely identify each individual child, Hibernate might delete all child table rows associated with the parent entity and re-add them according to the current collection state.

Bidirectional bags

The bidirectional bag is the most common type of entity collection. The @ManyToOne side is the owning side of the bidirectional bag association, while the @OneToMany is the inverse side, being marked with the mappedBy attribute.

Example 195. Bidirectional bag
@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;
    @OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
    private List<Phone> phones = new ArrayList<>();

    public Person() {
    }

    public Person(Long id) {
        this.id = id;
    }

    public List<Phone> getPhones() {
        return phones;
    }

    public void addPhone(Phone phone) {
        phones.add( phone );
        phone.setPerson( this );
    }

    public void removePhone(Phone phone) {
        phones.remove( phone );
        phone.setPerson( null );
    }
}

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

    @Id
    private Long id;

    private String type;

    @Column(name = "`number`", unique = true)
    @NaturalId
    private String number;

    @ManyToOne
    private Person person;

    public Phone() {
    }

    public Phone(Long id, String type, String number) {
        this.id = id;
        this.type = type;
        this.number = number;
    }

    public Long getId() {
        return id;
    }

    public String getType() {
        return type;
    }

    public String getNumber() {
        return number;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        Phone phone = (Phone) o;
        return Objects.equals( number, phone.number );
    }

    @Override
    public int hashCode() {
        return Objects.hash( number );
    }
}
CREATE TABLE Person (
    id BIGINT NOT NULL, PRIMARY KEY (id)
)

CREATE TABLE Phone (
    id BIGINT NOT NULL,
    number VARCHAR(255),
    type VARCHAR(255),
    person_id BIGINT,
    PRIMARY KEY (id)
)

ALTER TABLE Phone
ADD CONSTRAINT UK_l329ab0g4c1t78onljnxmbnp6
UNIQUE (number)

ALTER TABLE Phone
ADD CONSTRAINT FKmw13yfsjypiiq0i1osdkaeqpg
FOREIGN KEy (person_id) REFERENCES Person
Example 196. Bidirectional bag lifecycle
person.addPhone( new Phone( 1L, "landline", "028-234-9876" ) );
person.addPhone( new Phone( 2L, "mobile", "072-122-9876" ) );
entityManager.flush();
person.removePhone( person.getPhones().get( 0 ) );
INSERT INTO Phone (number, person_id, type, id)
VALUES ( '028-234-9876', 1, 'landline', 1 )

INSERT INTO Phone (number, person_id, type, id)
VALUES ( '072-122-9876', 1, 'mobile', 2 )

UPDATE Phone
SET person_id = NULL, type = 'landline' where id = 1
Example 197. Bidirectional bag with orphan removal
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Phone> phones = new ArrayList<>();
DELETE FROM Phone WHERE id = 1

When rerunning the previous example, the child will get removed because the parent-side propagates the removal upon disassociating the child entity reference.

2.8.5. Ordered Lists

Although they use the List interface on the Java side, bags don’t retain element order. To preserve the collection element order, there are two possibilities:

@OrderBy

the collection is ordered upon retrieval using a child entity property

@OrderColumn

the collection uses a dedicated order column in the collection link table

Unidirectional ordered lists

When using the @OrderBy annotation, the mapping looks as follows:

Example 198. Unidirectional @OrderBy list
@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;
    @OneToMany(cascade = CascadeType.ALL)
    @OrderBy("number")
    private List<Phone> phones = new ArrayList<>();

    public Person() {
    }

    public Person(Long id) {
        this.id = id;
    }

    public List<Phone> getPhones() {
        return phones;
    }
}

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

    @Id
    private Long id;

    private String type;

    @Column(name = "`number`")
    private String number;

    public Phone() {
    }

    public Phone(Long id, String type, String number) {
        this.id = id;
        this.type = type;
        this.number = number;
    }

    public Long getId() {
        return id;
    }

    public String getType() {
        return type;
    }

    public String getNumber() {
        return number;
    }
}

The database mapping is the same as with the Unidirectional bags example, so it won’t be repeated. Upon fetching the collection, Hibernate generates the following select statement:

Example 199. Unidirectional @OrderBy list select statement
SELECT
   phones0_.Person_id AS Person_i1_1_0_,
   phones0_.phones_id AS phones_i2_1_0_,
   unidirecti1_.id AS id1_2_1_,
   unidirecti1_."number" AS number2_2_1_,
   unidirecti1_.type AS type3_2_1_
FROM
   Person_Phone phones0_
INNER JOIN
   Phone unidirecti1_ ON phones0_.phones_id=unidirecti1_.id
WHERE
   phones0_.Person_id = 1
ORDER BY
   unidirecti1_."number"

The child table column is used to order the list elements.

The @OrderBy annotation can take multiple entity properties, and each property can take an ordering direction too (e.g. @OrderBy("name ASC, type DESC")).

If no property is specified (e.g. @OrderBy), the primary key of the child entity table is used for ordering.

Another ordering option is to use the @OrderColumn annotation:

Example 200. Unidirectional @OrderColumn list
@OneToMany(cascade = CascadeType.ALL)
@OrderColumn(name = "order_id")
private List<Phone> phones = new ArrayList<>();
CREATE TABLE Person_Phone (
    Person_id BIGINT NOT NULL ,
    phones_id BIGINT NOT NULL ,
    order_id INTEGER NOT NULL ,
    PRIMARY KEY ( Person_id, order_id )
)

This time, the link table takes the order_id column and uses it to materialize the collection element order. When fetching the list, the following select query is executed:

Example 201. Unidirectional @OrderColumn list select statement
select
   phones0_.Person_id as Person_i1_1_0_,
   phones0_.phones_id as phones_i2_1_0_,
   phones0_.order_id as order_id3_0_,
   unidirecti1_.id as id1_2_1_,
   unidirecti1_.number as number2_2_1_,
   unidirecti1_.type as type3_2_1_
from
   Person_Phone phones0_
inner join
   Phone unidirecti1_
      on phones0_.phones_id=unidirecti1_.id
where
   phones0_.Person_id = 1

With the order_id column in place, Hibernate can order the list in-memory after it’s being fetched from the database.

Bidirectional ordered lists

The mapping is similar with the Bidirectional bags example, just that the parent side is going to be annotated with either @OrderBy or @OrderColumn.

Example 202. Bidirectional @OrderBy list
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
@OrderBy("number")
private List<Phone> phones = new ArrayList<>();

Just like with the unidirectional @OrderBy list, the number column is used to order the statement on the SQL level.

When using the @OrderColumn annotation, the order_id column is going to be embedded in the child table:

Example 203. Bidirectional @OrderColumn list
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
@OrderColumn(name = "order_id")
private List<Phone> phones = new ArrayList<>();
CREATE TABLE Phone (
    id BIGINT NOT NULL ,
    number VARCHAR(255) ,
    type VARCHAR(255) ,
    person_id BIGINT ,
    order_id INTEGER ,
    PRIMARY KEY ( id )
)

When fetching the collection, Hibernate will use the fetched ordered columns to sort the elements according to the @OrderColumn mapping.

Customizing ordered list ordinal

You can customize the ordinal of the underlying ordered list by using the @ListIndexBase annotation.

Example 204. @ListIndexBase mapping example
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
@OrderColumn(name = "order_id")
@ListIndexBase(100)
private List<Phone> phones = new ArrayList<>();

When inserting two Phone records, Hibernate is going to start the List index from 100 this time.

Example 205. @ListIndexBase persist example
Person person = new Person( 1L );
entityManager.persist( person );
person.addPhone( new Phone( 1L, "landline", "028-234-9876" ) );
person.addPhone( new Phone( 2L, "mobile", "072-122-9876" ) );
INSERT INTO Phone("number", person_id, type, id)
VALUES ('028-234-9876', 1, 'landline', 1)

INSERT INTO Phone("number", person_id, type, id)
VALUES ('072-122-9876', 1, 'mobile', 2)

UPDATE Phone
SET order_id = 100
WHERE id = 1

UPDATE Phone
SET order_id = 101
WHERE id = 2
Customizing ORDER BY SQL clause

While the JPA @OrderBy annotation allows you to specify the entity attributes used for sorting when fetching the current annotated collection, the Hibernate specific @OrderBy annotation is used to specify a SQL clause instead.

In the following example, the @OrderBy annotations uses the CHAR_LENGTH SQL function to order the Article entities by the size of their contents.

Example 206. @OrderBy mapping example
@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;

    private String name;

    @OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
    @org.hibernate.annotations.OrderBy(clause = "CHAR_LENGTH(name) DESC")
    private List<Article> articles = new ArrayList<>();

    //Getters and setters are omitted for brevity
}

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

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private String content;

    @ManyToOne(fetch = FetchType.LAZY)
    private Person person;

    private Article() {
    }

    public Article(String name, String content) {
        this.name = name;
        this.content = content;
    }

    //Getters and setters are omitted for brevity
}

When fetching the articles collection, Hibernate uses the ORDER BY SQL clause provided by the mapping:

Example 207. @OrderBy fetching example
Person person = entityManager.find( Person.class, 1L );
assertEquals(
    "High-Performance Hibernate",
    person.getArticles().get( 0 ).getName()
);
select
    a.person_id as person_i4_0_0_,
    a.id as id1_0_0_,
    a.content as content2_0_1_,
    a.name as name3_0_1_,
    a.person_id as person_i4_0_1_
from
    Article a
where
    a.person_id = ?
order by
    CHAR_LENGTH(a.name) desc

2.8.6. Sets

Sets are collections that don’t allow duplicate entries and Hibernate supports both the unordered Set and the natural-ordering SortedSet.

Unidirectional sets

The unidirectional set uses a link table to hold the parent-child associations and the entity mapping looks as follows:

Example 208. Unidirectional set
@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;
    @OneToMany(cascade = CascadeType.ALL)
    private Set<Phone> phones = new HashSet<>();

    public Person() {
    }

    public Person(Long id) {
        this.id = id;
    }

    public Set<Phone> getPhones() {
        return phones;
    }
}

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

    @Id
    private Long id;

    private String type;

    @NaturalId
    @Column(name = "`number`")
    private String number;

    public Phone() {
    }

    public Phone(Long id, String type, String number) {
        this.id = id;
        this.type = type;
        this.number = number;
    }

    public Long getId() {
        return id;
    }

    public String getType() {
        return type;
    }

    public String getNumber() {
        return number;
    }

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        Phone phone = (Phone) o;
        return Objects.equals( number, phone.number );
    }

    @Override
    public int hashCode() {
        return Objects.hash( number );
    }
}

The unidirectional set lifecycle is similar to that of the Unidirectional bags, so it can be omitted. The only difference is that Set doesn’t allow duplicates, but this constraint is enforced by the Java object contract rather then the database mapping.

When using sets, it’s very important to supply proper equals/hashCode implementations for child entities. In the absence of a custom equals/hashCode implementation logic, Hibernate will use the default Java reference-based object equality which might render unexpected results when mixing detached and managed object instances.

Bidirectional sets

Just like bidirectional bags, the bidirectional set doesn’t use a link table, and the child table has a foreign key referencing the parent table primary key. The lifecycle is just like with bidirectional bags except for the duplicates which are filtered out.

Example 209. Bidirectional set
@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;

    @OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
    private Set<Phone> phones = new HashSet<>();

    public Person() {
    }

    public Person(Long id) {
        this.id = id;
    }

    public Set<Phone> getPhones() {
        return phones;
    }

    public void addPhone(Phone phone) {
        phones.add( phone );
        phone.setPerson( this );
    }

    public void removePhone(Phone phone) {
        phones.remove( phone );
        phone.setPerson( null );
    }
}

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

    @Id
    private Long id;

    private String type;

    @Column(name = "`number`", unique = true)
    @NaturalId
    private String number;

    @ManyToOne
    private Person person;

    public Phone() {
    }

    public Phone(Long id, String type, String number) {
        this.id = id;
        this.type = type;
        this.number = number;
    }

    public Long getId() {
        return id;
    }

    public String getType() {
        return type;
    }

    public String getNumber() {
        return number;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        Phone phone = (Phone) o;
        return Objects.equals( number, phone.number );
    }

    @Override
    public int hashCode() {
        return Objects.hash( number );
    }
}

2.8.7. Sorted sets

For sorted sets, the entity mapping must use the SortedSet interface instead. According to the SortedSet contract, all elements must implement the Comparable interface and therefore provide the sorting logic.

Unidirectional sorted sets

A SortedSet that relies on the natural sorting order given by the child element Comparable implementation logic must be annotated with the @SortNatural Hibernate annotation.

Example 210. Unidirectional natural sorted set
@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;
    @OneToMany(cascade = CascadeType.ALL)
    @SortNatural
    private SortedSet<Phone> phones = new TreeSet<>();

    public Person() {
    }

    public Person(Long id) {
        this.id = id;
    }

    public Set<Phone> getPhones() {
        return phones;
    }
}

@Entity(name = "Phone")
public static class Phone implements Comparable<Phone> {

    @Id
    private Long id;

    private String type;

    @NaturalId
    @Column(name = "`number`")
    private String number;

    public Phone() {
    }

    public Phone(Long id, String type, String number) {
        this.id = id;
        this.type = type;
        this.number = number;
    }

    public Long getId() {
        return id;
    }

    public String getType() {
        return type;
    }

    public String getNumber() {
        return number;
    }

    @Override
    public int compareTo(Phone o) {
        return number.compareTo( o.getNumber() );
    }

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        Phone phone = (Phone) o;
        return Objects.equals( number, phone.number );
    }

    @Override
    public int hashCode() {
        return Objects.hash( number );
    }
}

The lifecycle and the database mapping are identical to the Unidirectional bags, so they are intentionally omitted.

To provide a custom sorting logic, Hibernate also provides a @SortComparator annotation:

Example 211. Unidirectional custom comparator sorted set
@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;

    @OneToMany(cascade = CascadeType.ALL)
    @SortComparator(ReverseComparator.class)
    private SortedSet<Phone> phones = new TreeSet<>();

    public Person() {
    }

    public Person(Long id) {
        this.id = id;
    }

    public Set<Phone> getPhones() {
        return phones;
    }
}

public static class ReverseComparator implements Comparator<Phone> {
    @Override
    public int compare(Phone o1, Phone o2) {
        return o2.compareTo( o1 );
    }
}

@Entity(name = "Phone")
public static class Phone implements Comparable<Phone> {

    @Id
    private Long id;

    private String type;

    @NaturalId
    @Column(name = "`number`")
    private String number;

    public Phone() {
    }

    public Phone(Long id, String type, String number) {
        this.id = id;
        this.type = type;
        this.number = number;
    }

    public Long getId() {
        return id;
    }

    public String getType() {
        return type;
    }

    public String getNumber() {
        return number;
    }

    @Override
    public int compareTo(Phone o) {
        return number.compareTo( o.getNumber() );
    }

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        Phone phone = (Phone) o;
        return Objects.equals( number, phone.number );
    }

    @Override
    public int hashCode() {
        return Objects.hash( number );
    }
}
Bidirectional sorted sets

The @SortNatural and @SortComparator work the same for bidirectional sorted sets too:

Example 212. Bidirectional natural sorted set
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
@SortNatural
private SortedSet<Phone> phones = new TreeSet<>();


@OneToMany(cascade = CascadeType.ALL)
@SortComparator(ReverseComparator.class)

2.8.8. Maps

A java.util.Map is ternary association because it required a parent entity a map key and a value. An entity can either be a map key or a map value, depending on the mapping. Hibernate allows using the following map keys:

MapKeyColumn

for value type maps, the map key is a column in the link table that defines the grouping logic

MapKey

the map key is either the primary key or another property of the entity stored as a map entry value

MapKeyEnumerated

the map key is an Enum of the target child entity

MapKeyTemporal

the map key is a Date or a Calendar of the target child entity

MapKeyJoinColumn

the map key is an entity mapped as an association in the child entity that’s stored as a map entry key

Value type maps

A map of value type must use the @ElementCollection annotation, just like value type lists, bags or sets.

Example 213. Value type map with an entity as a map key
public enum PhoneType {
    LAND_LINE,
    MOBILE
}

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

    @Id
    private Long id;

    @Temporal(TemporalType.TIMESTAMP)
    @ElementCollection
    @CollectionTable(name = "phone_register")
    @Column(name = "since")
    @MapKeyJoinColumn(name = "phone_id", referencedColumnName = "id")
    private Map<Phone, Date> phoneRegister = new HashMap<>();

    public Person() {}

    public Person(Long id) {
        this.id = id;
    }

    public Map<Phone, Date> getPhoneRegister() {
        return phoneRegister;
    }
}

@Embeddable
public static class Phone {

    private PhoneType type;

    @Column(name = "`number`")
    private String number;

    public Phone() {
    }

    public Phone(PhoneType type, String number) {
        this.type = type;
        this.number = number;
    }

    public PhoneType getType() {
        return type;
    }

    public String getNumber() {
        return number;
    }
}
CREATE TABLE Person (
    id BIGINT NOT NULL ,
    PRIMARY KEY ( id )
)

CREATE TABLE phone_register (
    Person_id BIGINT NOT NULL ,
    since TIMESTAMP ,
    number VARCHAR(255) NOT NULL ,
    type INTEGER NOT NULL ,
    PRIMARY KEY ( Person_id, number, type )
)

ALTER TABLE phone_register
ADD CONSTRAINT FKrmcsa34hr68of2rq8qf526mlk
FOREIGN KEY (Person_id) REFERENCES Person

Adding entries to the map generates the following SQL statements:

Example 214. Adding value type map entries
person.getPhoneRegister().put(
    new Phone( PhoneType.LAND_LINE, "028-234-9876" ), new Date()
);
person.getPhoneRegister().put(
    new Phone( PhoneType.MOBILE, "072-122-9876" ), new Date()
);
INSERT INTO phone_register (Person_id, number, type, since)
VALUES (1, '072-122-9876', 1, '2015-12-15 17:16:45.311')

INSERT INTO phone_register (Person_id, number, type, since)
VALUES (1, '028-234-9876', 0, '2015-12-15 17:16:45.311')
Maps with a custom key type

Hibernate defines the @MapKeyType annotation which you can use to customize the Map key type.

Considering you have the following tables in your database:

create table person (
    id int8 not null,
    primary key (id)
)

create table call_register (
    person_id int8 not null,
    phone_number int4,
    call_timestamp_epoch int8 not null,
    primary key (person_id, call_key)
)

alter table if exists call_register
    add constraint FKsn58spsregnjyn8xt61qkxsub
    foreign key (person_id)
    references person

The call_register records the call history for every person. The call_timestamp_epoch column stores the phone call timestamp as a Unix timestamp since epoch.

The @MapKeyColumn annotation is used to define the table column holding the key while the @Column mapping gives the value of the java.util.Map in question.

Since we want to map all the calls by their associated java.util.Date, not by their timestamp since epoch which is a number, the entity mapping looks as follows:

Example 215. @MapKeyType mapping example
@Entity
@Table(name = "person")
public static class Person {

    @Id
    private Long id;

    @ElementCollection
    @CollectionTable(
        name = "call_register",
        joinColumns = @JoinColumn(name = "person_id")
    )
    @MapKeyType(
        @Type(
            type = "org.hibernate.userguide.collections.type.TimestampEpochType"
        )
    )
    @MapKeyColumn( name = "call_timestamp_epoch" )
    @Column(name = "phone_number")
    private Map<Date, Integer> callRegister = new HashMap<>();

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

    public Map<Date, Integer> getCallRegister() {
        return callRegister;
    }
}

The associated TimestampEpochType looks as follows:

public class TimestampEpochType
        extends AbstractSingleColumnStandardBasicType<Date>
        implements VersionType<Date>, LiteralType<Date> {

    public static final TimestampEpochType INSTANCE = new TimestampEpochType();

    public TimestampEpochType() {
        super(
            BigIntTypeDescriptor.INSTANCE,
            JdbcTimestampTypeDescriptor.INSTANCE
        );
    }

    @Override
    public String getName() {
        return "epoch";
    }

    @Override
    public Date next(
        Date current,
        SharedSessionContractImplementor session) {
        return seed( session );
    }

    @Override
    public Date seed(
        SharedSessionContractImplementor session) {
        return new Timestamp( System.currentTimeMillis() );
    }

    @Override
    public Comparator<Date> getComparator() {
        return getJavaTypeDescriptor().getComparator();
    }

    @Override
    public String objectToSQLString(
        Date value,
        Dialect dialect) throws Exception {
        final Timestamp ts = Timestamp.class.isInstance( value )
            ? ( Timestamp ) value
            : new Timestamp( value.getTime() );
        return StringType.INSTANCE.objectToSQLString(
            ts.toString(), dialect
        );
    }

    @Override
    public Date fromStringValue(
        String xml) throws HibernateException {
        return fromString( xml );
    }
}

The TimestampEpochType allows us to map a Unix timestamp since epoch to a java.util.Date. But, without the @MapKeyType Hibernate annotation, it would nt be possible to customize the Map key type.

Maps having an interface type as the key

Considering you have the following PhoneNumber interface with an implementation given by the MobilePhone class type:

Example 216. PhoneNumber interface and the MobilePhone class type
public interface PhoneNumber {

    String get();
}

@Embeddable
public static class MobilePhone
        implements PhoneNumber {

    static PhoneNumber fromString(String phoneNumber) {
        String[] tokens = phoneNumber.split( "-" );
        if ( tokens.length != 3 ) {
            throw new IllegalArgumentException( "invalid phone number: " + phoneNumber );
        }
        int i = 0;
        return new MobilePhone(
            tokens[i++],
            tokens[i++],
            tokens[i]
        );
    }

    private MobilePhone() {
    }

    public MobilePhone(
            String countryCode,
            String operatorCode,
            String subscriberCode) {
        this.countryCode = countryCode;
        this.operatorCode = operatorCode;
        this.subscriberCode = subscriberCode;
    }

    @Column(name = "country_code")
    private String countryCode;

    @Column(name = "operator_code")
    private String operatorCode;

    @Column(name = "subscriber_code")
    private String subscriberCode;

    @Override
    public String get() {
        return String.format(
            "%s-%s-%s",
            countryCode,
            operatorCode,
            subscriberCode
        );
    }

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        MobilePhone that = (MobilePhone) o;
        return Objects.equals( countryCode, that.countryCode ) &&
                Objects.equals( operatorCode, that.operatorCode ) &&
                Objects.equals( subscriberCode, that.subscriberCode );
    }

    @Override
    public int hashCode() {
        return Objects.hash( countryCode, operatorCode, subscriberCode );
    }
}

If you want to use the PhoneNumber interface as a java.util.Map key, then you need to supply the @MapKeyClass annotation as well.

Example 217. @MapKeyClass mapping example
@Entity
@Table(name = "person")
public static class Person {

    @Id
    private Long id;

    @ElementCollection
    @CollectionTable(
        name = "call_register",
        joinColumns = @JoinColumn(name = "person_id")
    )
    @MapKeyColumn( name = "call_timestamp_epoch" )
    @MapKeyClass( MobilePhone.class )
    @Column(name = "call_register")
    private Map<PhoneNumber, Integer> callRegister = new HashMap<>();

    //Getters and setters are omitted for brevity
}
create table person (
    id bigint not null,
    primary key (id)
)

create table call_register (
    person_id bigint not null,
    call_register integer,
    country_code varchar(255) not null,
    operator_code varchar(255) not null,
    subscriber_code varchar(255) not null,
    primary key (person_id, country_code, operator_code, subscriber_code)
)

alter table call_register
    add constraint FKqyj2at6ik010jqckeaw23jtv2
    foreign key (person_id)
    references person

When inserting a Person with a callRegister containing 2 MobilePhone references, Hibernate generates the following SQL statements:

Example 218. @MapKeyClass persist example
Person person = new Person();
person.setId( 1L );
person.getCallRegister().put( new MobilePhone( "01", "234", "567" ), 101 );
person.getCallRegister().put( new MobilePhone( "01", "234", "789" ), 102 );

entityManager.persist( person );
insert into person (id) values (?)

-- binding parameter [1] as [BIGINT] - [1]

insert into call_register(
    person_id,
    country_code,
    operator_code,
    subscriber_code,
    call_register
)
values
    (?, ?, ?, ?, ?)

-- binding parameter [1] as [BIGINT]  - [1]
-- binding parameter [2] as [VARCHAR] - [01]
-- binding parameter [3] as [VARCHAR] - [234]
-- binding parameter [4] as [VARCHAR] - [789]
-- binding parameter [5] as [INTEGER] - [102]

insert into call_register(
    person_id,
    country_code,
    operator_code,
    subscriber_code,
    call_register
)
values
    (?, ?, ?, ?, ?)

-- binding parameter [1] as [BIGINT]  - [1]
-- binding parameter [2] as [VARCHAR] - [01]
-- binding parameter [3] as [VARCHAR] - [234]
-- binding parameter [4] as [VARCHAR] - [567]
-- binding parameter [5] as [INTEGER] - [101]

When fetching a Person and accessing the callRegister Map, Hibernate generates the following SQL statements:

Example 219. @MapKeyClass fetch example
Person person = entityManager.find( Person.class, 1L );
assertEquals( 2, person.getCallRegister().size() );

assertEquals(
    Integer.valueOf( 101 ),
    person.getCallRegister().get( MobilePhone.fromString( "01-234-567" ) )
);

assertEquals(
    Integer.valueOf( 102 ),
    person.getCallRegister().get( MobilePhone.fromString( "01-234-789" ) )
);
select
    cr.person_id as person_i1_0_0_,
    cr.call_register as call_reg2_0_0_,
    cr.country_code as country_3_0_,
    cr.operator_code as operator4_0_,
    cr.subscriber_code as subscrib5_0_
from
    call_register cr
where
    cr.person_id = ?

-- binding parameter [1] as [BIGINT] - [1]

-- extracted value ([person_i1_0_0_] : [BIGINT])  - [1]
-- extracted value ([call_reg2_0_0_] : [INTEGER]) - [101]
-- extracted value ([country_3_0_]   : [VARCHAR]) - [01]
-- extracted value ([operator4_0_]   : [VARCHAR]) - [234]
-- extracted value ([subscrib5_0_]   : [VARCHAR]) - [567]

-- extracted value ([person_i1_0_0_] : [BIGINT])  - [1]
-- extracted value ([call_reg2_0_0_] : [INTEGER]) - [102]
-- extracted value ([country_3_0_]   : [VARCHAR]) - [01]
-- extracted value ([operator4_0_]   : [VARCHAR]) - [234]
-- extracted value ([subscrib5_0_]   : [VARCHAR]) - [789]
Unidirectional maps

A unidirectional map exposes a parent-child association from the parent-side only.

The following example shows a unidirectional map which also uses a @MapKeyTemporal annotation. The map key is a timestamp and it’s taken from the child entity table.

The @MapKey annotation is used to define the entity attribute used as a key of the java.util.Map in question.

Example 220. Unidirectional Map
public enum PhoneType {
    LAND_LINE,
    MOBILE
}

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

    @Id
    private Long id;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinTable(
            name = "phone_register",
            joinColumns = @JoinColumn(name = "phone_id"),
            inverseJoinColumns = @JoinColumn(name = "person_id"))
    @MapKey(name = "since")
    @MapKeyTemporal(TemporalType.TIMESTAMP)
    private Map<Date, Phone> phoneRegister = new HashMap<>();

    public Person() {
    }

    public Person(Long id) {
        this.id = id;
    }

    public Map<Date, Phone> getPhoneRegister() {
        return phoneRegister;
    }

    public void addPhone(Phone phone) {
        phoneRegister.put( phone.getSince(), phone );
    }
}

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

    @Id
    @GeneratedValue
    private Long id;

    private PhoneType type;

    @Column(name = "`number`")
    private String number;

    private Date since;

    public Phone() {
    }

    public Phone(PhoneType type, String number, Date since) {
        this.type = type;
        this.number = number;
        this.since = since;
    }

    public PhoneType getType() {
        return type;
    }

    public String getNumber() {
        return number;
    }

    public Date getSince() {
        return since;
    }
}
CREATE TABLE Person (
    id BIGINT NOT NULL ,
    PRIMARY KEY ( id )
)

CREATE TABLE Phone (
    id BIGINT NOT NULL ,
    number VARCHAR(255) ,
    since TIMESTAMP ,
    type INTEGER ,
    PRIMARY KEY ( id )
)

CREATE TABLE phone_register (
    phone_id BIGINT NOT NULL ,
    person_id BIGINT NOT NULL ,
    PRIMARY KEY ( phone_id, person_id )
)

ALTER TABLE phone_register
ADD CONSTRAINT FKc3jajlx41lw6clbygbw8wm65w
FOREIGN KEY (person_id) REFERENCES Phone

ALTER TABLE phone_register
ADD CONSTRAINT FK6npoomh1rp660o1b55py9ndw4
FOREIGN KEY (phone_id) REFERENCES Person
Bidirectional maps

Like most bidirectional associations, this relationship is owned by the child-side while the parent is the inverse side and can propagate its own state transitions to the child entities.

In the following example, you can see that @MapKeyEnumerated was used so that the Phone enumeration becomes the map key.

Example 221. Bidirectional Map
@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;

    @OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true)
    @MapKey(name = "type")
    @MapKeyEnumerated
    private Map<PhoneType, Phone> phoneRegister = new HashMap<>();

    public Person() {
    }

    public Person(Long id) {
        this.id = id;
    }

    public Map<PhoneType, Phone> getPhoneRegister() {
        return phoneRegister;
    }

    public void addPhone(Phone phone) {
        phone.setPerson( this );
        phoneRegister.put( phone.getType(), phone );
    }
}

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

    @Id
    @GeneratedValue
    private Long id;

    private PhoneType type;

    @Column(name = "`number`")
    private String number;

    private Date since;

    @ManyToOne
    private Person person;

    public Phone() {
    }

    public Phone(PhoneType type, String number, Date since) {
        this.type = type;
        this.number = number;
        this.since = since;
    }

    public PhoneType getType() {
        return type;
    }

    public String getNumber() {
        return number;
    }

    public Date getSince() {
        return since;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }
}
CREATE TABLE Person (
    id BIGINT NOT NULL ,
    PRIMARY KEY ( id )
)

CREATE TABLE Phone (
    id BIGINT NOT NULL ,
    number VARCHAR(255) ,
    since TIMESTAMP ,
    type INTEGER ,
    person_id BIGINT ,
    PRIMARY KEY ( id )
)

ALTER TABLE Phone
ADD CONSTRAINT FKmw13yfsjypiiq0i1osdkaeqpg
FOREIGN KEY (person_id) REFERENCES Person

2.8.9. Arrays

When discussing arrays, it is important to understand the distinction between SQL array types and Java arrays that are mapped as part of the application’s domain model.

Not all databases implement the SQL-99 ARRAY type and, for this reason, Hibernate doesn’t support native database array types.

Hibernate does support the mapping of arrays in the Java domain model - conceptually the same as mapping a List. However, it is important to realize that it is impossible for Hibernate to offer lazy-loading for arrays of entities and, for this reason, it is strongly recommended to map a "collection" of entities using a List rather than an array.

2.8.10. Arrays as binary

By default, Hibernate will choose a BINARY type, as supported by the current Dialect.

Example 222. Arrays stored as binary
@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;
    private String[] phones;

    public Person() {
    }

    public Person(Long id) {
        this.id = id;
    }

    public String[] getPhones() {
        return phones;
    }

    public void setPhones(String[] phones) {
        this.phones = phones;
    }
}
CREATE TABLE Person (
    id BIGINT NOT NULL ,
    phones VARBINARY(255) ,
    PRIMARY KEY ( id )
)

If you want to map arrays such as String[] or int[] to database-specific array types like PostgreSQL integer[] or text[], you need to write a custom Hibernate Type.

Check out this article for an example of how to write such a custom Hibernate Type.

2.8.11. Collections as basic value type

Notice how all the previous examples explicitly mark the collection attribute as either ElementCollection, OneToMany or ManyToMany. Collections not marked as such require a custom Hibernate Type and the collection elements must be stored in a single database column.

This is sometimes beneficial. Consider a use-case such as a VARCHAR column that represents a delimited list/set of Strings.

Example 223. Comma delimited collection
@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;

    @Type(type = "comma_delimited_strings")
    private List<String> phones = new ArrayList<>();

    public List<String> getPhones() {
        return phones;
    }
}

public class CommaDelimitedStringsJavaTypeDescriptor extends AbstractTypeDescriptor<List> {

    public static final String DELIMITER = ",";

    public CommaDelimitedStringsJavaTypeDescriptor() {
        super(
            List.class,
            new MutableMutabilityPlan<List>() {
                @Override
                protected List deepCopyNotNull(List value) {
                    return new ArrayList( value );
                }
            }
        );
    }

    @Override
    public String toString(List value) {
        return ( (List<String>) value ).stream().collect( Collectors.joining( DELIMITER ) );
    }

    @Override
    public List fromString(String string) {
        List<String> values = new ArrayList<>();
        Collections.addAll( values, string.split( DELIMITER ) );
        return values;
    }

    @Override
    public <X> X unwrap(List value, Class<X> type, WrapperOptions options) {
        return (X) toString( value );
    }

    @Override
    public <X> List wrap(X value, WrapperOptions options) {
        return fromString( (String) value );
    }
}

public class CommaDelimitedStringsType extends AbstractSingleColumnStandardBasicType<List> {

    public CommaDelimitedStringsType() {
        super(
            VarcharTypeDescriptor.INSTANCE,
            new CommaDelimitedStringsJavaTypeDescriptor()
        );
    }

    @Override
    public String getName() {
        return "comma_delimited_strings";
    }
}

The developer can use the comma-delimited collection like any other collection we’ve discussed so far and Hibernate will take care of the type transformation part. The collection itself behaves like any other basic value type, as its lifecycle is bound to its owner entity.

Example 224. Comma delimited collection lifecycle
person.phones.add( "027-123-4567" );
person.phones.add( "028-234-9876" );
session.flush();
person.getPhones().remove( 0 );
INSERT INTO Person ( phones, id )
VALUES ( '027-123-4567,028-234-9876', 1 )

UPDATE Person
SET    phones = '028-234-9876'
WHERE  id = 1

See the Hibernate Integrations Guide for more details on developing custom value type mappings.

2.8.12. Custom collection types

If you wish to use other collection types than List, Set or Map, like Queue for instance, you have to use a custom collection type, as illustrated by the following example:

Example 225. Custom collection mapping example
@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;

    @OneToMany(cascade = CascadeType.ALL)
    @CollectionType( type = "org.hibernate.userguide.collections.type.QueueType")
    private Collection<Phone> phones = new LinkedList<>();

    //Constructors are omitted for brevity

    public Queue<Phone> getPhones() {
        return (Queue<Phone>) phones;
    }
}

@Entity(name = "Phone")
public static class Phone implements Comparable<Phone> {

    @Id
    private Long id;

    private String type;

    @NaturalId
    @Column(name = "`number`")
    private String number;

    //Getters and setters are omitted for brevity

    @Override
    public int compareTo(Phone o) {
        return number.compareTo( o.getNumber() );
    }

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        Phone phone = (Phone) o;
        return Objects.equals( number, phone.number );
    }

    @Override
    public int hashCode() {
        return Objects.hash( number );
    }
}

public class QueueType implements UserCollectionType {

    @Override
    public PersistentCollection instantiate(
            SharedSessionContractImplementor session,
            CollectionPersister persister) throws HibernateException {
        return new PersistentQueue( session );
    }

    @Override
    public PersistentCollection wrap(
            SharedSessionContractImplementor session,
            Object collection) {
        return new PersistentQueue( session, (List) collection );
    }

    @Override
    public Iterator getElementsIterator(Object collection) {
        return ( (Queue) collection ).iterator();
    }

    @Override
    public boolean contains(Object collection, Object entity) {
        return ( (Queue) collection ).contains( entity );
    }

    @Override
    public Object indexOf(Object collection, Object entity) {
        int i = ( (List) collection ).indexOf( entity );
        return ( i < 0 ) ? null : i;
    }

    @Override
    public Object replaceElements(
            Object original,
            Object target,
            CollectionPersister persister,
            Object owner,
            Map copyCache,
            SharedSessionContractImplementor session)
            throws HibernateException {
        Queue result = (Queue) target;
        result.clear();
        result.addAll( (Queue) original );
        return result;
    }

    @Override
    public Object instantiate(int anticipatedSize) {
        return new LinkedList<>();
    }

}

public class PersistentQueue extends PersistentBag implements Queue {

    public PersistentQueue(SharedSessionContractImplementor session) {
        super( session );
    }

    public PersistentQueue(SharedSessionContractImplementor session, List list) {
        super( session, list );
    }

    @Override
    public boolean offer(Object o) {
        return add(o);
    }

    @Override
    public Object remove() {
        return poll();
    }

    @Override
    public Object poll() {
        int size = size();
        if(size > 0) {
            Object first = get(0);
            remove( 0 );
            return first;
        }
        throw new NoSuchElementException();
    }

    @Override
    public Object element() {
        return peek();
    }

    @Override
    public Object peek() {
        return size() > 0 ? get( 0 ) : null;
    }
}

The reason why the Queue interface is not used for the entity attribute is because Hibernate only allows the following types:

  • java.util.List

  • java.util.Set

  • java.util.Map

  • java.util.SortedSet

  • java.util.SortedMap

However, the custom collection type can still be customized as long as the base type is one of the aformentioned persistent types.

This way, the Phone collection can be used as a java.util.Queue:

Example 226. Custom collection example
Person person = entityManager.find( Person.class, 1L );
Queue<Phone> phones = person.getPhones();
Phone head = phones.peek();
assertSame(head, phones.poll());
assertEquals( 1, phones.size() );

2.9. Natural Ids

Natural ids represent domain model unique identifiers that have a meaning in the real world too. Even if a natural id does not make a good primary key (surrogate keys being usually preferred), it’s still useful to tell Hibernate about it. As we will see later, Hibernate provides a dedicated, efficient API for loading an entity by its natural id much like it offers for loading by its identifier (PK).

2.9.1. Natural Id Mapping

Natural ids are defined in terms of on e or more persistent attributes.

Example 227. Natural id using single basic attribute
@Entity(name = "Book")
public static class Book {

    @Id
    private Long id;

    private String title;

    private String author;

    @NaturalId
    private String isbn;

    //Getters and setters are omitted for brevity
}
Example 228. Natural id using single embedded attribute
@Entity(name = "Book")
public static class Book {

    @Id
    private Long id;

    private String title;

    private String author;

    @NaturalId
    @Embedded
    private Isbn isbn;

    //Getters and setters are omitted for brevity
}

@Embeddable
public static class Isbn implements Serializable {

    private String isbn10;

    private String isbn13;

    //Getters and setters are omitted for brevity

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        Isbn isbn = (Isbn) o;
        return Objects.equals( isbn10, isbn.isbn10 ) &&
                Objects.equals( isbn13, isbn.isbn13 );
    }

    @Override
    public int hashCode() {
        return Objects.hash( isbn10, isbn13 );
    }
}
Example 229. Natural id using multiple persistent attributes
@Entity(name = "Book")
public static class Book {

    @Id
    private Long id;

    private String title;

    private String author;

    @NaturalId
    private String productNumber;

    @NaturalId
    @ManyToOne(fetch = FetchType.LAZY)
    private Publisher publisher;

    //Getters and setters are omitted for brevity
}

@Entity(name = "Publisher")
public static class Publisher implements Serializable {

    @Id
    private Long id;

    private String name;

    //Getters and setters are omitted for brevity

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        Publisher publisher = (Publisher) o;
        return Objects.equals( id, publisher.id ) &&
                Objects.equals( name, publisher.name );
    }

    @Override
    public int hashCode() {
        return Objects.hash( id, name );
    }
}

2.9.2. Natural Id API

As stated before, Hibernate provides an API for loading entities by their associate natural id. This is represented by the org.hibernate.NaturalIdLoadAccess contract obtained via Session#byNaturalId.

If the entity does not define a natural id, trying to load an entity by its natural id will throw an exception.

Example 230. Using NaturalIdLoadAccess
Book book = entityManager
    .unwrap(Session.class)
    .byNaturalId( Book.class )
    .using( "isbn", "978-9730228236" )
    .load();
Book book = entityManager
    .unwrap(Session.class)
    .byNaturalId( Book.class )
    .using(
        "isbn",
        new Isbn(
            "973022823X",
            "978-9730228236"
        ) )
    .load();
Book book = entityManager
    .unwrap(Session.class)
    .byNaturalId( Book.class )
    .using("productNumber", "973022823X")
    .using("publisher", publisher)
    .load();

NaturalIdLoadAccess offers 2 distinct methods for obtaining the entity:

load()

obtains a reference to the entity, making sure that the entity state is initialized

getReference()

obtains a reference to the entity. The state may or may not be initialized. If the entity is already associated with the current running Session, that reference (loaded or not) is returned. If the entity is not loaded in the current Session and the entity supports proxy generation, an uninitialized proxy is generated and returned, otherwise the entity is loaded from the database and returned.

NaturalIdLoadAccess allows loading an entity by natural id and at the same time apply a pessimistic lock. For additional details on locking, see the Locking chapter.

We will discuss the last method available on NaturalIdLoadAccess ( setSynchronizationEnabled() ) in Natural Id - Mutability and Caching.

Because the Company and PostalCarrier entities define "simple" natural ids, we can load them as follows:

Example 231. Loading by simple natural id
Book book = entityManager
    .unwrap(Session.class)
    .bySimpleNaturalId( Book.class )
    .load( "978-9730228236" );
Book book = entityManager
    .unwrap(Session.class)
    .bySimpleNaturalId( Book.class )
    .load(
        new Isbn(
            "973022823X",
            "978-9730228236"
        )
    );

Here we see the use of the org.hibernate.SimpleNaturalIdLoadAccess contract, obtained via `Session#bySimpleNaturalId().

SimpleNaturalIdLoadAccess is similar to NaturalIdLoadAccess except that it does not define the using method. Instead, because these simple natural ids are defined based on just one attribute we can directly pass the corresponding natural id attribute value directly to the load() and getReference() methods.

If the entity does not define a natural id, or if the natural id is not of a "simple" type, an exception will be thrown there.

2.9.3. Natural Id - Mutability and Caching

A natural id may be mutable or immutable. By default the @NaturalId annotation marks an immutable natural id attribute. An immutable natural id is expected to never change its value.

If the value(s) of the natural id attribute(s) change, @NaturalId(mutable=true) should be used instead.

Example 232. Mutable natural id mapping
@Entity(name = "Author")
public static class Author {

    @Id
    private Long id;

    private String name;

    @NaturalId(mutable = true)
    private String email;

    //Getters and setters are omitted for brevity
}

Within the Session, Hibernate maintains a mapping from natural id values to entity identifiers (PK) values. If natural ids values changed, it is possible for this mapping to become out of date until a flush occurs.

To work around this condition, Hibernate will attempt to discover any such pending changes and adjust them when the load() or getReference() methods are executed. To be clear: this is only pertinent for mutable natural ids.

This discovery and adjustment have a performance impact. If an application is certain that none of its mutable natural ids already associated with the Session have changed, it can disable that checking by calling setSynchronizationEnabled(false) (the default is true). This will force Hibernate to circumvent the checking of mutable natural ids.

Example 233. Mutable natural id synchronization use-case
Author author = entityManager
    .unwrap(Session.class)
    .bySimpleNaturalId( Author.class )
    .load( "john@acme.com" );

author.setEmail( "john.doe@acme.com" );

assertNull(
    entityManager
        .unwrap(Session.class)
        .bySimpleNaturalId( Author.class )
        .setSynchronizationEnabled( false )
        .load( "john.doe@acme.com" )
);

assertSame( author,
    entityManager
        .unwrap(Session.class)
        .bySimpleNaturalId( Author.class )
        .setSynchronizationEnabled( true )
        .load( "john.doe@acme.com" )
);

Not only can this NaturalId-to-PK resolution be cached in the Session, but we can also have it cached in the second-level cache if second level caching is enabled.

Example 234. Natural id caching
@Entity(name = "Book")
@NaturalIdCache
public static class Book {

    @Id
    private Long id;

    private String title;

    private String author;

    @NaturalId
    private String isbn;

    //Getters and setters are omitted for brevity
}

2.10. Dynamic Model

JPA only acknowledges the entity model mapping so, if you are concerned about JPA provider portability, it’s best to stick to the strict POJO model. On the other hand, Hibernate can work with both POJO entities as well as with dynamic entity models.

2.10.1. Dynamic mapping models

Persistent entities do not necessarily have to be represented as POJO/JavaBean classes. Hibernate also supports dynamic models (using Map of Maps at runtime). With this approach, you do not write persistent classes, only mapping files.

A given entity has just one entity mode within a given SessionFactory. This is a change from previous versions which allowed to define multiple entity modes for an entity and to select which to load. Entity modes can now be mixed within a domain model; a dynamic entity might reference a POJO entity, and vice versa.

Example 235. Dynamic domain model Hibernate mapping
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class entity-name="Book">
        <id name="isbn" column="isbn" length="32" type="string"/>

        <property name="title" not-null="true" length="50" type="string"/>

        <property name="author" not-null="true" length="50" type="string"/>

    </class>
</hibernate-mapping>

After you defined your entity mapping, you need to instruct Hibernate to use the dynamic mapping mode:

Example 236. Dynamic domain model Hibernate mapping
settings.put( "hibernate.default_entity_mode", "dynamic-map" );

When you are going to save the following Book dynamic entity, Hibernate is going to generate the following SQL statement:

Example 237. Persist dynamic entity
Map<String, String> book = new HashMap<>();
book.put( "isbn", "978-9730228236" );
book.put( "title", "High-Performance Java Persistence" );
book.put( "author", "Vlad Mihalcea" );

entityManager
    .unwrap(Session.class)
    .save( "Book", book );
insert
into
    Book
    (title, author, isbn)
values
    (?, ?, ?)

-- binding parameter [1] as [VARCHAR] - [High-Performance Java Persistence]
-- binding parameter [2] as [VARCHAR] - [Vlad Mihalcea]
-- binding parameter [3] as [VARCHAR] - [978-9730228236]

The main advantage of dynamic models is quick turnaround time for prototyping without the need for entity class implementation. The main down-fall is that you lose compile-time type checking and will likely deal with many exceptions at runtime. However, as a result of the Hibernate mapping, the database schema can easily be normalized and sound, allowing to add a proper domain model implementation on top later on.

It is also interesting to note that dynamic models are great for certain integration use cases as well. Envers, for example, makes extensive use of dynamic models to represent the historical data.

2.11. Inheritance

Although relational database systems don’t provide support for inheritance, Hibernate provides several strategies to leverage this object-oriented trait onto domain model entities:

MappedSuperclass

Inheritance is implemented in domain model only without reflecting it in the database schema. See MappedSuperclass.

Single table

The domain model class hierarchy is materialized into a single table which contains entities belonging to different class types. See Single table.

Joined table

The base class and all the subclasses have their own database tables and fetching a subclass entity requires a join with the parent table as well. See Joined table.

Table per class

Each subclass has its own table containing both the subclass and the base class properties. See Table per class.

2.11.1. MappedSuperclass

In the following domain model class hierarchy, a 'DebitAccount' and a 'CreditAccount' share the same 'Account' base class.

Inheritance class diagram

When using MappedSuperclass, the inheritance is visible in the domain model only and each database table contains both the base class and the subclass properties.

Example 238. @MappedSuperclass inheritance
@MappedSuperclass
public static class Account {

    @Id
    private Long id;

    private String owner;

    private BigDecimal balance;

    private BigDecimal interestRate;

    public Long getId() {
        return id;
    }

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

    public String getOwner() {
        return owner;
    }

    public void setOwner(String owner) {
        this.owner = owner;
    }

    public BigDecimal getBalance() {
        return balance;
    }

    public void setBalance(BigDecimal balance) {
        this.balance = balance;
    }

    public BigDecimal getInterestRate() {
        return interestRate;
    }

    public void setInterestRate(BigDecimal interestRate) {
        this.interestRate = interestRate;
    }
}

@Entity(name = "DebitAccount")
public static class DebitAccount extends Account {

    private BigDecimal overdraftFee;

    public BigDecimal getOverdraftFee() {
        return overdraftFee;
    }

    public void setOverdraftFee(BigDecimal overdraftFee) {
        this.overdraftFee = overdraftFee;
    }
}

@Entity(name = "CreditAccount")
public static class CreditAccount extends Account {

    private BigDecimal creditLimit;

    public BigDecimal getCreditLimit() {
        return creditLimit;
    }

    public void setCreditLimit(BigDecimal creditLimit) {
        this.creditLimit = creditLimit;
    }
}
CREATE TABLE DebitAccount (
    id BIGINT NOT NULL ,
    balance NUMERIC(19, 2) ,
    interestRate NUMERIC(19, 2) ,
    owner VARCHAR(255) ,
    overdraftFee NUMERIC(19, 2) ,
    PRIMARY KEY ( id )
)

CREATE TABLE CreditAccount (
    id BIGINT NOT NULL ,
    balance NUMERIC(19, 2) ,
    interestRate NUMERIC(19, 2) ,
    owner VARCHAR(255) ,
    creditLimit NUMERIC(19, 2) ,
    PRIMARY KEY ( id )
)

Because the @MappedSuperclass inheritance model is not mirrored at database level, it’s not possible to use polymorphic queries (fetching subclasses by their base class).

2.11.2. Single table

The single table inheritance strategy maps all subclasses to only one database table. Each subclass declares its own persistent properties. Version and id properties are assumed to be inherited from the root class.

When omitting an explicit inheritance strategy (e.g. @Inheritance), JPA will choose the SINGLE_TABLE strategy by default.

Example 239. Single Table inheritance
@Entity(name = "Account")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public static class Account {

    @Id
    private Long id;

    private String owner;

    private BigDecimal balance;

    private BigDecimal interestRate;

    public Long getId() {
        return id;
    }

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

    public String getOwner() {
        return owner;
    }

    public void setOwner(String owner) {
        this.owner = owner;
    }

    public BigDecimal getBalance() {
        return balance;
    }

    public void setBalance(BigDecimal balance) {
        this.balance = balance;
    }

    public BigDecimal getInterestRate() {
        return interestRate;
    }

    public void setInterestRate(BigDecimal interestRate) {
        this.interestRate = interestRate;
    }
}

@Entity(name = "DebitAccount")
public static class DebitAccount extends Account {

    private BigDecimal overdraftFee;

    public BigDecimal getOverdraftFee() {
        return overdraftFee;
    }

    public void setOverdraftFee(BigDecimal overdraftFee) {
        this.overdraftFee = overdraftFee;
    }
}

@Entity(name = "CreditAccount")
public static class CreditAccount extends Account {

    private BigDecimal creditLimit;

    public BigDecimal getCreditLimit() {
        return creditLimit;
    }

    public void setCreditLimit(BigDecimal creditLimit) {
        this.creditLimit = creditLimit;
    }
}
CREATE TABLE Account (
    DTYPE VARCHAR(31) NOT NULL ,
    id BIGINT NOT NULL ,
    balance NUMERIC(19, 2) ,
    interestRate NUMERIC(19, 2) ,
    owner VARCHAR(255) ,
    overdraftFee NUMERIC(19, 2) ,
    creditLimit NUMERIC(19, 2) ,
    PRIMARY KEY ( id )
)

Each subclass in a hierarchy must define a unique discriminator value, which is used to differentiate between rows belonging to separate subclass types. If this is not specified, the DTYPE column is used as a discriminator, storing the associated subclass name.

Example 240. Single Table inheritance discriminator column
DebitAccount debitAccount = new DebitAccount();
debitAccount.setId( 1L );
debitAccount.setOwner( "John Doe" );
debitAccount.setBalance( BigDecimal.valueOf( 100 ) );
debitAccount.setInterestRate( BigDecimal.valueOf( 1.5d ) );
debitAccount.setOverdraftFee( BigDecimal.valueOf( 25 ) );

CreditAccount creditAccount = new CreditAccount();
creditAccount.setId( 2L );
creditAccount.setOwner( "John Doe" );
creditAccount.setBalance( BigDecimal.valueOf( 1000 ) );
creditAccount.setInterestRate( BigDecimal.valueOf( 1.9d ) );
creditAccount.setCreditLimit( BigDecimal.valueOf( 5000 ) );

entityManager.persist( debitAccount );
entityManager.persist( creditAccount );
INSERT INTO Account (balance, interestRate, owner, overdraftFee, DTYPE, id)
VALUES (100, 1.5, 'John Doe', 25, 'DebitAccount', 1)

INSERT INTO Account (balance, interestRate, owner, creditLimit, DTYPE, id)
VALUES (1000, 1.9, 'John Doe', 5000, 'CreditAccount', 2)

When using polymorphic queries, only a single table is required to be scanned to fetch all associated subclass instances.

Example 241. Single Table polymorphic query
List<Account> accounts = entityManager
    .createQuery( "select a from Account a" )
    .getResultList();
SELECT  singletabl0_.id AS id2_0_ ,
        singletabl0_.balance AS balance3_0_ ,
        singletabl0_.interestRate AS interest4_0_ ,
        singletabl0_.owner AS owner5_0_ ,
        singletabl0_.overdraftFee AS overdraf6_0_ ,
        singletabl0_.creditLimit AS creditLi7_0_ ,
        singletabl0_.DTYPE AS DTYPE1_0_
FROM    Account singletabl0_

Among all other inheritance alternatives, the single table strategy performs the best since it requires access to one table only. Because all subclass columns are stored in a single table, it’s not possible to use NOT NULL constraints anymore, so integrity checks must be moved either into the data access layer or enforced through CHECK or TRIGGER constraints.

Discriminator

The discriminator column contains marker values that tell the persistence layer what subclass to instantiate for a particular row. Hibernate Core supports the following restricted set of types as discriminator column: String, char, int, byte, short, boolean(including yes_no, true_false).

Use the @DiscriminatorColumn to define the discriminator column as well as the discriminator type.

The enum DiscriminatorType used in javax.persistence.DiscriminatorColumn only contains the values STRING, CHAR and INTEGER which means that not all Hibernate supported types are available via the @DiscriminatorColumn annotation. You can also use @DiscriminatorFormula to express in SQL a virtual discriminator column. This is particularly useful when the discriminator value can be extracted from one or more columns of the table. Both @DiscriminatorColumn and @DiscriminatorFormula are to be set on the root entity (once per persisted hierarchy).

@org.hibernate.annotations.DiscriminatorOptions allows to optionally specify Hibernate specific discriminator options which are not standardized in JPA. The available options are force and insert.

The force attribute is useful if the table contains rows with extra discriminator values that are not mapped to a persistent class. This could for example occur when working with a legacy database. If force is set to true Hibernate will specify the allowed discriminator values in the SELECT query, even when retrieving all instances of the root class.

The second option, insert, tells Hibernate whether or not to include the discriminator column in SQL INSERTs. Usually, the column should be part of the INSERT statement, but if your discriminator column is also part of a mapped composite identifier you have to set this option to false.

There used to be @org.hibernate.annotations.ForceDiscriminator annotation which was deprecated in version 3.6 and later removed. Use @DiscriminatorOptions instead.

Discriminator formula

Assuming a legacy database schema where the discriminator is based on inspecting a certain column, we can take advantage of the Hibernate specific @DiscriminatorFormula annotation and map the inheritance model as follows:

Example 242. Single Table discriminator formula
@Entity(name = "Account")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorFormula(
    "case when debitKey is not null " +
    "then 'Debit' " +
    "else ( " +
    "   case when creditKey is not null " +
    "   then 'Credit' " +
    "   else 'Unknown' " +
    "   end ) " +
    "end "
)
public static class Account {

    @Id
    private Long id;

    private String owner;

    private BigDecimal balance;

    private BigDecimal interestRate;

    public Long getId() {
        return id;
    }

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

    public String getOwner() {
        return owner;
    }

    public void setOwner(String owner) {
        this.owner = owner;
    }

    public BigDecimal getBalance() {
        return balance;
    }

    public void setBalance(BigDecimal balance) {
        this.balance = balance;
    }

    public BigDecimal getInterestRate() {
        return interestRate;
    }

    public void setInterestRate(BigDecimal interestRate) {
        this.interestRate = interestRate;
    }
}

@Entity(name = "DebitAccount")
@DiscriminatorValue(value = "Debit")
public static class DebitAccount extends Account {

    private String debitKey;

    private BigDecimal overdraftFee;

    private DebitAccount() {
    }

    public DebitAccount(String debitKey) {
        this.debitKey = debitKey;
    }

    public String getDebitKey() {
        return debitKey;
    }

    public BigDecimal getOverdraftFee() {
        return overdraftFee;
    }

    public void setOverdraftFee(BigDecimal overdraftFee) {
        this.overdraftFee = overdraftFee;
    }
}

@Entity(name = "CreditAccount")
@DiscriminatorValue(value = "Credit")
public static class CreditAccount extends Account {

    private String creditKey;

    private BigDecimal creditLimit;

    private CreditAccount() {
    }

    public CreditAccount(String creditKey) {
        this.creditKey = creditKey;
    }

    public String getCreditKey() {
        return creditKey;
    }

    public BigDecimal getCreditLimit() {
        return creditLimit;
    }

    public void setCreditLimit(BigDecimal creditLimit) {
        this.creditLimit = creditLimit;
    }
}
CREATE TABLE Account (
    id int8 NOT NULL ,
    balance NUMERIC(19, 2) ,
    interestRate NUMERIC(19, 2) ,
    owner VARCHAR(255) ,
    debitKey VARCHAR(255) ,
    overdraftFee NUMERIC(19, 2) ,
    creditKey VARCHAR(255) ,
    creditLimit NUMERIC(19, 2) ,
    PRIMARY KEY ( id )
)

The @DiscriminatorFormula defines a custom SQL clause that can be used to identify a certain subclass type. The @DiscriminatorValue defines the mapping between the result of the @DiscriminatorFormula and the inheritance subclass type.

Implicit discriminator values

Aside from the usual discriminator values assigned to each individual subclass type, the @DiscriminatorValue can take two additional values:

null

If the underlying discriminator column is null, the null discriminator mapping is going to be used.

not null

If the underlying discriminator column has a not-null value that is not explicitly mapped to any entity, the not-null discriminator mapping used.

To understand how these two values work, consider the following entity mapping:

Example 243. @DiscriminatorValue null and not-null entity mapping
@Entity(name = "Account")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorValue( "null" )
public static class Account {

    @Id
    private Long id;

    private String owner;

    private BigDecimal balance;

    private BigDecimal interestRate;

    public Long getId() {
        return id;
    }

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

    public String getOwner() {
        return owner;
    }

    public void setOwner(String owner) {
        this.owner = owner;
    }

    public BigDecimal getBalance() {
        return balance;
    }

    public void setBalance(BigDecimal balance) {
        this.balance = balance;
    }

    public BigDecimal getInterestRate() {
        return interestRate;
    }

    public void setInterestRate(BigDecimal interestRate) {
        this.interestRate = interestRate;
    }
}

@Entity(name = "DebitAccount")
@DiscriminatorValue( "Debit" )
public static class DebitAccount extends Account {

    private BigDecimal overdraftFee;

    public BigDecimal getOverdraftFee() {
        return overdraftFee;
    }

    public void setOverdraftFee(BigDecimal overdraftFee) {
        this.overdraftFee = overdraftFee;
    }
}

@Entity(name = "CreditAccount")
@DiscriminatorValue( "Credit" )
public static class CreditAccount extends Account {

    private BigDecimal creditLimit;

    public BigDecimal getCreditLimit() {
        return creditLimit;
    }

    public void setCreditLimit(BigDecimal creditLimit) {
        this.creditLimit = creditLimit;
    }
}

@Entity(name = "OtherAccount")
@DiscriminatorValue( "not null" )
public static class OtherAccount extends Account {

    private boolean active;

    public boolean isActive() {
        return active;
    }

    public void setActive(boolean active) {
        this.active = active;
    }
}

The Account class has a @DiscriminatorValue( "null" ) mapping, meaning that any account row which does not contain any discriminator value will be mapped to an Account base class entity. The DebitAccount and CreditAccount entities use explicit discriminator values. The OtherAccount entity is used as a generic account type because it maps any database row whose discriminator column is not explicitly assigned to any other entity in the current inheritance tree.

To visualize how it works, consider the following example:

Example 244. @DiscriminatorValue null and not-null entity persistence
DebitAccount debitAccount = new DebitAccount();
debitAccount.setId( 1L );
debitAccount.setOwner( "John Doe" );
debitAccount.setBalance( BigDecimal.valueOf( 100 ) );
debitAccount.setInterestRate( BigDecimal.valueOf( 1.5d ) );
debitAccount.setOverdraftFee( BigDecimal.valueOf( 25 ) );

CreditAccount creditAccount = new CreditAccount();
creditAccount.setId( 2L );
creditAccount.setOwner( "John Doe" );
creditAccount.setBalance( BigDecimal.valueOf( 1000 ) );
creditAccount.setInterestRate( BigDecimal.valueOf( 1.9d ) );
creditAccount.setCreditLimit( BigDecimal.valueOf( 5000 ) );

Account account = new Account();
account.setId( 3L );
account.setOwner( "John Doe" );
account.setBalance( BigDecimal.valueOf( 1000 ) );
account.setInterestRate( BigDecimal.valueOf( 1.9d ) );

entityManager.persist( debitAccount );
entityManager.persist( creditAccount );
entityManager.persist( account );

entityManager.unwrap( Session.class ).doWork( connection -> {
    try(Statement statement = connection.createStatement()) {
        statement.executeUpdate(
            "insert into Account (DTYPE, active, balance, interestRate, owner, id) " +
            "values ('Other', true, 25, 0.5, 'Vlad', 4)"
        );
    }
} );

Map<Long, Account> accounts = entityManager.createQuery(
    "select a from Account a", Account.class )
.getResultList()
.stream()
.collect( Collectors.toMap( Account::getId, Function.identity()));

assertEquals(4, accounts.size());
assertEquals( DebitAccount.class, accounts.get( 1L ).getClass() );
assertEquals( CreditAccount.class, accounts.get( 2L ).getClass() );
assertEquals( Account.class, accounts.get( 3L ).getClass() );
assertEquals( OtherAccount.class, accounts.get( 4L ).getClass() );
INSERT INTO Account (balance, interestRate, owner, overdraftFee, DTYPE, id)
VALUES (100, 1.5, 'John Doe', 25, 'Debit', 1)

INSERT INTO Account (balance, interestRate, owner, overdraftFee, DTYPE, id)
VALUES (1000, 1.9, 'John Doe', 5000, 'Credit', 2)

INSERT INTO Account (balance, interestRate, owner, id)
VALUES (1000, 1.9, 'John Doe', 3)

INSERT INTO Account (DTYPE, active, balance, interestRate, owner, id)
VALUES ('Other', true, 25, 0.5, 'Vlad', 4)

SELECT a.id as id2_0_,
       a.balance as balance3_0_,
       a.interestRate as interest4_0_,
       a.owner as owner5_0_,
       a.overdraftFee as overdraf6_0_,
       a.creditLimit as creditLi7_0_,
       a.active as active8_0_,
       a.DTYPE as DTYPE1_0_
FROM   Account a

As you can see, the Account entity row has a value of NULL in the DTYPE discriminator column, while the OtherAccount entity was saved with a DTYPE column value of other which has not explicit mapping.

2.11.3. Joined table

Each subclass can also be mapped to its own table. This is also called table-per-subclass mapping strategy. An inherited state is retrieved by joining with the table of the superclass.

A discriminator column is not required for this mapping strategy. Each subclass must, however, declare a table column holding the object identifier.

Example 245. Join Table
@Entity(name = "Account")
@Inheritance(strategy = InheritanceType.JOINED)
public static class Account {

    @Id
    private Long id;

    private String owner;

    private BigDecimal balance;

    private BigDecimal interestRate;

    public Long getId() {
        return id;
    }

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

    public String getOwner() {
        return owner;
    }

    public void setOwner(String owner) {
        this.owner = owner;
    }

    public BigDecimal getBalance() {
        return balance;
    }

    public void setBalance(BigDecimal balance) {
        this.balance = balance;
    }

    public BigDecimal getInterestRate() {
        return interestRate;
    }

    public void setInterestRate(BigDecimal interestRate) {
        this.interestRate = interestRate;
    }
}

@Entity(name = "DebitAccount")
public static class DebitAccount extends Account {

    private BigDecimal overdraftFee;

    public BigDecimal getOverdraftFee() {
        return overdraftFee;
    }

    public void setOverdraftFee(BigDecimal overdraftFee) {
        this.overdraftFee = overdraftFee;
    }
}

@Entity(name = "CreditAccount")
public static class CreditAccount extends Account {

    private BigDecimal creditLimit;

    public BigDecimal getCreditLimit() {
        return creditLimit;
    }

    public void setCreditLimit(BigDecimal creditLimit) {
        this.creditLimit = creditLimit;
    }
}
CREATE TABLE Account (
    id BIGINT NOT NULL ,
    balance NUMERIC(19, 2) ,
    interestRate NUMERIC(19, 2) ,
    owner VARCHAR(255) ,
    PRIMARY KEY ( id )
)

CREATE TABLE CreditAccount (
    creditLimit NUMERIC(19, 2) ,
    id BIGINT NOT NULL ,
    PRIMARY KEY ( id )
)

CREATE TABLE DebitAccount (
    overdraftFee NUMERIC(19, 2) ,
    id BIGINT NOT NULL ,
    PRIMARY KEY ( id )
)

ALTER TABLE CreditAccount
ADD CONSTRAINT FKihw8h3j1k0w31cnyu7jcl7n7n
FOREIGN KEY (id) REFERENCES Account

ALTER TABLE DebitAccount
ADD CONSTRAINT FKia914478noepymc468kiaivqm
FOREIGN KEY (id) REFERENCES Account

The primary key of this table is also a foreign key to the superclass table and described by the @PrimaryKeyJoinColumns.

The table name still defaults to the non-qualified class name. Also, if @PrimaryKeyJoinColumn is not set, the primary key / foreign key columns are assumed to have the same names as the primary key columns of the primary table of the superclass.

Example 246. Join Table with @PrimaryKeyJoinColumn
@Entity(name = "Account")
@Inheritance(strategy = InheritanceType.JOINED)
public static class Account {

    @Id
    private Long id;

    private String owner;

    private BigDecimal balance;

    private BigDecimal interestRate;

    public Long getId() {
        return id;
    }

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

    public String getOwner() {
        return owner;
    }

    public void setOwner(String owner) {
        this.owner = owner;
    }

    public BigDecimal getBalance() {
        return balance;
    }

    public void setBalance(BigDecimal balance) {
        this.balance = balance;
    }

    public BigDecimal getInterestRate() {
        return interestRate;
    }

    public void setInterestRate(BigDecimal interestRate) {
        this.interestRate = interestRate;
    }
}

@Entity(name = "DebitAccount")
@PrimaryKeyJoinColumn(name = "account_id")
public static class DebitAccount extends Account {

    private BigDecimal overdraftFee;

    public BigDecimal getOverdraftFee() {
        return overdraftFee;
    }

    public void setOverdraftFee(BigDecimal overdraftFee) {
        this.overdraftFee = overdraftFee;
    }
}

@Entity(name = "CreditAccount")
@PrimaryKeyJoinColumn(name = "account_id")
public static class CreditAccount extends Account {

    private BigDecimal creditLimit;

    public BigDecimal getCreditLimit() {
        return creditLimit;
    }

    public void setCreditLimit(BigDecimal creditLimit) {
        this.creditLimit = creditLimit;
    }
}
CREATE TABLE CreditAccount (
    creditLimit NUMERIC(19, 2) ,
    account_id BIGINT NOT NULL ,
    PRIMARY KEY ( account_id )
)

CREATE TABLE DebitAccount (
    overdraftFee NUMERIC(19, 2) ,
    account_id BIGINT NOT NULL ,
    PRIMARY KEY ( account_id )
)

ALTER TABLE CreditAccount
ADD CONSTRAINT FK8ulmk1wgs5x7igo370jt0q005
FOREIGN KEY (account_id) REFERENCES Account

ALTER TABLE DebitAccount
ADD CONSTRAINT FK7wjufa570onoidv4omkkru06j
FOREIGN KEY (account_id) REFERENCES Account

When using polymorphic queries, the base class table must be joined with all subclass tables to fetch every associated subclass instance.

Example 247. Join Table polymorphic query
List<Account> accounts = entityManager
    .createQuery( "select a from Account a" )
    .getResultList();
SELECT jointablet0_.id AS id1_0_ ,
       jointablet0_.balance AS balance2_0_ ,
       jointablet0_.interestRate AS interest3_0_ ,
       jointablet0_.owner AS owner4_0_ ,
       jointablet0_1_.overdraftFee AS overdraf1_2_ ,
       jointablet0_2_.creditLimit AS creditLi1_1_ ,
       CASE WHEN jointablet0_1_.id IS NOT NULL THEN 1
            WHEN jointablet0_2_.id IS NOT NULL THEN 2
            WHEN jointablet0_.id IS NOT NULL THEN 0
       END AS clazz_
FROM   Account jointablet0_
       LEFT OUTER JOIN DebitAccount jointablet0_1_ ON jointablet0_.id = jointablet0_1_.id
       LEFT OUTER JOIN CreditAccount jointablet0_2_ ON jointablet0_.id = jointablet0_2_.id

The joined table inheritance polymorphic queries can use several JOINS which might affect performance when fetching a large number of entities.

2.11.4. Table per class

A third option is to map only the concrete classes of an inheritance hierarchy to tables. This is called the table-per-concrete-class strategy. Each table defines all persistent states of the class, including the inherited state.

In Hibernate, it is not necessary to explicitly map such inheritance hierarchies. You can map each class as a separate entity root. However, if you wish use polymorphic associations (e.g. an association to the superclass of your hierarchy), you need to use the union subclass mapping.

Example 248. Table per class
@Entity(name = "Account")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public static class Account {

    @Id
    private Long id;

    private String owner;

    private BigDecimal balance;

    private BigDecimal interestRate;

    public Long getId() {
        return id;
    }

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

    public String getOwner() {
        return owner;
    }

    public void setOwner(String owner) {
        this.owner = owner;
    }

    public BigDecimal getBalance() {
        return balance;
    }

    public void setBalance(BigDecimal balance) {
        this.balance = balance;
    }

    public BigDecimal getInterestRate() {
        return interestRate;
    }

    public void setInterestRate(BigDecimal interestRate) {
        this.interestRate = interestRate;
    }
}

@Entity(name = "DebitAccount")
public static class DebitAccount extends Account {

    private BigDecimal overdraftFee;

    public BigDecimal getOverdraftFee() {
        return overdraftFee;
    }

    public void setOverdraftFee(BigDecimal overdraftFee) {
        this.overdraftFee = overdraftFee;
    }
}

@Entity(name = "CreditAccount")
public static class CreditAccount extends Account {

    private BigDecimal creditLimit;

    public BigDecimal getCreditLimit() {
        return creditLimit;
    }

    public void setCreditLimit(BigDecimal creditLimit) {
        this.creditLimit = creditLimit;
    }
}
CREATE TABLE Account (
    id BIGINT NOT NULL ,
    balance NUMERIC(19, 2) ,
    interestRate NUMERIC(19, 2) ,
    owner VARCHAR(255) ,
    PRIMARY KEY ( id )
)

CREATE TABLE CreditAccount (
    id BIGINT NOT NULL ,
    balance NUMERIC(19, 2) ,
    interestRate NUMERIC(19, 2) ,
    owner VARCHAR(255) ,
    creditLimit NUMERIC(19, 2) ,
    PRIMARY KEY ( id )
)

CREATE TABLE DebitAccount (
    id BIGINT NOT NULL ,
    balance NUMERIC(19, 2) ,
    interestRate NUMERIC(19, 2) ,
    owner VARCHAR(255) ,
    overdraftFee NUMERIC(19, 2) ,
    PRIMARY KEY ( id )
)

When using polymorphic queries, a UNION is required to fetch the base class table along with all subclass tables as well.

Example 249. Table per class polymorphic query
List<Account> accounts = entityManager
    .createQuery( "select a from Account a" )
    .getResultList();
SELECT tablepercl0_.id AS id1_0_ ,
       tablepercl0_.balance AS balance2_0_ ,
       tablepercl0_.interestRate AS interest3_0_ ,
       tablepercl0_.owner AS owner4_0_ ,
       tablepercl0_.overdraftFee AS overdraf1_2_ ,
       tablepercl0_.creditLimit AS creditLi1_1_ ,
       tablepercl0_.clazz_ AS clazz_
FROM (
    SELECT    id ,
             balance ,
             interestRate ,
             owner ,
             CAST(NULL AS INT) AS overdraftFee ,
             CAST(NULL AS INT) AS creditLimit ,
             0 AS clazz_
    FROM     Account
    UNION ALL
    SELECT   id ,
             balance ,
             interestRate ,
             owner ,
             overdraftFee ,
             CAST(NULL AS INT) AS creditLimit ,
             1 AS clazz_
    FROM     DebitAccount
    UNION ALL
    SELECT   id ,
             balance ,
             interestRate ,
             owner ,
             CAST(NULL AS INT) AS overdraftFee ,
             creditLimit ,
             2 AS clazz_
    FROM     CreditAccount
) tablepercl0_

Polymorphic queries require multiple UNION queries, so be aware of the performance implications of a large class hierarchy.

2.11.5. Implicit and explicit polymorphism

By default, when you query a base class entity, the polymorphic query will fetch all subclasses belonging to the base type.

However, you can even query interfaces or base classes that don’t belong to the JPA entity inheritance model.

For instance, considering the following DomainModelEntity interface:

Example 250. Domain Model Entity interface
public interface DomainModelEntity<ID> {

    ID getId();

    Integer getVersion();
}

If we have two entity mappings, a Book and a Blog, and the Book entity is mapped with the @Polymorphism annotation and taking the PolymorphismType.EXPLICIT setting:

Example 251. @Polymorphism entity mapping
@Entity(name = "Event")
public static class Book implements DomainModelEntity<Long> {

    @Id
    private Long id;

    @Version
    private Integer version;

    private String title;

    private String author;

    //Getter and setters omitted for brevity
}

@Entity(name = "Blog")
@Polymorphism(type = PolymorphismType.EXPLICIT)
public static class Blog implements DomainModelEntity<Long> {

    @Id
    private Long id;

    @Version
    private Integer version;

    private String site;

    //Getter and setters omitted for brevity
}

If we have the following entity objects in our system:

Example 252. Domain Model entity objects
Book book = new Book();
book.setId( 1L );
book.setAuthor( "Vlad Mihalcea" );
book.setTitle( "High-Performance Java Persistence" );
entityManager.persist( book );

Blog blog = new Blog();
blog.setId( 1L );
blog.setSite( "vladmihalcea.com" );
entityManager.persist( blog );

We can now query against the DomainModelEntity interface, and Hibernate is going to fetch only the entities that are either mapped with @Polymorphism(type = PolymorphismType.IMPLICIT) or they are not annotated at all with the @Polymorphism annotation (implying the IMPLICIT behavior):

Example 253. Fetching Domain Model entities using non-mapped base class polymorphism
List<DomainModelEntity> accounts = entityManager
.createQuery(
    "select e " +
    "from org.hibernate.userguide.inheritance.polymorphism.DomainModelEntity e" )
.getResultList();

assertEquals(1, accounts.size());
assertTrue( accounts.get( 0 ) instanceof Book );

Therefore, only the Book was fetched since the Blog entity was marked with the @Polymorphism(type = PolymorphismType.EXPLICIT) annotation, which instructs Hibernate to skip it when executing a polymorphic query against a non-mapped base class.

2.12. Immutability

Immutability can be specified for both entities and collections.

2.12.1. Entity immutability

If a specific entity is immutable, it is good practice to mark it with the @Immutable annotation.

Example 254. Immutable entity
@Entity(name = "Event")
@Immutable
public static class Event {

    @Id
    private Long id;

    private Date createdOn;

    private String message;

    //Getters and setters are omitted for brevity

}

Internally, Hibernate is going to perform several optimizations, such as:

  • reducing memory footprint since there is no need to retain the dehydrated state for the dirty checking mechanism

  • speeding-up the Persistence Context flushing phase since immutable entities can skip the dirty checking process

Considering the following entity is persisted in the database:

Example 255. Persisting an immutable entity
doInJPA( this::entityManagerFactory, entityManager -> {
    Event event = new Event();
    event.setId( 1L );
    event.setCreatedOn( new Date( ) );
    event.setMessage( "Hibernate User Guide rocks!" );

    entityManager.persist( event );
} );

When loading the entity and trying to change its state, Hibernate will skip any modification, therefore no SQL UPDATE statement is executed.

Example 256. The immutable entity ignores any update
doInJPA( this::entityManagerFactory, entityManager -> {
    Event event = entityManager.find( Event.class, 1L );
    log.info( "Change event message" );
    event.setMessage( "Hibernate User Guide" );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
    Event event = entityManager.find( Event.class, 1L );
    assertEquals("Hibernate User Guide rocks!", event.getMessage());
} );
SELECT e.id AS id1_0_0_,
       e.createdOn AS createdO2_0_0_,
       e.message AS message3_0_0_
FROM   event e
WHERE  e.id = 1

-- Change event message

SELECT e.id AS id1_0_0_,
       e.createdOn AS createdO2_0_0_,
       e.message AS message3_0_0_
FROM   event e
WHERE  e.id = 1

2.12.2. Collection immutability

Just like entities, collections can also be marked with the @Immutable annotation.

Considering the following entity mappings:

Example 257. Immutable collection
@Entity(name = "Batch")
public static class Batch {

    @Id
    private Long id;

    private String name;

    @OneToMany(cascade = CascadeType.ALL)
    @Immutable
    private List<Event> events = new ArrayList<>( );

    //Getters and setters are omitted for brevity

}

@Entity(name = "Event")
@Immutable
public static class Event {

    @Id
    private Long id;

    private Date createdOn;

    private String message;

    //Getters and setters are omitted for brevity

}

This time, not only the Event entity is immutable, but the Event collection stored by the Batch parent entity. Once the immutable collection is created, it can never be modified.

Example 258. Persisting an immutable collection
doInJPA( this::entityManagerFactory, entityManager -> {
    Batch batch = new Batch();
    batch.setId( 1L );
    batch.setName( "Change request" );

    Event event1 = new Event();
    event1.setId( 1L );
    event1.setCreatedOn( new Date( ) );
    event1.setMessage( "Update Hibernate User Guide" );

    Event event2 = new Event();
    event2.setId( 2L );
    event2.setCreatedOn( new Date( ) );
    event2.setMessage( "Update Hibernate Getting Started Guide" );

    batch.getEvents().add( event1 );
    batch.getEvents().add( event2 );

    entityManager.persist( batch );
} );

The Batch entity is mutable. Only the events collection is immutable.

For instance, we can still modify the entity name:

Example 259. Changing the mutable entity
doInJPA( this::entityManagerFactory, entityManager -> {
    Batch batch = entityManager.find( Batch.class, 1L );
    log.info( "Change batch name" );
    batch.setName( "Proposed change request" );
} );
SELECT b.id AS id1_0_0_,
       b.name AS name2_0_0_
FROM   Batch b
WHERE  b.id = 1

-- Change batch name

UPDATE batch
SET    name = 'Proposed change request'
WHERE  id = 1

However, when trying to modify the events collection:

Example 260. Immutable collections cannot be modified
try {
    doInJPA( this::entityManagerFactory, entityManager -> {
        Batch batch = entityManager.find( Batch.class, 1L );
        batch.getEvents().clear();
    } );
}
catch ( Exception e ) {
    log.error( "Immutable collections cannot be modified" );
}
Unresolved directive in chapters/domain/immutability.adoc - include::extras/immutability/collection-immutability-update-example.log[]

While immutable entity changes are simply discarded, modifying an immutable collection end up in a HibernateException being thrown.

3. Bootstrap

The term bootstrapping refers to initializing and starting a software component. In Hibernate, we are specifically talking about the process of building a fully functional SessionFactory instance or EntityManagerFactory instance, for JPA. The process is very different for each.

This chapter will not focus on all the possibilities of bootstrapping. Those will be covered in each specific more-relevant chapters later on. Instead, we focus here on the API calls needed to perform the bootstrapping.

During the bootstrap process, you might want to customize Hibernate behavior so make sure you check the Configurations section as well.

3.1. JPA Bootstrapping

Bootstrapping Hibernate as a JPA provider can be done in a JPA-spec compliant manner or using a proprietary bootstrapping approach. The standardized approach has some limitations in certain environments, but aside from those, it is highly recommended that you use JPA-standardized bootstrapping.

3.1.1. JPA-compliant bootstrapping

In JPA, we are ultimately interested in bootstrapping a javax.persistence.EntityManagerFactory instance. The JPA specification defines two primary standardized bootstrap approaches depending on how the application intends to access the javax.persistence.EntityManager instances from an EntityManagerFactory.

It uses the terms EE and SE for these two approaches, but those terms are very misleading in this context. What the JPA spec calls EE bootstrapping implies the existence of a container (EE, OSGi, etc), who’ll manage and inject the persistence context on behalf of the application. What it calls SE bootstrapping is everything else. We will use the terms container-bootstrapping and application-bootstrapping in this guide.

For compliant container-bootstrapping, the container will build an EntityManagerFactory for each persistent-unit defined in the META-INF/persistence.xml configuration file and make that available to the application for injection via the javax.persistence.PersistenceUnit annotation or via JNDI lookup.

Example 261. Injecting the default EntityManagerFactory
@PersistenceUnit
private EntityManagerFactory emf;

Or, in case you have multiple Persistence Units (e.g. multiple persistence.xml configuration files), you can inject a specific EntityManagerFactory by Unit name:

Example 262. Injecting a specific EntityManagerFactory
@PersistenceUnit(
    unitName = "CRM"
)
private EntityManagerFactory entityManagerFactory;

The META-INF/persistence.xml file looks as follows:

Example 263. META-INF/persistence.xml configuration file
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
             http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
             version="2.1">

    <persistence-unit name="CRM">
        <description>
            Persistence unit for Hibernate User Guide
        </description>

        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <class>org.hibernate.documentation.userguide.Document</class>

        <properties>
            <property name="javax.persistence.jdbc.driver"
                      value="org.h2.Driver" />

            <property name="javax.persistence.jdbc.url"
                      value="jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE" />

            <property name="javax.persistence.jdbc.user"
                      value="sa" />

            <property name="javax.persistence.jdbc.password"
                      value="" />

            <property name="hibernate.show_sql"
                      value="true" />

            <property name="hibernate.hbm2ddl.auto"
                      value="update" />
        </properties>

    </persistence-unit>

</persistence>

For compliant application-bootstrapping, rather than the container building the EntityManagerFactory for the application, the application builds the EntityManagerFactory itself using the javax.persistence.Persistence bootstrap class. The application creates an EntityManagerFactory by calling the createEntityManagerFactory method:

Example 264. Application bootstrapped EntityManagerFactory
// Create an EMF for our CRM persistence-unit.
EntityManagerFactory emf = Persistence.createEntityManagerFactory( "CRM" );

If you don’t want to provide a persistence.xml configuration file, JPA allows you to provide all the configuration options in a PersistenceUnitInfo implementation and call HibernatePersistenceProvider.html#createContainerEntityManagerFactory.

To inject the default Persistence Context, you can use the @PersistenceContext annotation.

Example 265. Inject the default EntityManager
@PersistenceContext
private EntityManager em;

To inject a specific Persistence Context, you can use the @PersistenceContext annotation, and you can even pass EntityManager-specific properties using the @PersistenceProperty annotation.

Example 266. Inject a configurable EntityManager
@PersistenceContext(
    unitName = "CRM",
    properties = {
        @PersistenceProperty(
            name="org.hibernate.flushMode",
            value= "MANUAL"
        )
    }
)
private EntityManager entityManager;

If you would like additional details on accessing and using EntityManager instances, sections 7.6 and 7.7 of the JPA 2.1 specification cover container-managed and application-managed EntityManagers, respectively.

3.1.2. Externalizing XML mapping files

JPA offers two mapping options:

  • annotations

  • XML mappings

Although annotations are much more common, there are projects were XML mappings are preferred. You can even mix annotations and XML mappings so that you can override annotation mappings with XML configurations that can be easily changed without recompiling the project source code. This is possible because if there are two conflicting mappings, the XML mappings takes precedence over its annotation counterpart.

The JPA specifications requires the XML mappings to be located on the class path:

An object/relational mapping XML file named orm.xml may be specified in the META-INF directory in the root of the persistence unit or in the META-INF directory of any jar file referenced by the persistence.xml.

Alternatively, or in addition, one or more mapping files may be referenced by the mapping-file elements of the persistence-unit element. These mapping files may be present anywhere on the class path.

— Section 8.2.1.6.2 of the JPA 2.1 Specification

Therefore, the mapping files can reside in the application jar artifacts, or they can be stored in an external folder location with the cogitation that that location be included in the class path.

Hibernate is more lenient in this regard so you can use any external location even outside of the application configured class path.

Example 267. META-INF/persistence.xml configuration file for external XML mappings
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
             http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
             version="2.1">

    <persistence-unit name="CRM">
        <description>
            Persistence unit for Hibernate User Guide
        </description>

        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <mapping-file>file:///etc/opt/app/mappings/orm.xml</mapping-file>

        <properties>
            <property name="javax.persistence.jdbc.driver"
                      value="org.h2.Driver" />

            <property name="javax.persistence.jdbc.url"
                      value="jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE" />

            <property name="javax.persistence.jdbc.user"
                      value="sa" />

            <property name="javax.persistence.jdbc.password"
                      value="" />

            <property name="hibernate.show_sql"
                      value="true" />

            <property name="hibernate.hbm2ddl.auto"
                      value="update" />
        </properties>

    </persistence-unit>

</persistence>

In the persistence.xml configuration file above, the orm.xml XML file containing all JPA entity mappings is located in the /etc/opt/app/mappings/ folder.

3.2. Native Bootstrapping

This section discusses the process of bootstrapping a Hibernate SessionFactory. Specifically it discusses the bootstrapping APIs as redesigned in 5.0. For a discussion of the legacy bootstrapping API, see Legacy Bootstrapping

3.2.1. Building the ServiceRegistry

The first step in native bootstrapping is the building of a ServiceRegistry holding the services Hibernate will need during bootstrapping and at run time.

Actually, we are concerned with building 2 different ServiceRegistries. First is the org.hibernate.boot.registry.BootstrapServiceRegistry. The BootstrapServiceRegistry is intended to hold services that Hibernate needs at both bootstrap and run time. This boils down to 3 services:

org.hibernate.boot.registry.classloading.spi.ClassLoaderService

which controls how Hibernate interacts with `ClassLoader`s

org.hibernate.integrator.spi.IntegratorService

which controls the management and discovery of org.hibernate.integrator.spi.Integrator instances.

org.hibernate.boot.registry.selector.spi.StrategySelector

which control how Hibernate resolves implementations of various strategy contracts. This is a very powerful service, but a full discussion of it is beyond the scope of this guide.

If you are ok with the default behavior of Hibernate in regards to these BootstrapServiceRegistry services (which is quite often the case, especially in stand-alone environments), then building the BootstrapServiceRegistry can be skipped.

If you wish to alter how the BootstrapServiceRegistry is built, that is controlled through the org.hibernate.boot.registry.BootstrapServiceRegistryBuilder:

Example 268. Controlling BootstrapServiceRegistry building
BootstrapServiceRegistryBuilder bootstrapRegistryBuilder =
    new BootstrapServiceRegistryBuilder();
// add a custom ClassLoader
bootstrapRegistryBuilder.applyClassLoader( customClassLoader );
// manually add an Integrator
bootstrapRegistryBuilder.applyIntegrator( customIntegrator );

BootstrapServiceRegistry bootstrapRegistry = bootstrapRegistryBuilder.build();

The services of the BootstrapServiceRegistry cannot be extended (added to) nor overridden (replaced).

The second ServiceRegistry is the org.hibernate.boot.registry.StandardServiceRegistry. You will almost always need to configure the StandardServiceRegistry, which is done through org.hibernate.boot.registry.StandardServiceRegistryBuilder:

Example 269. Building a BootstrapServiceRegistryBuilder
// An example using an implicitly built BootstrapServiceRegistry
StandardServiceRegistryBuilder standardRegistryBuilder =
    new StandardServiceRegistryBuilder();

// An example using an explicitly built BootstrapServiceRegistry
BootstrapServiceRegistry bootstrapRegistry =
    new BootstrapServiceRegistryBuilder().build();

StandardServiceRegistryBuilder standardRegistryBuilder =
    new StandardServiceRegistryBuilder( bootstrapRegistry );

A StandardServiceRegistry is also highly configurable via the StandardServiceRegistryBuilder API. See the StandardServiceRegistryBuilder Javadocs for more details.

Some specific methods of interest:

Example 270. Configuring a MetadataSources
ServiceRegistry standardRegistry =
        new StandardServiceRegistryBuilder().build();

MetadataSources sources = new MetadataSources( standardRegistry );

// alternatively, we can build the MetadataSources without passing
// a service registry, in which case it will build a default
// BootstrapServiceRegistry to use.  But the approach shown
// above is preferred
// MetadataSources sources = new MetadataSources();

// add a class using JPA/Hibernate annotations for mapping
sources.addAnnotatedClass( MyEntity.class );

// add the name of a class using JPA/Hibernate annotations for mapping.
// differs from above in that accessing the Class is deferred which is
// important if using runtime bytecode-enhancement
sources.addAnnotatedClassName( "org.hibernate.example.Customer" );

// Read package-level metadata.
sources.addPackage( "hibernate.example" );

// Read package-level metadata.
sources.addPackage( MyEntity.class.getPackage() );

// Adds the named hbm.xml resource as a source: which performs the
// classpath lookup and parses the XML
sources.addResource( "org/hibernate/example/Order.hbm.xml" );

// Adds the named JPA orm.xml resource as a source: which performs the
// classpath lookup and parses the XML
sources.addResource( "org/hibernate/example/Product.orm.xml" );

// Read all mapping documents from a directory tree.
// Assumes that any file named *.hbm.xml is a mapping document.
sources.addDirectory( new File( ".") );

// Read mappings from a particular XML file
sources.addFile( new File( "./mapping.xml") );

// Read all mappings from a jar file.
// Assumes that any file named *.hbm.xml is a mapping document.
sources.addJar( new File( "./entities.jar") );

// Read a mapping as an application resource using the convention that a class named foo.bar.MyEntity is
// mapped by a file named foo/bar/MyEntity.hbm.xml which can be resolved as a classpath resource.
sources.addClass( MyEntity.class );

3.2.2. Event Listener registration

The main use cases for an org.hibernate.integrator.spi.Integrator right now are registering event listeners and providing services (see org.hibernate.integrator.spi.ServiceContributingIntegrator). With 5.0 we plan on expanding that to allow altering the metamodel describing the mapping between object and relational models.

Example 271. Configuring an event listener
public class MyIntegrator implements org.hibernate.integrator.spi.Integrator {

    @Override
    public void integrate(
            Metadata metadata,
            SessionFactoryImplementor sessionFactory,
            SessionFactoryServiceRegistry serviceRegistry) {

        // As you might expect, an EventListenerRegistry is the thing with which event
        // listeners are registered
        // It is a service so we look it up using the service registry
        final EventListenerRegistry eventListenerRegistry =
            serviceRegistry.getService( EventListenerRegistry.class );

        // If you wish to have custom determination and handling of "duplicate" listeners,
        // you would have to add an implementation of the
        // org.hibernate.event.service.spi.DuplicationStrategy contract like this
        eventListenerRegistry.addDuplicationStrategy( new CustomDuplicationStrategy() );

        // EventListenerRegistry defines 3 ways to register listeners:

        // 1) This form overrides any existing registrations with
        eventListenerRegistry.setListeners( EventType.AUTO_FLUSH,
                                            DefaultAutoFlushEventListener.class );

        // 2) This form adds the specified listener(s) to the beginning of the listener chain
        eventListenerRegistry.prependListeners( EventType.PERSIST,
                                                DefaultPersistEventListener.class );

        // 3) This form adds the specified listener(s) to the end of the listener chain
        eventListenerRegistry.appendListeners( EventType.MERGE,
                                               DefaultMergeEventListener.class );
    }

    @Override
    public void disintegrate(
            SessionFactoryImplementor sessionFactory,
            SessionFactoryServiceRegistry serviceRegistry) {

    }
}

3.2.3. Building the Metadata

The second step in native bootstrapping is the building of a org.hibernate.boot.Metadata object containing the parsed representations of an application domain model and its mapping to a database. The first thing we obviously need to build a parsed representation is the source information to be parsed (annotated classes, hbm.xml files, orm.xml files). This is the purpose of org.hibernate.boot.MetadataSources:

MetadataSources has many other methods as well, explore its API and Javadocs for more information. Also, all methods on MetadataSources offer fluent-style call chaining::

Example 272. Configuring a MetadataSources with method chaining
ServiceRegistry standardRegistry =
        new StandardServiceRegistryBuilder().build();

MetadataSources sources = new MetadataSources( standardRegistry )
    .addAnnotatedClass( MyEntity.class )
    .addAnnotatedClassName( "org.hibernate.example.Customer" )
    .addResource( "org/hibernate/example/Order.hbm.xml" )
    .addResource( "org/hibernate/example/Product.orm.xml" );

Once we have the sources of mapping information defined, we need to build the Metadata object. If you are ok with the default behavior in building the Metadata then you can simply call the buildMetadata method of the MetadataSources.

Notice that a ServiceRegistry can be passed at a number of points in this bootstrapping process. The suggested approach is to build a StandardServiceRegistry yourself and pass that along to the MetadataSources constructor. From there, MetadataBuilder, Metadata, SessionFactoryBuilder and SessionFactory will all pick up that same StandardServiceRegistry.

However, if you wish to adjust the process of building Metadata from MetadataSources, you will need to use the MetadataBuilder as obtained via MetadataSources#getMetadataBuilder. MetadataBuilder allows a lot of control over the Metadata building process. See its Javadocs for full details.

Example 273. Building Metadata via MetadataBuilder
ServiceRegistry standardRegistry =
    new StandardServiceRegistryBuilder().build();

MetadataSources sources = new MetadataSources( standardRegistry );

MetadataBuilder metadataBuilder = sources.getMetadataBuilder();

// Use the JPA-compliant implicit naming strategy
metadataBuilder.applyImplicitNamingStrategy(
    ImplicitNamingStrategyJpaCompliantImpl.INSTANCE );

// specify the schema name to use for tables, etc when none is explicitly specified
metadataBuilder.applyImplicitSchemaName( "my_default_schema" );

// specify a custom Attribute Converter
metadataBuilder.applyAttributeConverter( myAttributeConverter );

Metadata metadata = metadataBuilder.build();

3.2.4. Building the SessionFactory

The final step in native bootstrapping is to build the SessionFactory itself. Much like discussed above, if you are ok with the default behavior of building a SessionFactory from a Metadata reference, you can simply call the buildSessionFactory method on the Metadata object.

However, if you would like to adjust that building process you will need to use SessionFactoryBuilder as obtained via [Metadata#getSessionFactoryBuilder. Again, see its Javadocs for more details.

Example 274. Native Bootstrapping - Putting it all together
StandardServiceRegistry standardRegistry = new StandardServiceRegistryBuilder()
    .configure( "org/hibernate/example/hibernate.cfg.xml" )
    .build();

Metadata metadata = new MetadataSources( standardRegistry )
    .addAnnotatedClass( MyEntity.class )
    .addAnnotatedClassName( "org.hibernate.example.Customer" )
    .addResource( "org/hibernate/example/Order.hbm.xml" )
    .addResource( "org/hibernate/example/Product.orm.xml" )
    .getMetadataBuilder()
    .applyImplicitNamingStrategy( ImplicitNamingStrategyJpaCompliantImpl.INSTANCE )
    .build();

SessionFactory sessionFactory = metadata.getSessionFactoryBuilder()
    .applyBeanManager( getBeanManager() )
    .build();

The bootstrapping API is quite flexible, but in most cases it makes the most sense to think of it as a 3 step process:

  1. Build the StandardServiceRegistry

  2. Build the Metadata

  3. Use those 2 to build the SessionFactory

Example 275. Building SessionFactory via SessionFactoryBuilder
StandardServiceRegistry standardRegistry = new StandardServiceRegistryBuilder()
        .configure( "org/hibernate/example/hibernate.cfg.xml" )
        .build();

Metadata metadata = new MetadataSources( standardRegistry )
    .addAnnotatedClass( MyEntity.class )
    .addAnnotatedClassName( "org.hibernate.example.Customer" )
    .addResource( "org/hibernate/example/Order.hbm.xml" )
    .addResource( "org/hibernate/example/Product.orm.xml" )
    .getMetadataBuilder()
    .applyImplicitNamingStrategy( ImplicitNamingStrategyJpaCompliantImpl.INSTANCE )
    .build();

SessionFactoryBuilder sessionFactoryBuilder = metadata.getSessionFactoryBuilder();

// Supply an SessionFactory-level Interceptor
sessionFactoryBuilder.applyInterceptor( new CustomSessionFactoryInterceptor() );

// Add a custom observer
sessionFactoryBuilder.addSessionFactoryObservers( new CustomSessionFactoryObserver() );

// Apply a CDI BeanManager ( for JPA event listeners )
sessionFactoryBuilder.applyBeanManager( getBeanManager() );

SessionFactory sessionFactory = sessionFactoryBuilder.build();

4. Schema generation

Hibernate allows you to generate the database from the entity mappings.

Although the automatic schema generation is very useful for testing and prototyping purposes, in a production environment, it’s much more flexible to manage the schema using incremental migration scripts.

Traditionally, the process of generating schema from entity mapping has been called HBM2DDL. To get a list of Hibernate-native and JPA-specific configuration properties consider reading the Configurations section.

Considering the following Domain Model:

Example 276. Schema generation Domain Model
@Entity(name = "Customer")
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;
    }
}

@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;
    }
}

If the hibernate.hbm2ddl.auto configuration is set to create, Hibernate is going to generate the following database schema:

Example 277. Auto-generated database schema
create table Customer (
    id integer not null,
    accountsPayableXrefId binary,
    image blob,
    name varchar(255),
    primary key (id)
)

create table Book (
    id bigint not null,
    isbn varchar(255),
    title varchar(255),
    author_id bigint,
    primary key (id)
)

create table Person (
    id bigint not null,
    name varchar(255),
    primary key (id)
)

alter table Book
    add constraint UK_u31e1frmjp9mxf8k8tmp990i unique (isbn)

alter table Book
    add constraint FKrxrgiajod1le3gii8whx2doie
    foreign key (author_id)
    references Person

4.1. Importing script files

To customize the schema generation process, the hibernate.hbm2ddl.import_files configuration property must be used to provide other scripts files that Hibernate can use when the SessionFactory is started.

For instance, considering the following schema-generation.sql import file:

Example 278. Schema generation import file
create sequence book_sequence start with 1 increment by 1

If we configure Hibernate to import the script above:

Example 279. Enabling query cache
<property
    name="hibernate.hbm2ddl.import_files"
    value="schema-generation.sql" />

Hibernate is going to execute the script file after the schema is automatically generated.

4.2. Database objects

Hibernate allows you to customize the schema generation process via the HBM database-object element.

Considering the following HBM mapping:

Example 280. Schema generation HBM database-object
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd" >

<hibernate-mapping>
    <database-object>
        <create>
            CREATE OR REPLACE FUNCTION sp_count_books(
                IN authorId bigint,
                OUT bookCount bigint)
                RETURNS bigint AS
            $BODY$
                BEGIN
                    SELECT COUNT(*) INTO bookCount
                    FROM book
                    WHERE author_id = authorId;
                END;
            $BODY$
            LANGUAGE plpgsql;
        </create>
        <drop></drop>
        <dialect-scope name="org.hibernate.dialect.PostgreSQL95Dialect" />
    </database-object>
</hibernate-mapping>

When the SessionFactory is bootstrapped, Hibernate is going to execute the database-object, therefore creating the sp_count_books function.

4.3. Database-level checks

Hibernate offers the @Check annotation so that you can specify an arbitrary SQL CHECK constraint which can be defined as follows:

Example 281. Database check entity mapping example
@Entity(name = "Book")
@Check( constraints = "CASE WHEN isbn IS NOT NULL THEN LENGTH(isbn) = 13 ELSE true END")
public static class Book {

    @Id
    private Long id;

    private String title;

    @NaturalId
    private String isbn;

    private Double price;

    //Getters and setters omitted for brevity

}

Now, if you try to add a Book entity with an isbn attribute whose length is not 13 characters, a ConstraintViolationException is going to be thrown.

Example 282. Database check failure example
Book book = new Book();
book.setId( 1L );
book.setPrice( 49.99d );
book.setTitle( "High-Performance Java Persistence" );
book.setIsbn( "11-11-2016" );

entityManager.persist( book );
INSERT  INTO Book (isbn, price, title, id)
VALUES  ('11-11-2016', 49.99, 'High-Performance Java Persistence', 1)

-- WARN SqlExceptionHelper:129 - SQL Error: 0, SQLState: 23514
-- ERROR SqlExceptionHelper:131 - ERROR: new row for relation "book" violates check constraint "book_isbn_check"

4.4. Default value for database column

With Hibernate, you can specify a default value for a given database column using the @ColumnDefault annotation.

Example 283. @ColumnDefault mapping example
@Entity(name = "Person")
@DynamicInsert
public static class Person {

    @Id
    private Long id;

    @ColumnDefault("'N/A'")
    private String name;

    @ColumnDefault("-1")
    private Long clientId;

    //Getter and setters omitted for brevity

}
CREATE TABLE Person (
  id BIGINT NOT NULL,
  clientId BIGINT DEFAULT -1,
  name VARCHAR(255) DEFAULT 'N/A',
  PRIMARY KEY (id)
)

In the mapping above, both the name and clientId table columns are going to use a DEFAULT value.

The entity is annotated with the @DynamicInsert annotation so that the INSERT statement does not include the entity attribute that have not been set.

This way, when omitting the name and the clientId attribute, the database is going to set them according to their default values.

Example 284. @ColumnDefault mapping example
doInJPA( this::entityManagerFactory, entityManager -> {
    Person person = new Person();
    person.setId( 1L );
    entityManager.persist( person );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
    Person person = entityManager.find( Person.class, 1L );
    assertEquals( "N/A", person.getName() );
    assertEquals( Long.valueOf( -1L ), person.getClientId() );
} );
INSERT INTO Person (id) VALUES (?)

4.5. Columns unique constraint

The @UniqueConstraint annotation is used to specify a unique constraint to be included by the automated schema generator for the primary or secondary table associated with the current annotated entity.

Considering the following entity mapping, Hibernate generates the unique constraint DDL when creating the database schema:

Example 285. @UniqueConstraint mapping example
@Entity
@Table(
    name = "book",
    uniqueConstraints =  @UniqueConstraint(
        name = "uk_book_title_author",
        columnNames = {
            "title",
            "author_id"
        }
    )
)
public static class Book {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(
        name = "author_id",
        foreignKey = @ForeignKey(name = "fk_book_author_id")
    )
    private Author author;

    //Getter and setters omitted for brevity
}

@Entity
@Table(name = "author")
public static class Author {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    //Getter and setters omitted for brevity
}
create table author (
    id bigint not null,
    first_name varchar(255),
    last_name varchar(255),
    primary key (id)
)

create table book (
    id bigint not null,
    title varchar(255),
    author_id bigint,
    primary key (id)
)

alter table book
   add constraint uk_book_title_author
   unique (title, author_id)

alter table book
   add constraint fk_book_author_id
   foreign key (author_id)
   references author

With the uk_book_title_author unique constraint in place, it’s no longer possible to add two books with the same title and for the same author.

Example 286. @UniqueConstraintTest persist example
Author _author = doInJPA( this::entityManagerFactory, entityManager -> {
    Author author = new Author();
    author.setFirstName( "Vlad" );
    author.setLastName( "Mihalcea" );
    entityManager.persist( author );

    Book book = new Book();
    book.setTitle( "High-Performance Java Persistence" );
    book.setAuthor( author );
    entityManager.persist( book );

    return author;
} );

try {
    doInJPA( this::entityManagerFactory, entityManager -> {
        Book book = new Book();
        book.setTitle( "High-Performance Java Persistence" );
        book.setAuthor( _author );
        entityManager.persist( book );
    } );
}
catch (Exception expected) {
    assertNotNull( ExceptionUtil.findCause( expected, ConstraintViolationException.class ) );
}
insert
into
    author
    (first_name, last_name, id)
values
    (?, ?, ?)

-- binding parameter [1] as [VARCHAR] - [Vlad]
-- binding parameter [2] as [VARCHAR] - [Mihalcea]
-- binding parameter [3] as [BIGINT]  - [1]

insert
into
    book
    (author_id, title, id)
values
    (?, ?, ?)

-- binding parameter [1] as [BIGINT]  - [1]
-- binding parameter [2] as [VARCHAR] - [High-Performance Java Persistence]
-- binding parameter [3] as [BIGINT]  - [2]

insert
into
    book
    (author_id, title, id)
values
    (?, ?, ?)

-- binding parameter [1] as [BIGINT]  - [1]
-- binding parameter [2] as [VARCHAR] - [High-Performance Java Persistence]
-- binding parameter [3] as [BIGINT]  - [3]

-- SQL Error: 23505, SQLState: 23505
-- Unique index or primary key violation: "UK_BOOK_TITLE_AUTHOR_INDEX_1 ON PUBLIC.BOOK(TITLE, AUTHOR_ID) VALUES ( /* key:1 */ 3, 'High-Performance Java Persistence', 1)";

The second INSERT statement fails because of the unique constraint violation.

4.6. Columns index

The @Index annotation is used by the automated schema generation tool to create a database index.

Considering the following entity mapping, Hibernate generates the index when creating the database schema:

Example 287. @Index mapping example
@Entity
@Table(
    name = "author",
    indexes =  @Index(
        name = "idx_author_first_last_name",
        columnList = "first_name, last_name",
        unique = false
    )
)
public static class Author {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    //Getter and setters omitted for brevity
}
create table author (
    id bigint not null,
    first_name varchar(255),
    last_name varchar(255),
    primary key (id)
)

create index idx_author_first_last_name
    on author (first_name, last_name)

5. 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.

5.1. Accessing Hibernate APIs from JPA

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

Example 288. Accessing Hibernate APIs from JPA
Session session = entityManager.unwrap( Session.class );
SessionImplementor sessionImplementor = entityManager.unwrap( SessionImplementor.class );

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

5.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.

5.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 289. @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 290. 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 291. 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 292. 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.

5.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 293. 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 false

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 false.

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

Example 294. 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>

5.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 295. Making an entity persistent with JPA
Person person = new Person();
person.setId( 1L );
person.setName("John Doe");

entityManager.persist( person );
Example 296. 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 with 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.

5.4. Deleting (removing) entities

Entities can also be deleted.

Example 297. Deleting an entity with JPA
entityManager.remove( person );
Example 298. 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.

5.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 299. Obtaining an entity reference without initializing its data with JPA
Book book = new Book();
book.setAuthor( entityManager.getReference( Person.class, personId ) );
Example 300. 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.

5.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 301. Obtaining an entity reference with its data initialized with JPA
Person person = entityManager.find( Person.class, personId );
Example 302. Obtaining an entity reference with its data initialized with Hibernate API
Person person = session.get( Person.class, personId );
Example 303. 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.

It’s possible to return a Java 8 Optional as well:

Example 304. Obtaining an Optional entity reference with its data initialized using the byId() Hibernate API
Optional<Person> optionalPerson = session.byId( Person.class ).loadOptional( personId );

5.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 305. 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 306. Get entity reference by simple natural-id
Book book = session.bySimpleNaturalId( Book.class ).getReference( isbn );
Example 307. Load entity by natural-id
Book book = session
    .byNaturalId( Book.class )
    .using( "isbn", isbn )
    .load( );

We can also use a Java 8 Optional to load an entity by its natural id:

Example 308. Load an Optional entity by natural-id
Optional<Book> optionalBook = session
    .byNaturalId( Book.class )
    .using( "isbn", isbn )
    .loadOptional( );

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.

5.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 309. Modifying managed state with JPA
Person person = entityManager.find( Person.class, personId );
person.setName("John Doe");
entityManager.flush();
Example 310. Modifying managed state with Hibernate API
Person person = session.byId( Person.class ).load( personId );
person.setName("John Doe");
entityManager.flush();

By default, when you modify an entity, all columns but the identifier are being set during update.

Therefore, considering you have the following Product entity mapping:

Example 311. Product entity mapping
@Entity(name = "Product")
public static class Product {

    @Id
    private Long id;

    @Column
    private String name;

    @Column
    private String description;

    @Column(name = "price_cents")
    private Integer priceCents;

    @Column
    private Integer quantity;

    //Getters and setters are omitted for brevity

}

If you persist the following Product entity:

Example 312. Persisting a Product entity
Product book = new Product();
book.setId( 1L );
book.setName( "High-Performance Java Persistence" );
book.setDescription( "Get the most out of your persistence layer" );
book.setPriceCents( 29_99 );
book.setQuantity( 10_000 );

entityManager.persist( book );

When you modify the Product entity, Hibernate generates the following SQL UPDATE statement:

Example 313. Modifying the Product entity
doInJPA( this::entityManagerFactory, entityManager -> {
    Product book = entityManager.find( Product.class, 1L );
    book.setPriceCents( 24_99 );
} );
UPDATE
    Product
SET
    description = ?,
    name = ?,
    price_cents = ?,
    quantity = ?
WHERE
    id = ?

-- binding parameter [1] as [VARCHAR] - [Get the most out of your persistence layer]
-- binding parameter [2] as [VARCHAR] - [High-Performance Java Persistence]
-- binding parameter [3] as [INTEGER] - [2499]
-- binding parameter [4] as [INTEGER] - [10000]
-- binding parameter [5] as [BIGINT]  - [1]

The default UPDATE statement containing all columns has two advantages:

  • it allows you to better benefit from JDBC Statement caching.

  • it allows you to enable batch updates even if multiple entities modify different properties.

However, there is also one downside to including all columns in the SQL UPDATE statement. If you have multiple indexes, the database might update those redundantly even if you don’t actually modify all column values.

To fix this issue, you can use dynamic updates.

5.8.1. Dynamic updates

To enable dynamic updates, you need to annotate the entity with the @DynamicUpdate annotation:

Example 314. Product entity mapping
@Entity(name = "Product")
@DynamicUpdate
public static class Product {

    @Id
    private Long id;

    @Column
    private String name;

    @Column
    private String description;

    @Column(name = "price_cents")
    private Integer priceCents;

    @Column
    private Integer quantity;

    //Getters and setters are omitted for brevity

}

This time, when reruning the previous test case, Hibernate generates the following SQL UPDATE statement:

Example 315. Modifying the Product entity with a dynamic update
UPDATE
    Product
SET
    price_cents = ?
WHERE
    id = ?

-- binding parameter [1] as [INTEGER] - [2499]
-- binding parameter [2] as [BIGINT]  - [1]

The dynamic update allows you to set just the columns sthat were modified in the associated entity.

5.9. Refresh entity state

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

Example 316. 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 317. 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.

Traditionally, Hibernate has been allowing detached entities to be refreshed. Unfortunately, JPA prohibits this practice and specifies that an IllegalArgumentException should be thrown instead.

For this reason, when bootstrapping the Hibernate SessionFactory using the native API, the legacy detached entity refresh behavior is going to be preserved. On the other hand, when bootstrapping Hibernate through JPA EntityManagerFactory building process, detached entities are not allowed to be refreshed by default.

However, this default behavior can be overwritten through the hibernate.allow_refresh_detached_entity configuration property. If this property is explicitly set to true, then you can refresh detached entities even when using the JPA bootstraps mechanism, therefore bypassing the JPA specification restriction.

For more about the hibernate.allow_refresh_detached_entity configuration property, check out the Configurations section as well.

5.9.1. Refresh gotchas

The refresh entity state transition is meant to overwrite the entity attributes according to the info currently contained in the associated database record.

However, you have to be very careful when cascading the refresh action to any transient entity.

For instance, consider the following example:

Example 318. Refreshing entity state gotcha
try {
    Person person = entityManager.find( Person.class, personId );

    Book book = new Book();
    book.setId( 100L );
    book.setTitle( "Hibernate User Guide" );
    book.setAuthor( person );
    person.getBooks().add( book );

    entityManager.refresh( person );
}
catch ( EntityNotFoundException expected ) {
    log.info( "Beware when cascading the refresh associations to transient entities!" );
}

In the aforementioned example, an EntityNotFoundException is thrown because the Book entity is still in a transient state. When the refresh action is cascaded from the Person entity, Hibernate will not be able to locate the Book entity in the database.

For this reason, you should be very careful when mixing the refresh action with transient child entity objects.

5.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.

5.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 319. 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 320. 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.

5.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 321. Visualizing merge
public Person merge(Person detached) {
    Person newReference = session.byId( Person.class ).load( detached.getId() );
    newReference.setName( detached.getName() );
    return newReference;
}
Example 322. 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 323. 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 );
Merging gotchas

For example, Hibernate throws IllegalStateException when merging a parent entity which has references to 2 detached child entities child1 and child2 (obtained from different sessions), and child1 and child2 represent the same persistent entity, Child.

A new configuration property, hibernate.event.merge.entity_copy_observer, controls how Hibernate will respond when multiple representations of the same persistent entity ("entity copy") is detected while merging.

The possible values are:

disallow (the default)

throws IllegalStateException if an entity copy is detected

allow

performs the merge operation on each entity copy that is detected

log

(provided for testing only) performs the merge operation on each entity copy that is detected and logs information about the entity copies. This setting requires DEBUG logging be enabled for org.hibernate.event.internal.EntityCopyAllowedLoggedObserver.

In addition, the application may customize the behavior by providing an implementation of org.hibernate.event.spi.EntityCopyObserver and setting hibernate.event.merge.entity_copy_observer to the class name. When this property is set to allow or log, Hibernate will merge each entity copy detected while cascading the merge operation. In the process of merging each entity copy, Hibernate will cascade the merge operation from each entity copy to its associations with cascade=CascadeType.MERGE or CascadeType.ALL. The entity state resulting from merging an entity copy will be overwritten when another entity copy is merged.

Because cascade order is undefined, the order in which the entity copies are merged is undefined. As a result, if property values in the entity copies are not consistent, the resulting entity state will be indeterminate, and data will be lost from all entity copies except for the last one merged. Therefore, the last writer wins.

If an entity copy cascades the merge operation to an association that is (or contains) a new entity, that new entity will be merged (i.e., persisted and the merge operation will be cascaded to its associations according to its mapping), even if that same association is ultimately overwritten when Hibernate merges a different representation having a different value for its association.

If the association is mapped with orphanRemoval = true, the new entity will not be deleted because the semantics of orphanRemoval do not apply if the entity being orphaned is a new entity.

There are known issues when representations of the same persistent entity have different values for a collection. See HHH-9239 and HHH-9240 for more details. These issues can cause data loss or corruption.

By setting hibernate.event.merge.entity_copy_observer configuration property to allow or log, Hibernate will allow entity copies of any type of entity to be merged.

The only way to exclude particular entity classes or associations that contain critical data is to provide a custom implementation of org.hibernate.event.spi.EntityCopyObserver with the desired behavior, and setting hibernate.event.merge.entity_copy_observer to the class name.

Hibernate provides limited DEBUG logging capabilities that can help determine the entity classes for which entity copies were found. By setting hibernate.event.merge.entity_copy_observer to log and enabling DEBUG logging for org.hibernate.event.internal.EntityCopyAllowedLoggedObserver, the following will be logged each time an application calls EntityManager.merge( entity ) or
Session.merge( entity ):

  • number of times multiple representations of the same persistent entity was detected summarized by entity name;

  • details by entity name and ID, including output from calling toString() on each representation being merged as well as the merge result.

The log should be reviewed to determine if multiple representations of entities containing critical data are detected. If so, the application should be modified so there is only one representation, and a custom implementation of org.hibernate.event.spi.EntityCopyObserver should be provided to disallow entity copies for entities with critical data.

Using optimistic locking is recommended to detect if different representations are from different versions of the same persistent entity. If they are not from the same version, Hibernate will throw either the JPA OptimisticLockException or the native StaleObjectStateException depending on your bootstrapping strategy.

5.11. Checking persistent state

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

Example 324. Verifying managed state with JPA
boolean contained = entityManager.contains( person );
Example 325. Verifying managed state with Hibernate API
boolean contained = session.contains( person );
Example 326. 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 327. 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 328. 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" );

5.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 329. 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 330. 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 331. 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 332. Verify if an entity is contained in a persistence context
entityManager.contains( person );

session.contains( person );

5.13. Cascading entity state transitions

JPA allows you to propagate the state transition from a parent entity to a child. For this purpose, the JPA javax.persistence.CascadeType defines various cascade types:

ALL

cascades all entity state transitions

PERSIST

cascades the entity persist operation.

MERGE

cascades the entity merge operation.

REMOVE

cascades the entity remove operation.

REFRESH

cascades the entity refresh operation.

DETACH

cascades the entity detach operation.

Additionally, the CascadeType.ALL will propagate any Hibernate-specific operation, which is defined by the org.hibernate.annotations.CascadeType enum:

SAVE_UPDATE

cascades the entity saveOrUpdate operation.

REPLICATE

cascades the entity replicate operation.

LOCK

cascades the entity lock operation.

The following examples will explain some of the aforementioned cascade operations using the following entities:

@Entity
public class Person {

    @Id
    private Long id;

    private String name;

    @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL)
    private List<Phone> phones = 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<Phone> getPhones() {
        return phones;
    }

    public void addPhone(Phone phone) {
        this.phones.add( phone );
        phone.setOwner( this );
    }
}


@Entity
public class Phone {

    @Id
    private Long id;

    @Column(name = "`number`")
    private String number;

    @ManyToOne(fetch = FetchType.LAZY)
    private Person owner;

    public Long getId() {
        return id;
    }

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

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public Person getOwner() {
        return owner;
    }

    public void setOwner(Person owner) {
        this.owner = owner;
    }
}

5.13.1. CascadeType.PERSIST

The CascadeType.PERSIST allows us to persist a child entity along with the parent one.

Example 333. CascadeType.PERSIST example
Person person = new Person();
person.setId( 1L );
person.setName( "John Doe" );

Phone phone = new Phone();
phone.setId( 1L );
phone.setNumber( "123-456-7890" );

person.addPhone( phone );

entityManager.persist( person );
INSERT INTO Person ( name, id )
VALUES ( 'John Doe', 1 )

INSERT INTO Phone ( `number`, person_id, id )
VALUE ( '123-456-7890', 1, 1 )

Even if just the Person parent entity was persisted, Hibernate has managed to cascade the persist operation to the associated Phone child entity as well.

5.13.2. CascadeType.MERGE

The CascadeType.MERGE allows us to merge a child entity along with the parent one.

Example 334. CascadeType.MERGE example
Phone phone = entityManager.find( Phone.class, 1L );
Person person = phone.getOwner();

person.setName( "John Doe Jr." );
phone.setNumber( "987-654-3210" );

entityManager.clear();

entityManager.merge( person );
SELECT
    p.id as id1_0_1_,
    p.name as name2_0_1_,
    ph.owner_id as owner_id3_1_3_,
    ph.id as id1_1_3_,
    ph.id as id1_1_0_,
    ph."number" as number2_1_0_,
    ph.owner_id as owner_id3_1_0_
FROM
    Person p
LEFT OUTER JOIN
    Phone ph
        on p.id=ph.owner_id
WHERE
    p.id = 1

During merge, the current state of the entity is copied onto the entity version that was just fetched from the database. That’s the reason why Hibernate executed the SELECT statement which fetched both the Person entity along with its children.

5.13.3. CascadeType.REMOVE

The CascadeType.REMOVE allows us to remove a child entity along with the parent one. Traditionally, Hibernate called this operation delete, that’s why the org.hibernate.annotations.CascadeType provides a DELETE cascade option. However, CascadeType.REMOVE and org.hibernate.annotations.CascadeType.DELETE are identical.

Example 335. CascadeType.REMOVE example
Person person = entityManager.find( Person.class, 1L );

entityManager.remove( person );
DELETE FROM Phone WHERE id = 1

DELETE FROM Person WHERE id = 1

5.13.4. CascadeType.DETACH

CascadeType.DETACH is used to propagate the detach operation from a parent entity to a child.

Example 336. CascadeType.DETACH example
Person person = entityManager.find( Person.class, 1L );
assertEquals( 1, person.getPhones().size() );
Phone phone = person.getPhones().get( 0 );

assertTrue( entityManager.contains( person ));
assertTrue( entityManager.contains( phone ));

entityManager.detach( person );

assertFalse( entityManager.contains( person ));
assertFalse( entityManager.contains( phone ));

5.13.5. CascadeType.LOCK

Although unintuitively, CascadeType.LOCK does not propagate a lock request from a parent entity to its children. Such a use case requires the use of the PessimisticLockScope.EXTENDED value of the javax.persistence.lock.scope property.

However, CascadeType.LOCK allows us to reattach a parent entity along with its children to the currently running Persistence Context.

Example 337. CascadeType.LOCK example
Person person = entityManager.find( Person.class, 1L );
assertEquals( 1, person.getPhones().size() );
Phone phone = person.getPhones().get( 0 );

assertTrue( entityManager.contains( person ) );
assertTrue( entityManager.contains( phone ) );

entityManager.detach( person );

assertFalse( entityManager.contains( person ) );
assertFalse( entityManager.contains( phone ) );

entityManager.unwrap( Session.class )
        .buildLockRequest( new LockOptions( LockMode.NONE ) )
        .lock( person );

assertTrue( entityManager.contains( person ) );
assertTrue( entityManager.contains( phone ) );

5.13.6. CascadeType.REFRESH

The CascadeType.REFRESH is used to propagate the refresh operation from a parent entity to a child. The refresh operation will discard the current entity state, and it will override it using the one loaded from the database.

Example 338. CascadeType.REFRESH example
Person person = entityManager.find( Person.class, 1L );
Phone phone = person.getPhones().get( 0 );

person.setName( "John Doe Jr." );
phone.setNumber( "987-654-3210" );

entityManager.refresh( person );

assertEquals( "John Doe", person.getName() );
assertEquals( "123-456-7890", phone.getNumber() );
SELECT
    p.id as id1_0_1_,
    p.name as name2_0_1_,
    ph.owner_id as owner_id3_1_3_,
    ph.id as id1_1_3_,
    ph.id as id1_1_0_,
    ph."number" as number2_1_0_,
    ph.owner_id as owner_id3_1_0_
FROM
    Person p
LEFT OUTER JOIN
    Phone ph
        ON p.id=ph.owner_id
WHERE
    p.id = 1

In the aforementioned example, you can see that both the Person and Phone entities are refreshed even if we only called this operation on the parent entity only.

5.13.7. CascadeType.REPLICATE

The CascadeType.REPLICATE is to replicate both the parent and the child entities. The replicate operation allows you to synchronize entities coming from different sources of data.

Example 339. CascadeType.REPLICATE example
Person person = new Person();
person.setId( 1L );
person.setName( "John Doe Sr." );

Phone phone = new Phone();
phone.setId( 1L );
phone.setNumber( "(01) 123-456-7890" );
person.addPhone( phone );

entityManager.unwrap( Session.class ).replicate( person, ReplicationMode.OVERWRITE );
SELECT
    id
FROM
    Person
WHERE
    id = 1

SELECT
    id
FROM
    Phone
WHERE
    id = 1

UPDATE
    Person
SET
    name = 'John Doe Sr.'
WHERE
    id = 1

UPDATE
    Phone
SET
    "number" = '(01) 123-456-7890',
    owner_id = 1
WHERE
    id = 1

As illustrated by the SQL statements being generated, both the Person and Phone entities are replicated to the underlying database rows.

5.13.8. @OnDelete cascade

While the previous cascade types propagate entity state transitions, the @OnDelete cascade is a DDL-level FK feature which allows you to remove a child record whenever the parent row is deleted.

So, when annotating the @ManyToOne association with @OnDelete( action = OnDeleteAction.CASCADE ), the automatic schema generator will apply the ON DELETE CASCADE SQL directive to the Foreign Key declaration, as illustrated by the following example.

Example 340. @OnDelete mapping
@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;

    private String name;

    //Getters and setters are omitted for brevity
}

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

    @Id
    private Long id;

    @Column(name = "`number`")
    private String number;

    @ManyToOne(fetch = FetchType.LAZY)
    @OnDelete( action = OnDeleteAction.CASCADE )
    private Person owner;

    //Getters and setters are omitted for brevity
}
create table Person (
    id bigint not null,
    name varchar(255),
    primary key (id)
)

create table Phone (
    id bigint not null,
    "number" varchar(255),
    owner_id bigint,
    primary key (id)
)

alter table Phone
    add constraint FK82m836qc1ss2niru7eogfndhl
    foreign key (owner_id)
    references Person
    on delete cascade

Now, you can just remove the Person entity, and the associated Phone is going to be removed automatically.

Example 341. @OnDelete example
Person person = entityManager.find( Person.class, 1L );
entityManager.remove( person );
delete from Person where id = ?

-- binding parameter [1] as [BIGINT] - [1]

6. Flushing

Flushing is the process of synchronizing the state of the persistence context with the underlying database. The EntityManager and the Hibernate Session expose a set of methods, through which the application developer can change the persistent state of an entity.

The persistence context acts as a transactional write-behind cache, queuing any entity state change. Like any write-behind cache, changes are first applied in-memory and synchronized with the database during flush time. The flush operation takes every entity state change and translates it to an INSERT, UPDATE or DELETE statement.

Because DML statements are grouped together, Hibernate can apply batching transparently. See the Batching chapter for more information.

The flushing strategy is given by the flushMode of the current running Hibernate Session. Although JPA defines only two flushing strategies (AUTO and COMMIT), Hibernate has a much broader spectrum of flush types:

ALWAYS

Flushes the Session before every query.

AUTO

This is the default mode and it flushes the Session only if necessary.

COMMIT

The Session tries to delay the flush until the current Transaction is committed, although it might flush prematurely too.

MANUAL

The Session flushing is delegated to the application, which must call Session.flush() explicitly in order to apply the persistence context changes.

6.1. AUTO flush

By default, Hibernate uses the AUTO flush mode which triggers a flush in the following circumstances:

  • prior to committing a Transaction

  • prior to executing a JPQL/HQL query that overlaps with the queued entity actions

  • before executing any native SQL query that has no registered synchronization

6.1.1. AUTO flush on commit

In the following example, an entity is persisted and then the transaction is committed.

Example 342. Automatic flushing on commit
entityManager = entityManagerFactory().createEntityManager();
txn = entityManager.getTransaction();
txn.begin();

Person person = new Person( "John Doe" );
entityManager.persist( person );
log.info( "Entity is in persisted state" );

txn.commit();
--INFO: Entity is in persisted state
INSERT INTO Person (name, id) VALUES ('John Doe', 1)

Hibernate logs the message prior to inserting the entity because the flush only occurred during transaction commit.

This is valid for the SEQUENCE and TABLE identifier generators. The IDENTITY generator must execute the insert right after calling persist(). For details, see the discussion of generators in Identifier generators.

6.1.2. AUTO flush on JPQL/HQL query

A flush may also be triggered when executing an entity query.

Example 343. Automatic flushing on JPQL/HQL
Person person = new Person( "John Doe" );
entityManager.persist( person );
entityManager.createQuery( "select p from Advertisement p" ).getResultList();
entityManager.createQuery( "select p from Person p" ).getResultList();
SELECT a.id AS id1_0_ ,
       a.title AS title2_0_
FROM   Advertisement a

INSERT INTO Person (name, id) VALUES ('John Doe', 1)

SELECT p.id AS id1_1_ ,
       p.name AS name2_1_
FROM   Person p

The reason why the Advertisement entity query didn’t trigger a flush is because there’s no overlapping between the Advertisement and the Person tables:

Example 344. Automatic flushing on JPQL/HQL entities
@Entity(name = "Person")
public static class Person {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    public Person() {}

    public Person(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

}

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

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    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;
    }
}

When querying for a Person entity, the flush is triggered prior to executing the entity query.

Example 345. Automatic flushing on JPQL/HQL
Person person = new Person( "John Doe" );
entityManager.persist( person );
entityManager.createQuery( "select p from Person p" ).getResultList();
INSERT INTO Person (name, id) VALUES ('John Doe', 1)

SELECT p.id AS id1_1_ ,
       p.name AS name2_1_
FROM   Person p

This time, the flush was triggered by a JPQL query because the pending entity persist action overlaps with the query being executed.

6.1.3. AUTO flush on native SQL query

When executing a native SQL query, a flush is always triggered when using the EntityManager API.

Example 346. Automatic flushing on native SQL using EntityManager
assertTrue(((Number) entityManager
        .createNativeQuery( "select count(*) from Person")
        .getSingleResult()).intValue() == 0 );

Person person = new Person( "John Doe" );
entityManager.persist( person );

assertTrue(((Number) entityManager
        .createNativeQuery( "select count(*) from Person")
        .getSingleResult()).intValue() == 1 );

The Session API doesn’t trigger an AUTO flush when executing a native query

Example 347. Automatic flushing on native SQL using Session
			assertTrue(((Number) entityManager
					.createNativeQuery( "select count(*) from Person")
					.getSingleResult()).intValue() == 0 );

			Person person = new Person( "John Doe" );
			entityManager.persist( person );
			Session session = entityManager.unwrap(Session.class);

			// for this to work, the Session/EntityManager must be put into COMMIT FlushMode
			//  - this is a change since 5.2 to account for merging EntityManager functionality
			// 		directly into Session.  Flushing would be the JPA-spec compliant behavior,
			//		so we know do that by default.
			session.setFlushMode( FlushModeType.COMMIT );
			//		or using Hibernate's FlushMode enum
			//session.setHibernateFlushMode( FlushMode.COMMIT );

			assertTrue(((Number) session
					.createNativeQuery( "select count(*) from Person")
					.uniqueResult()).intValue() == 0 );
			//end::flushing-auto-flush-sql-native-example[\]
		} );
	}

	@Test
	public void testFlushAutoSQLSynchronization() {
		doInJPA( this::entityManagerFactory, entityManager -> {
			entityManager.createNativeQuery( "delete from Person" ).executeUpdate();;
		} );
		doInJPA( this::entityManagerFactory, entityManager -> {
			log.info( "testFlushAutoSQLSynchronization" );
			assertTrue(((Number) entityManager
					.createNativeQuery( "select count(*) from Person")
					.getSingleResult()).intValue() == 0 );

			Person person = new Person( "John Doe" );
			entityManager.persist( person );
			Session session = entityManager.unwrap( Session.class );

			assertTrue(((Number) session
					.createNativeQuery( "select count(*) from Person")
					.addSynchronizedEntityClass( Person.class )
					.uniqueResult()).intValue() == 1 );
		} );
	}

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

		@Id
		@GeneratedValue
		private Long id;

		private String name;

		public Person() {}

		public Person(String name) {
			this.name = name;
		}

		public Long getId() {
			return id;
		}

		public String getName() {
			return name;
		}

	}

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

		@Id
		@GeneratedValue
		private Long id;

		private String title;

		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;
		}
	}
}

To flush the Session, the query must use a synchronization:

Example 348. Automatic flushing on native SQL with Session synchronization
assertTrue(((Number) entityManager
        .createNativeQuery( "select count(*) from Person")
        .getSingleResult()).intValue() == 0 );

Person person = new Person( "John Doe" );
entityManager.persist( person );
Session session = entityManager.unwrap( Session.class );

assertTrue(((Number) session
        .createNativeQuery( "select count(*) from Person")
        .addSynchronizedEntityClass( Person.class )
        .uniqueResult()).intValue() == 1 );

6.2. COMMIT flush

JPA also defines a COMMIT flush mode, which is described as follows:

If FlushModeType.COMMIT is set, the effect of updates made to entities in the persistence context upon queries is unspecified.

— Section 3.10.8 of the JPA 2.1 Specification

When executing a JPQL query, the persistence context is only flushed when the current running transaction is committed.

Example 349. COMMIT flushing on JPQL
Person person = new Person("John Doe");
entityManager.persist(person);

entityManager.createQuery("select p from Advertisement p")
    .setFlushMode( FlushModeType.COMMIT)
    .getResultList();

entityManager.createQuery("select p from Person p")
    .setFlushMode( FlushModeType.COMMIT)
    .getResultList();
SELECT a.id AS id1_0_ ,
       a.title AS title2_0_
FROM   Advertisement a

SELECT p.id AS id1_1_ ,
       p.name AS name2_1_
FROM   Person p

INSERT INTO Person (name, id) VALUES ('John Doe', 1)

Because the JPA doesn’t impose a strict rule on delaying flushing, when executing a native SQL query, the persistence context is going to be flushed.

Example 350. COMMIT flushing on SQL
Person person = new Person("John Doe");
entityManager.persist(person);

assertTrue(((Number) entityManager
    .createNativeQuery("select count(*) from Person")
    .getSingleResult()).intValue() == 1);
INSERT INTO Person (name, id) VALUES ('John Doe', 1)

SELECT COUNT(*) FROM Person

6.3. ALWAYS flush

The ALWAYS is only available with the native Session API.

The ALWAYS flush mode triggers a persistence context flush even when executing a native SQL query against the Session API.

Example 351. COMMIT flushing on SQL
Person person = new Person("John Doe");
entityManager.persist(person);

Session session = entityManager.unwrap( Session.class);
assertTrue(((Number) session
        .createNativeQuery("select count(*) from Person")
        .setFlushMode( FlushMode.ALWAYS)
        .uniqueResult()).intValue() == 1);
INSERT INTO Person (name, id) VALUES ('John Doe', 1)

SELECT COUNT(*) FROM Person

6.4. MANUAL flush

Both the EntityManager and the Hibernate Session define a flush() method that, when called, triggers a manual flush. Hibernate also defines a MANUAL flush mode so the persistence context can only be flushed manually.

Example 352. MANUAL flushing
Person person = new Person("John Doe");
entityManager.persist(person);

Session session = entityManager.unwrap( Session.class);
session.setHibernateFlushMode( FlushMode.MANUAL );

assertTrue(((Number) entityManager
    .createQuery("select count(id) from Person")
    .getSingleResult()).intValue() == 0);

assertTrue(((Number) session
    .createNativeQuery("select count(*) from Person")
    .uniqueResult()).intValue() == 0);
SELECT COUNT(p.id) AS col_0_0_
FROM   Person p

SELECT COUNT(*)
FROM   Person

The INSERT statement was not executed because the persistence context because there was no manual flush() call.

This mode is useful when using multi-request logical transactions and only the last request should flush the persistence context.

6.5. Flush operation order

From a database perspective, a row state can be altered using either an INSERT, an UPDATE or a DELETE statement. Because entity state changes are automatically converted to SQL statements, it’s important to know which entity actions are associated to a given SQL statement.

INSERT

The INSERT statement is generated either by the EntityInsertAction or EntityIdentityInsertAction. These actions are scheduled by the persist operation, either explicitly or through cascading the PersistEvent from a parent to a child entity.

DELETE

The DELETE statement is generated by the EntityDeleteAction or OrphanRemovalAction.

UPDATE

The UPDATE statement is generated by EntityUpdateAction during flushing if the managed entity has been marked modified. The dirty checking mechanism is responsible for determining if a managed entity has been modified since it was first loaded.

Hibernate does not execute the SQL statements in the order of their associated entity state operations.

To visualize how this works, consider the following example:

Example 353. Flush operation order
Person person = entityManager.find( Person.class, 1L);
entityManager.remove(person);

Person newPerson = new Person( );
newPerson.setId( 2L );
newPerson.setName( "John Doe" );
entityManager.persist( newPerson );
INSERT INTO Person (name, id)
VALUES ('John Doe', 2L)

DELETE FROM Person WHERE id = 1

Even if we removed the first entity and then persist a new one, Hibernate is going to execute the DELETE statement after the INSERT.

The order in which SQL statements are executed is given by the ActionQueue and not by the order in which entity state operations have been previously defined.

The ActionQueue executes all operations in the following order:

  1. OrphanRemovalAction

  2. EntityInsertAction or EntityIdentityInsertAction

  3. EntityUpdateAction

  4. CollectionRemoveAction

  5. CollectionUpdateAction

  6. CollectionRecreateAction

  7. EntityDeleteAction

7. Database access

7.1. ConnectionProvider

As an ORM tool, probably the single most important thing you need to tell Hibernate is how to connect to your database so that it may connect on behalf of your application. This is ultimately the function of the org.hibernate.engine.jdbc.connections.spi.ConnectionProvider interface. Hibernate provides some out of the box implementations of this interface. ConnectionProvider is also an extension point so you can also use custom implementations from third parties or written yourself. The ConnectionProvider to use is defined by the hibernate.connection.provider_class setting. See the org.hibernate.cfg.AvailableSettings#CONNECTION_PROVIDER

Generally speaking, applications should not have to configure a ConnectionProvider explicitly if using one of the Hibernate-provided implementations. Hibernate will internally determine which ConnectionProvider to use based on the following algorithm:

  1. If hibernate.connection.provider_class is set, it takes precedence

  2. else if hibernate.connection.datasource is set → Using DataSources

  3. else if any setting prefixed by hibernate.c3p0. is set → Using c3p0

  4. else if any setting prefixed by hibernate.proxool. is set → Using Proxool

  5. else if any setting prefixed by hibernate.hikari. is set → Using Hikari

  6. else if hibernate.connection.url is set → Using Hibernate’s built-in (and unsupported) pooling

  7. else → User-provided Connections

7.2. Using DataSources

Hibernate can integrate with a javax.sql.DataSource for obtaining JDBC Connections. Applications would tell Hibernate about the DataSource via the (required) hibernate.connection.datasource setting which can either specify a JNDI name or would reference the actual DataSource instance. For cases where a JNDI name is given, be sure to read JNDI

For JPA applications, note that hibernate.connection.datasource corresponds to either javax.persistence.jtaDataSource or javax.persistence.nonJtaDataSource.

The DataSource ConnectionProvider also (optionally) accepts the hibernate.connection.username and hibernate.connection.password. If specified, the DataSource#getConnection(String username, String password) will be used. Otherwise, the no-arg form is used.

7.3. Using c3p0

To use this integration, the application must include the hibernate-c3p0 module jar (as well as its dependencies) on the classpath.

Hibernate also provides support for applications to use c3p0 connection pooling. When using this c3p0 support, a number of additional configuration settings are recognized.

Transaction isolation of the Connections is managed by the ConnectionProvider itself. See ConnectionProvider support for transaction isolation setting.

hibernate.connection.driver_class

The name of the JDBC Driver class to use

hibernate.connection.url

The JDBC connection url.

Any settings prefixed with hibernate.connection. (other than the "special ones")

These all have the hibernate.connection. prefix stripped and the rest will be passed as JDBC connection properties

hibernate.c3p0.min_size or c3p0.minPoolSize

The minimum size of the c3p0 pool. See c3p0 minPoolSize

hibernate.c3p0.max_size or c3p0.maxPoolSize

The maximum size of the c3p0 pool. See c3p0 maxPoolSize

hibernate.c3p0.timeout or c3p0.maxIdleTime

The Connection idle time. See c3p0 maxIdleTime

hibernate.c3p0.max_statements or c3p0.maxStatements

Controls the c3p0 PreparedStatement cache size (if using). See c3p0 maxStatements

hibernate.c3p0.acquire_increment or c3p0.acquireIncrement

Number of connections c3p0 should acquire at a time when pool is exhausted. See c3p0 acquireIncrement

hibernate.c3p0.idle_test_period or c3p0.idleConnectionTestPeriod

Idle time before a c3p0 pooled connection is validated. See c3p0 idleConnectionTestPeriod

hibernate.c3p0.initialPoolSize

The initial c3p0 pool size. If not specified, default is to use the min pool size. See c3p0 initialPoolSize

Any other settings prefixed with hibernate.c3p0.

Will have the hibernate. portion stripped and be passed to c3p0.

Any other settings prefixed with c3p0.

Get passed to c3p0 as is. See c3p0 configuration

7.4. Using Proxool

To use this integration, the application must include the hibernate-proxool module jar (as well as its dependencies) on the classpath.

Hibernate also provides support for applications to use Proxool connection pooling.

Transaction isolation of the Connections is managed by the ConnectionProvider itself. See ConnectionProvider support for transaction isolation setting.

7.5. Using existing Proxool pools

Controlled by the hibernate.proxool.existing_pool setting. If set to true, this ConnectionProvider will use an already existing Proxool pool by alias as indicated by the hibernate.proxool.pool_alias setting.

7.6. Configuring Proxool via XML

The hibernate.proxool.xml setting names a Proxool configuration XML file to be loaded as a classpath resource and loaded by Proxool’s JAXPConfigurator. See proxool configuration. hibernate.proxool.pool_alias must be set to indicate which pool to use.

7.7. Configuring Proxool via Properties

The hibernate.proxool.properties setting names a Proxool configuration properties file to be loaded as a classpath resource and loaded by Proxool’s PropertyConfigurator. See proxool configuration. hibernate.proxool.pool_alias must be set to indicate which pool to use.

7.8. Using Hikari

To use this integration, the application must include the hibernate-hikari module jar (as well as its dependencies) on the classpath.

Hibernate also provides support for applications to use Hikari connection pool.

Set all of your Hikari settings in Hibernate prefixed by hibernate.hikari. and this ConnectionProvider will pick them up and pass them along to Hikari. Additionally, this ConnectionProvider will pick up the following Hibernate-specific properties and map them to the corresponding Hikari ones (any hibernate.hikari. prefixed ones have precedence):

hibernate.connection.driver_class

Mapped to Hikari’s driverClassName setting

hibernate.connection.url

Mapped to Hikari’s jdbcUrl setting

hibernate.connection.username

Mapped to Hikari’s username setting

hibernate.connection.password

Mapped to Hikari’s password setting

hibernate.connection.isolation

Mapped to Hikari’s transactionIsolation setting. See ConnectionProvider support for transaction isolation setting. Note that Hikari only supports JDBC standard isolation levels (apparently).

hibernate.connection.autocommit

Mapped to Hikari’s autoCommit setting

7.9. Using Hibernate’s built-in (and unsupported) pooling

The built-in connection pool is not supported supported for use.

This section is here just for completeness.

7.10. User-provided Connections

It is possible to use Hibernate by simply passing a Connection to use to the Session when the Session is opened. This usage is discouraged and not discussed here.

7.11. ConnectionProvider support for transaction isolation setting

All of the provided ConnectionProvider implementations, other than DataSourceConnectionProvider, support consistent setting of transaction isolation for all Connections obtained from the underlying pool. The value for hibernate.connection.isolation can be specified in one of 3 formats:

  • the integer value accepted at the JDBC level

  • the name of the java.sql.Connection constant field representing the isolation you would like to use. For example, TRANSACTION_REPEATABLE_READ for java.sql.Connection#TRANSACTION_REPEATABLE_READ. Not that this is only supported for JDBC standard isolation levels, not for isolation levels specific to a particular JDBC driver.

  • a short-name version of the java.sql.Connection constant field without the TRANSACTION_ prefix. For example, REPEATABLE_READ for java.sql.Connection#TRANSACTION_REPEATABLE_READ. Again, this is only supported for JDBC standard isolation levels, not for isolation levels specific to a particular JDBC driver.

7.12. Database Dialect

Although SQL is relatively standardized, each database vendor uses a subset and superset of ANSI SQL defined syntax. This is referred to as the database’s dialect. Hibernate handles variations across these dialects through its org.hibernate.dialect.Dialect class and the various subclasses for each database vendor.

In most cases Hibernate will be able to determine the proper Dialect to use by asking some questions of the JDBC Connection during bootstrap. For information on Hibernate’s ability to determine the proper Dialect to use (and your ability to influence that resolution), see Dialect resolution.

If for some reason it is not able to determine the proper one or you want to use a custom Dialect, you will need to set the hibernate.dialect setting.

Table 4. Provided Dialects
Dialect (short name) Remarks

Cache71

Support for the Caché database, version 2007.1

CUBRID

Support for the CUBRID database, version 8.3. May work with later versions.

DB2

Support for the DB2 database, version 8.2.

DB297

Support for the DB2 database, version 9.7.

DB2390

Support for DB2 Universal Database for OS/390, also known as DB2/390.

DB2400

Support for DB2 Universal Database for iSeries, also known as DB2/400.

DerbyTenFive

Support for the Derby database, version 10.5

DerbyTenSix

Support for the Derby database, version 10.6

DerbyTenSeven

Support for the Derby database, version 10.7

Firebird

Support for the Firebird database

FrontBase

Support for the Frontbase database

H2

Support for the H2 database

HSQL

Support for the HSQL (HyperSQL) database

Informix

Support for the Informix database

Ingres

Support for the Ingres database, version 9.2

Ingres9

Support for the Ingres database, version 9.3. May work with newer versions

Ingres10

Support for the Ingres database, version 10. May work with newer versions

Interbase

Support for the Interbase database.

JDataStore

Support for the JDataStore database

McKoi

Support for the McKoi database

Mimer

Support for the Mimer database, version 9.2.1. May work with newer versions

MySQL5

Support for the MySQL database, version 5.x

MySQL5InnoDB

Support for the MySQL database, version 5.x preferring the InnoDB storage engine when exporting tables.

MySQL57InnoDB

Support for the MySQL database, version 5.7 preferring the InnoDB storage engine when exporting tables. May work with newer versions

MariaDB

Support for the MariadB database. May work with newer versions

MariaDB53

Support for the MariadB database, version 5.3 and newer.

Oracle8i

Support for the Oracle database, version 8i

Oracle9i

Support for the Oracle database, version 9i

Oracle10g

Support for the Oracle database, version 10g

Pointbase

Support for the Pointbase database

PostgresPlus

Support for the Postgres Plus database

PostgreSQL81

Support for the PostgrSQL database, version 8.1

PostgreSQL82

Support for the PostgreSQL database, version 8.2

PostgreSQL9

Support for the PostgreSQL database, version 9. May work with later versions.

Progress

Support for the Progress database, version 9.1C. May work with newer versions.

SAPDB

Support for the SAPDB/MAXDB database.

SQLServer

Support for the SQL Server 2000 database

SQLServer2005

Support for the SQL Server 2005 database

SQLServer2008

Support for the SQL Server 2008 database

Sybase11

Support for the Sybase database, up to version 11.9.2

SybaseAnywhere

Support for the Sybase Anywhere database

SybaseASE15

Support for the Sybase Adaptive Server Enterprise database, version 15

SybaseASE157

Support for the Sybase Adaptive Server Enterprise database, version 15.7. May work with newer versions.

Teradata

Support for the Teradata database

TimesTen

Support for the TimesTen database, version 5.1. May work with newer versions

8. Transactions and concurrency control

It is important to understand that the term transaction has many different yet related meanings in regards to persistence and Object/Relational Mapping. In most use-cases these definitions align, but that is not always the case.

  • Might refer to the physical transaction with the database.

  • Might refer to the logical notion of a transaction as related to a persistence context.

  • Might refer to the application notion of a Unit-of-Work, as defined by the archetypal pattern.

This documentation largely treats the physical and logic notions of a transaction as one-in-the-same.

8.1. Physical Transactions

Hibernate uses the JDBC API for persistence. In the world of Java there are two well-defined mechanism for dealing with transactions in JDBC: JDBC itself and JTA. Hibernate supports both mechanisms for integrating with transactions and allowing applications to manage physical transactions.

Transaction handling per Session is handled by the org.hibernate.resource.transaction.spi.TransactionCoordinator contract, which are built by the org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder service. TransactionCoordinatorBuilder represents a strategy for dealing with transactions whereas TransactionCoordinator represents one instance of that strategy related to a Session. Which TransactionCoordinatorBuilder implementation to use is defined by the hibernate.transaction.coordinator_class setting.

jdbc (the default for non-JPA applications)

Manages transactions via calls to java.sql.Connection

jta

Manages transactions via JTA. See Java EE bootstrapping

If a JPA application does not provide a setting for hibernate.transaction.coordinator_class, Hibernate will automatically build the proper transaction coordinator based on the transaction type for the persistence unit.

If a non-JPA application does not provide a setting for hibernate.transaction.coordinator_class, Hibernate will use jdbc as the default. This default will cause problems if the application actually uses JTA-based transactions. A non-JPA application that uses JTA-based transactions should explicitly set hibernate.transaction.coordinator_class=jta or provide a custom org.hibernate.resource.transaction.TransactionCoordinatorBuilder that builds a org.hibernate.resource.transaction.TransactionCoordinator that properly coordinates with JTA-based transactions.

For details on implementing a custom TransactionCoordinatorBuilder, or simply better understanding how it works, see the Integrations Guide.

Hibernate uses JDBC connections and JTA resources directly, without adding any additional locking behavior. Hibernate does not lock objects in memory. The behavior defined by the isolation level of your database transactions does not change when you use Hibernate. The Hibernate Session acts as a transaction-scoped cache providing repeatable reads for lookup by identifier and queries that result in loading entities.

To reduce lock contention in the database, the physical database transaction needs to be as short as possible. Long database transactions prevent your application from scaling to a highly-concurrent load. Do not hold a database transaction open during end-user-level work, but open it after the end-user-level work is finished. This is concept is referred to as transactional write-behind.

8.2. JTA configuration

Interaction with a JTA system is consolidated behind a single contract named org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform which exposes access to the javax.transaction.TransactionManager and javax.transaction.UserTransaction for that system as well as exposing the ability to register javax.transaction.Synchronization instances, check transaction status, etc.

Generally, JtaPlatform will need access to JNDI to resolve the JTA TransactionManager, UserTransaction, etc. See JNDI chapter for details on configuring access to JNDI.

Hibernate tries to discover the JtaPlatform it should use through the use of another service named org.hibernate.engine.transaction.jta.platform.spi.JtaPlatformResolver. If that resolution does not work, or if you wish to provide a custom implementation you will need to specify the hibernate.transaction.jta.platform setting. Hibernate provides many implementations of the JtaPlatform contract, all with short names:

Borland

JtaPlatform for the Borland Enterprise Server.

Bitronix

JtaPlatform for Bitronix.

JBossAS

JtaPlatform for Arjuna/JBossTransactions/Narayana when used within the JBoss/WildFly Application Server.

JBossTS

JtaPlatform for Arjuna/JBossTransactions/Narayana when used standalone.

JOnAS

JtaPlatform for JOTM when used within JOnAS.

JOTM

JtaPlatform for JOTM when used standalone.

JRun4

JtaPlatform for the JRun 4 Application Server.

OC4J

JtaPlatform for Oracle’s OC4J container.

Orion

JtaPlatform for the Orion Application Server.

Resin

JtaPlatform for the Resin Application Server.

SapNetWeaver

JtaPlatform for the SAP NetWeaver Application Server.

SunOne

JtaPlatform for the SunOne Application Server.

Weblogic

JtaPlatform for the Weblogic Application Server.

WebSphere

JtaPlatform for older versions of the WebSphere Application Server.

WebSphereExtended

JtaPlatform for newer versions of the WebSphere Application Server.

8.3. Hibernate Transaction API

Hibernate provides an API for helping to isolate applications from the differences in the underlying physical transaction system in use. Based on the configured TransactionCoordinatorBuilder, Hibernate will simply do the right thing when this transaction API is used by the application. This allows your applications and components to be more portable move around into different environments.

To use this API, you would obtain the org.hibernate.Transaction from the Session. Transaction allows for all the normal operations you’d expect: begin, commit and rollback, and it even exposes some cool methods like:

markRollbackOnly

that works in both JTA and JDBC

getTimeout and setTimeout

that again work in both JTA and JDBC

registerSynchronization

that allows you to register JTA Synchronizations even in non-JTA environments. In fact in both JTA and JDBC environments, these Synchronizations are kept locally by Hibernate. In JTA environments, Hibernate will only ever register one single Synchronization with the TransactionManager to avoid ordering problems.

Additionally, it exposes a getStatus method that returns an org.hibernate.resource.transaction.spi.TransactionStatus enum. This method checks with the underlying transaction system if needed, so care should be taken to minimize its use; it can have a big performance impact in certain JTA set ups.

Let’s take a look at using the Transaction API in the various environments.

Example 354. Using Transaction API in JDBC
StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
        // "jdbc" is the default, but for explicitness
        .applySetting( AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, "jdbc" )
        .build();

Metadata metadata = new MetadataSources( serviceRegistry )
        .addAnnotatedClass( Customer.class )
        .getMetadataBuilder()
        .build();

SessionFactory sessionFactory = metadata.getSessionFactoryBuilder()
        .build();

Session session = sessionFactory.openSession();
try {
    // calls Connection#setAutoCommit( false ) to
    // signal start of transaction
    session.getTransaction().begin();

    session.createQuery( "UPDATE customer set NAME = 'Sir. '||NAME" )
            .executeUpdate();

    // calls Connection#commit(), if an error
    // happens we attempt a rollback
    session.getTransaction().commit();
}
catch ( Exception e ) {
    // we may need to rollback depending on
    // where the exception happened
    if ( session.getTransaction().getStatus() == TransactionStatus.ACTIVE
            || session.getTransaction().getStatus() == TransactionStatus.MARKED_ROLLBACK ) {
        session.getTransaction().rollback();
    }
    // handle the underlying error
}
finally {
    session.close();
    sessionFactory.close();
}
Example 355. Using Transaction API in JTA (CMT)
StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
        // "jdbc" is the default, but for explicitness
        .applySetting( AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, "jta" )
        .build();

Metadata metadata = new MetadataSources( serviceRegistry )
        .addAnnotatedClass( Customer.class )
        .getMetadataBuilder()
        .build();

SessionFactory sessionFactory = metadata.getSessionFactoryBuilder()
        .build();

// Note: depending on the JtaPlatform used and some optional settings,
// the underlying transactions here will be controlled through either
// the JTA TransactionManager or UserTransaction

Session session = sessionFactory.openSession();
try {
    // Since we are in CMT, a JTA transaction would
    // already have been started.  This call essentially
    // no-ops
    session.getTransaction().begin();

    Number customerCount = (Number) session.createQuery( "select count(c) from Customer c" ).uniqueResult();

    // Since we did not start the transaction ( CMT ),
    // we also will not end it.  This call essentially
    // no-ops in terms of transaction handling.
    session.getTransaction().commit();
}
catch ( Exception e ) {
    // again, the rollback call here would no-op (aside from
    // marking the underlying CMT transaction for rollback only).
    if ( session.getTransaction().getStatus() == TransactionStatus.ACTIVE
            || session.getTransaction().getStatus() == TransactionStatus.MARKED_ROLLBACK ) {
        session.getTransaction().rollback();
    }
    // handle the underlying error
}
finally {
    session.close();
    sessionFactory.close();
}
Example 356. Using Transaction API in JTA (BMT)
StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
        // "jdbc" is the default, but for explicitness
        .applySetting( AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, "jta" )
        .build();

Metadata metadata = new MetadataSources( serviceRegistry )
        .addAnnotatedClass( Customer.class )
        .getMetadataBuilder()
        .build();

SessionFactory sessionFactory = metadata.getSessionFactoryBuilder()
        .build();

// Note: depending on the JtaPlatform used and some optional settings,
// the underlying transactions here will be controlled through either
// the JTA TransactionManager or UserTransaction

Session session = sessionFactory.openSession();
try {
    // Assuming a JTA transaction is not already active,
    // this call the TM/UT begin method.  If a JTA
    // transaction is already active, we remember that
    // the Transaction associated with the Session did
    // not "initiate" the JTA transaction and will later
    // nop-op the commit and rollback calls...
    session.getTransaction().begin();

    session.persist( new Customer(  ) );
    Customer customer = (Customer) session.createQuery( "select c from Customer c" ).uniqueResult();

    // calls TM/UT commit method, assuming we are initiator.
    session.getTransaction().commit();
}
catch ( Exception e ) {
    // we may need to rollback depending on
    // where the exception happened
    if ( session.getTransaction().getStatus() == TransactionStatus.ACTIVE
            || session.getTransaction().getStatus() == TransactionStatus.MARKED_ROLLBACK ) {
        // calls TM/UT commit method, assuming we are initiator;
        // otherwise marks the JTA transaction for rollback only
        session.getTransaction().rollback();
    }
    // handle the underlying error
}
finally {
    session.close();
    sessionFactory.close();
}

In the CMT case we really could have omitted all of the Transaction calls. But the point of the examples was to show that the Transaction API really does insulate your code from the underlying transaction mechanism. In fact, if you strip away the comments and the single configuration setting supplied at bootstrap, the code is exactly the same in all 3 examples. In other words, we could develop that code and drop it, as-is, in any of the 3 transaction environments.

The Transaction API tries hard to make the experience consistent across all environments. To that end, it generally defers to the JTA specification when there are differences (for example automatically trying rollback on a failed commit).

8.4. Contextual sessions

Most applications using Hibernate need some form of contextual session, where a given session is in effect throughout the scope of a given context. However, across applications the definition of what constitutes a context is typically different; different contexts define different scopes to the notion of current. Applications using Hibernate prior to version 3.0 tended to utilize either home-grown ThreadLocal-based contextual sessions, helper classes such as HibernateUtil, or utilized third-party frameworks, such as Spring or Pico, which provided proxy/interception-based contextual sessions.

Starting with version 3.0.1, Hibernate added the SessionFactory.getCurrentSession() method. Initially, this assumed usage of JTA transactions, where the JTA transaction defined both the scope and context of a current session. Given the maturity of the numerous stand-alone JTA TransactionManager implementations, most, if not all, applications should be using JTA transaction management, whether or not they are deployed into a J2EE container. Based on that, the JTA-based contextual sessions are all you need to use.

However, as of version 3.1, the processing behind SessionFactory.getCurrentSession() is now pluggable. To that end, a new extension interface, org.hibernate.context.spi.CurrentSessionContext, and a new configuration parameter, hibernate.current_session_context_class, have been added to allow pluggability of the scope and context of defining current sessions.

See the Javadocs for the org.hibernate.context.spi.CurrentSessionContext interface for a detailed discussion of its contract. It defines a single method, currentSession(), by which the implementation is responsible for tracking the current contextual session. Out-of-the-box, Hibernate comes with three implementations of this interface:

org.hibernate.context.internal.JTASessionContext

current sessions are tracked and scoped by a JTA transaction. The processing here is exactly the same as in the older JTA-only approach. See the Javadocs for more details.

  • org.hibernate.context.internal.ThreadLocalSessionContext:current sessions are tracked by thread of execution. See the Javadocs for more details.

  • org.hibernate.context.internal.ManagedSessionContext: current sessions are tracked by thread of execution. However, you are responsible to bind and unbind a Session instance with static methods on this class: it does not open, flush, or close a Session. See the Javadocs for details.

Typically, the value of this parameter would just name the implementation class to use. For the three out-of-the-box implementations, however, there are three corresponding short names: jta, thread, and managed.

The first two implementations provide a one session - one database transaction programming model. This is also known and used as session-per-request. The beginning and end of a Hibernate session is defined by the duration of a database transaction. If you use programmatic transaction demarcation in plain Java SE without JTA, you are advised to use the Hibernate Transaction API to hide the underlying transaction system from your code. If you use JTA, you can utilize the JTA interfaces to demarcate transactions. If you execute in an EJB container that supports CMT, transaction boundaries are defined declaratively and you do not need any transaction or session demarcation operations in your code. Refer to Transactions and concurrency control for more information and code examples.

The hibernate.current_session_context_class configuration parameter defines which org.hibernate.context.spi.CurrentSessionContext implementation should be used. For backwards compatibility, if this configuration parameter is not set but a org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform is configured, Hibernate will use the org.hibernate.context.internal.JTASessionContext.

8.5. Transactional patterns (and anti-patterns)

8.6. Session-per-operation anti-pattern

This is an anti-pattern of opening and closing a Session for each database call in a single thread. It is also an anti-pattern in terms of database transactions. Group your database calls into a planned sequence. In the same way, do not auto-commit after every SQL statement in your application. Hibernate disables, or expects the application server to disable, auto-commit mode immediately. Database transactions are never optional. All communication with a database must be encapsulated by a transaction. Avoid auto-commit behavior for reading data because many small transactions are unlikely to perform better than one clearly-defined unit of work, and are more difficult to maintain and extend.

Using auto-commit does not circumvent database transactions. Instead, when in auto-commit mode, JDBC drivers simply perform each call in an implicit transaction call. It is as if your application called commit after each and every JDBC call.

8.7. Session-per-request pattern

This is the most common transaction pattern. The term request here relates to the concept of a system that reacts to a series of requests from a client/user. Web applications are a prime example of this type of system, though certainly not the only one. At the beginning of handling such a request, the application opens a Hibernate Session, starts a transaction, performs all data related work, ends the transaction and closes the Session. The crux of the pattern is the one-to-one relationship between the transaction and the Session.

Within this pattern there is a common technique of defining a current session to simplify the need of passing this Session around to all the application components that may need access to it. Hibernate provides support for this technique through the getCurrentSession method of the SessionFactory. The concept of a current session has to have a scope that defines the bounds in which the notion of current is valid. This is the purpose of the org.hibernate.context.spi.CurrentSessionContext contract.

There are 2 reliable defining scopes:

  • First is a JTA transaction because it allows a callback hook to know when it is ending, which gives Hibernate a chance to close the Session and clean up. This is represented by the org.hibernate.context.internal.JTASessionContext implementation of the org.hibernate.context.spi.CurrentSessionContext contract. Using this implementation, a Session will be opened the first time getCurrentSession is called within that transaction.

  • Secondly is this application request cycle itself. This is best represented with the org.hibernate.context.internal.ManagedSessionContext implementation of the org.hibernate.context.spi.CurrentSessionContext contract. Here an external component is responsible for managing the lifecycle and scoping of a current session. At the start of such a scope, ManagedSessionContext#bind() method is called passing in the Session. At the end, its unbind() method is called. Some common examples of such external components include:

    • javax.servlet.Filter implementation

    • AOP interceptor with a pointcut on the service methods

    • A proxy/interception container

The getCurrentSession() method has one downside in a JTA environment. If you use it, after_statement connection release mode is also used by default. Due to a limitation of the JTA specification, Hibernate cannot automatically clean up any unclosed ScrollableResults or Iterator instances returned by scroll() or iterate(). Release the underlying database cursor by calling ScrollableResults#close() or Hibernate.close(Iterator) explicitly from a finally block.

8.8. Conversations

The session-per-request pattern is not the only valid way of designing units of work. Many business processes require a whole series of interactions with the user that are interleaved with database accesses. In web and enterprise applications, it is not acceptable for a database transaction to span a user interaction. Consider the following example:

The first screen of a dialog opens. The data seen by the user is loaded in a particular Session and database transaction. The user is free to modify the objects.

The user uses a UI element to save their work after five minutes of editing. The modifications are made persistent. The user also expects to have exclusive access to the data during the edit session.

Even though we have multiple databases access here, from the point of view of the user, this series of steps represents a single unit of work. There are many ways to implement this in your application.

A first naive implementation might keep the Session and database transaction open while the user is editing, using database-level locks to prevent other users from modifying the same data and to guarantee isolation and atomicity. This is an anti-pattern because lock contention is a bottleneck which will prevent scalability in the future.

Several database transactions are used to implement the conversation. In this case, maintaining isolation of business processes becomes the partial responsibility of the application tier. A single conversation usually spans several database transactions. These multiple database accesses can only be atomic as a whole if only one of these database transactions (typically the last one) stores the updated data. All others only read data. A common way to receive this data is through a wizard-style dialog spanning several request/response cycles. Hibernate includes some features which make this easy to implement.

Automatic Versioning

Hibernate can perform automatic optimistic concurrency control for you. It can automatically detect (at the end of the conversation) if a concurrent modification occurred during user think time.

Detached Objects

If you decide to use the session-per-request pattern, all loaded instances will be in the detached state during user think time. Hibernate allows you to reattach the objects and persist the modifications. The pattern is called session-per-request-with-detached-objects. Automatic versioning is used to isolate concurrent modifications.

Extended Session

The Hibernate Session can be disconnected from the underlying JDBC connection after the database transaction has been committed and reconnected when a new client request occurs. This pattern is known as session-per-conversation and makes even reattachment unnecessary. Automatic versioning is used to isolate concurrent modifications and the Session will not be allowed to flush automatically, only explicitly.

Session-per-request-with-detached-objects and session-per-conversation each have advantages and disadvantages.

8.9. Session-per-application

The session-per-application is also considered an anti-pattern. The Hibernate Session, like the JPA EntityManager, is not a thread-safe object and it is intended to be confined to a single thread at once. If the Session is shared among multiple threads, there will be race conditions as well as visibility issues , so beware of this.

An exception thrown by Hibernate means you have to rollback your database transaction and close the Session immediately. If your Session is bound to the application, you have to stop the application. Rolling back the database transaction does not put your business objects back into the state they were at the start of the transaction. This means that the database state and the business objects will be out of sync. Usually, this is not a problem because exceptions are not recoverable and you will have to start over after rollback anyway.

The Session caches every object that is in a persistent state (watched and checked for dirty state by Hibernate). If you keep it open for a long time or simply load too much data, it will grow endlessly until you get an OutOfMemoryException. One solution is to call clear() and evict() to manage the Session cache, but you should consider a Stored Procedure if you need mass data operations. Some solutions are shown in the Batching chapter. Keeping a Session open for the duration of a user session also means a higher probability of stale data.

9. JNDI

Hibernate does optionally interact with JNDI on the application’s behalf. Generally, it does this when the application:

  • has asked the SessionFactory be bound to JNDI

  • has specified a DataSource to use by JNDI name

  • is using JTA transactions and the JtaPlatform needs to do JNDI lookups for TransactionManager, UserTransaction, etc

All of these JNDI calls route through a single service whose role is org.hibernate.engine.jndi.spi.JndiService. The standard JndiService accepts a number of configuration settings

hibernate.jndi.class

names the javax.naming.InitialContext implementation class to use. See javax.naming.Context#INITIAL_CONTEXT_FACTORY

hibernate.jndi.url

names the JNDI InitialContext connection url. See javax.naming.Context.PROVIDER_URL

Any other settings prefixed with hibernate.jndi. will be collected and passed along to the JNDI provider.

The standard JndiService assumes that all JNDI calls are relative to the same InitialContext. If your application uses multiple naming servers for whatever reason, you will need a custom JndiService implementation to handle those details.

10. Locking

In a relational database, locking refers to actions taken to prevent data from changing between the time it is read and the time is used.

Your locking strategy can be either optimistic or pessimistic.

Optimistic

Optimistic locking assumes that multiple transactions can complete without affecting each other, and that therefore transactions can proceed without locking the data resources that they affect. Before committing, each transaction verifies that no other transaction has modified its data. If the check reveals conflicting modifications, the committing transaction rolls back.

Pessimistic

Pessimistic locking assumes that concurrent transactions will conflict with each other, and requires resources to be locked after they are read and only unlocked after the application has finished using the data.

Hibernate provides mechanisms for implementing both types of locking in your applications.

10.1. Optimistic

When your application uses long transactions or conversations that span several database transactions, you can store versioning data so that if the same entity is updated by two conversations, the last to commit changes is informed of the conflict, and does not override the other conversation’s work. This approach guarantees some isolation, but scales well and works particularly well in read-often-write-sometimes situations.

Hibernate provides two different mechanisms for storing versioning information, a dedicated version number or a timestamp.

A version or timestamp property can never be null for a detached instance. Hibernate detects any instance with a null version or timestamp as transient, regardless of other unsaved-value strategies that you specify. Declaring a nullable version or timestamp property is an easy way to avoid problems with transitive reattachment in Hibernate, especially useful if you use assigned identifiers or composite keys.

10.1.1. Mapping optimistic locking

JPA defines support for optimistic locking based on either a version (sequential numeric) or timestamp strategy. To enable this style of optimistic locking simply add the javax.persistence.Version to the persistent attribute that defines the optimistic locking value. According to JPA, the valid types for these attributes are limited to:

  • int or Integer

  • short or Short

  • long or Long

  • java.sql.Timestamp

However, Hibernate allows you to use even Java 8 Date/Time types, such as Instant.

Example 357. @Version annotation mapping
@Entity(name = "Person")
public static class Person {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "`name`")
    private String name;

    @Version
    private long version;

    //Getters and setters are omitted for brevity

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

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "`name`")
    private String name;

    @Version
    private Timestamp version;

    //Getters and setters are omitted for brevity

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

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "`name`")
    private String name;

    @Version
    private Instant version;

    //Getters and setters are omitted for brevity

}
Dedicated version number

The version number mechanism for optimistic locking is provided through a @Version annotation.

Example 358. @Version annotation
@Version
private long version;

Here, the version property is mapped to the version column, and the entity manager uses it to detect conflicting updates, and prevent the loss of updates that would otherwise be overwritten by a last-commit-wins strategy.

The version column can be any kind of type, as long as you define and implement the appropriate UserVersionType.

Your application is forbidden from altering the version number set by Hibernate. To artificially increase the version number, see the documentation for properties LockModeType.OPTIMISTIC_FORCE_INCREMENT or LockModeType.PESSIMISTIC_FORCE_INCREMENT check in the Hibernate Entity Manager reference documentation.

If the version number is generated by the database, such as a trigger, use the annotation @org.hibernate.annotations.Generated(GenerationTime.ALWAYS) on the version attribute.

Timestamp

Timestamps are a less reliable way of optimistic locking than version numbers, but can be used by applications for other purposes as well. Timestamping is automatically used if you the @Version annotation on a Date or Calendar property type.

Example 359. Using timestamps for optimistic locking
@Version
private Date version;

Hibernate can retrieve the timestamp value from the database or the JVM, by reading the value you specify for the @org.hibernate.annotations.Source annotation. The value can be either org.hibernate.annotations.SourceType.DB or org.hibernate.annotations.SourceType.VM. The default behavior is to use the database, and is also used if you don’t specify the annotation at all.

The timestamp can also be generated by the database instead of Hibernate if you use the @org.hibernate.annotations.Generated(GenerationTime.ALWAYS) or the @Source annotation.

Example 360. Database-generated version timestamp mapping
@Entity(name = "Person")
public static class Person {

    @Id
    private Long id;

    private String firstName;

    private String lastName;

    @Version
    @Source(value = SourceType.DB)
    private Date version;
}

Now, when persisting a Person entity, Hibernate calls the database-specific current timestamp retrieval function:

Example 361. Database-generated version timestamp example
Person person = new Person();
person.setId( 1L );
person.setFirstName( "John" );
person.setLastName( "Doe" );
assertNull( person.getVersion() );

entityManager.persist( person );
assertNotNull( person.getVersion() );
CALL current_timestamp()

INSERT INTO
    Person
    (firstName, lastName, version, id)
VALUES
    (?, ?, ?, ?)

-- binding parameter [1] as [VARCHAR]   - [John]
-- binding parameter [2] as [VARCHAR]   - [Doe]
-- binding parameter [3] as [TIMESTAMP] - [2017-05-18 12:03:03.808]
-- binding parameter [4] as [BIGINT]    - [1]
Excluding attributes

By default, every entity attribute modification is going to trigger a version incrementation. If there is an entity property which should not bump up the entity version, then you need to annotate it with the Hibernate @OptimisticLock annotation, as illustrated in the following example.

Example 362. @OptimisticLock mapping example
@Entity(name = "Phone")
public static class Phone {

    @Id
    private Long id;

    @Column(name = "`number`")
    private String number;

    @OptimisticLock( excluded = true )
    private long callCount;

    @Version
    private Long version;

    //Getters and setters are omitted for brevity

    public void incrementCallCount() {
        this.callCount++;
    }
}

This way, if one tread modifies the Phone number while a second thread increments the callCount attribute, the two concurrent transactions are not going to conflict as illustrated by the following example.

Example 363. @OptimisticLock exlude attribute example
doInJPA( this::entityManagerFactory, entityManager -> {
    Phone phone = entityManager.find( Phone.class, 1L );
    phone.setNumber( "+123-456-7890" );

    doInJPA( this::entityManagerFactory, _entityManager -> {
        Phone _phone = _entityManager.find( Phone.class, 1L );
        _phone.incrementCallCount();

        log.info( "Bob changes the Phone call count" );
    } );

    log.info( "Alice changes the Phone number" );
} );
-- Bob changes the Phone call count

update
    Phone
set
    callCount = 1,
    "number" = '123-456-7890',
    version = 0
where
    id = 1
    and version = 0

-- Alice changes the Phone number

update
    Phone
set
    callCount = 0,
    "number" = '+123-456-7890',
    version = 1
where
    id = 1
    and version = 0

When Bob changes the Phone entity callCount, the entity version is not bumped up. That’s why Alice’s UPDATE succeeds since the entity version is still 0, even if Bob has changed the record since Alice loaded it.

Although there is no conflict between Bob and Alice, Alice’s UPDATE overrides Bob’s change to the callCount attribute.

For this reason, you should only use this feature if you can accommodate lost updates on the excluded entity properties.

Versionless optimistic locking

Although the default @Version property optimistic locking mechanism is sufficient in many situations, sometimes, you need rely on the actual database row column values to prevent lost updates.

Hibernate supports a form of optimistic locking that does not require a dedicated "version attribute". This is also useful for use with modeling legacy schemas.

The idea is that you can get Hibernate to perform "version checks" using either all of the entity’s attributes, or just the attributes that have changed. This is achieved through the use of the @OptimisticLocking annotation which defines a single attribute of type org.hibernate.annotations.OptimisticLockType.

There are 4 available OptimisticLockTypes:

NONE

optimistic locking is disabled even if there is a @Version annotation present

VERSION (the default)

performs optimistic locking based on a @Version as described above

ALL

performs optimistic locking based on all fields as part of an expanded WHERE clause restriction for the UPDATE/DELETE SQL statements

DIRTY

performs optimistic locking based on dirty fields as part of an expanded WHERE clause restriction for the UPDATE/DELETE SQL statements

Versionless optimistic locking using OptimisticLockType.ALL
Example 364. OptimisticLockType.ALL mapping example
@Entity(name = "Person")
@OptimisticLocking(type = OptimisticLockType.ALL)
@DynamicUpdate
public static class Person {

    @Id
    private Long id;

    @Column(name = "`name`")
    private String name;

    private String country;

    private String city;

    @Column(name = "created_on")
    private Timestamp createdOn;

    //Getters and setters are omitted for brevity
}

When you need to modify the Person entity above:

Example 365. OptimisticLockType.ALL update example
Person person = entityManager.find( Person.class, 1L );
person.setCity( "Washington D.C." );
UPDATE
    Person
SET
    city=?
WHERE
    id=?
    AND city=?
    AND country=?
    AND created_on=?
    AND "name"=?

-- binding parameter [1] as [VARCHAR] - [Washington D.C.]
-- binding parameter [2] as [BIGINT] - [1]
-- binding parameter [3] as [VARCHAR] - [New York]
-- binding parameter [4] as [VARCHAR] - [US]
-- binding parameter [5] as [TIMESTAMP] - [2016-11-16 16:05:12.876]
-- binding parameter [6] as [VARCHAR] - [John Doe]

As you can see, all the columns of the associated database row are used in the WHERE clause. If any column has changed after the row was loaded, there won’t be any match, and a StaleStateException or an OptimisticLockException is going to be thrown.

When using OptimisticLockType.ALL, you should also use @DynamicUpdate because the UPDATE statement must take into consideration all the entity property values.

Versionless optimistic locking using OptimisticLockType.DIRTY

The OptimisticLockType.DIRTY differs from OptimisticLockType.ALL in that it only takes into consideration the entity properties that have changed since the entity was loaded in the currently running Persistence Context.

Example 366. OptimisticLockType.DIRTY mapping example
@Entity(name = "Person")
@OptimisticLocking(type = OptimisticLockType.DIRTY)
@DynamicUpdate
@SelectBeforeUpdate
public static class Person {

    @Id
    private Long id;

    @Column(name = "`name`")
    private String name;

    private String country;

    private String city;

    @Column(name = "created_on")
    private Timestamp createdOn;

    //Getters and setters are omitted for brevity
}

When you need to modify the Person entity above:

Example 367. OptimisticLockType.DIRTY update example
Person person = entityManager.find( Person.class, 1L );
person.setCity( "Washington D.C." );
UPDATE
    Person
SET
    city=?
WHERE
    id=?
    and city=?

-- binding parameter [1] as [VARCHAR] - [Washington D.C.]
-- binding parameter [2] as [BIGINT] - [1]
-- binding parameter [3] as [VARCHAR] - [New York]

This time, only the database column that has changed was used in the WHERE clause.

The main advantage of OptimisticLockType.DIRTY over OptimisticLockType.ALL and the default OptimisticLockType.VERSION used implicitly along with the @Version mapping, is that it allows you to minimize the risk of OptimisticLockException across non-overlapping entity property changes.

When using OptimisticLockType.DIRTY, you should also use @DynamicUpdate because the UPDATE statement must take into consideration all the dirty entity property values, and also the @SelectBeforeUpdate annotation so that detached entities are properly handled by the Session#update(entity) operation.

10.2. Pessimistic

Typically, you only need to specify an isolation level for the JDBC connections and let the database handle locking issues. If you do need to obtain exclusive pessimistic locks or re-obtain locks at the start of a new transaction, Hibernate gives you the tools you need.

Hibernate always uses the locking mechanism of the database, and never lock objects in memory.

10.3. LockMode and LockModeType

Long before JPA 1.0, Hibernate already defined various explicit locking strategies through its LockMode enumeration. JPA comes with its own LockModeType enumeration which defines similar strategies as the Hibernate-native LockMode.

LockModeType LockMode Description

NONE

NONE

The absence of a lock. All objects switch to this lock mode at the end of a Transaction. Objects associated with the session via a call to update() or saveOrUpdate() also start out in this lock mode.

READ and OPTIMISTIC

READ

The entity version is checked towards the end of the currently running transaction.

WRITE and OPTIMISTIC_FORCE_INCREMENT

WRITE

The entity version is incremented automatically even if the entity has not changed.

PESSIMISTIC_FORCE_INCREMENT

PESSIMISTIC_FORCE_INCREMENT

The entity is locked pessimistically and its version is incremented automatically even if the entity has not changed.

PESSIMISTIC_READ

PESSIMISTIC_READ

The entity is locked pessimistically using a shared lock, if the database supports such a feature. Otherwise, an explicit lock is used.

PESSIMISTIC_WRITE

PESSIMISTIC_WRITE, UPGRADE

The entity is locked using an explicit lock.

PESSIMISTIC_WRITE with a javax.persistence.lock.timeout setting of 0

UPGRADE_NOWAIT

The lock acquisition request fails fast if the row s already locked.

PESSIMISTIC_WRITE with a javax.persistence.lock.timeout setting of -2

UPGRADE_SKIPLOCKED

The lock acquisition request skips the already locked rows. It uses a SELECT …​ FOR UPDATE SKIP LOCKED in Oracle and PostgreSQL 9.5, or SELECT …​ with (rowlock, updlock, readpast) in SQL Server.

The explicit user request mentioned above occurs as a consequence of any of the following actions:

  • a call to Session.load(), specifying a LockMode.

  • a call to Session.lock().

  • a call to Query.setLockMode().

If you call Session.load() with option UPGRADE, UPGRADE_NOWAIT or UPGRADE_SKIPLOCKED, and the requested object is not already loaded by the session, the object is loaded using SELECT …​ FOR UPDATE.

If you call load() for an object that is already loaded with a less restrictive lock than the one you request, Hibernate calls lock() for that object.

Session.lock() performs a version number check if the specified lock mode is READ, UPGRADE, UPGRADE_NOWAIT or UPGRADE_SKIPLOCKED. In the case of UPGRADE, UPGRADE_NOWAIT or UPGRADE_SKIPLOCKED, the SELECT …​ FOR UPDATE syntax is used.

If the requested lock mode is not supported by the database, Hibernate uses an appropriate alternate mode instead of throwing an exception. This ensures that applications are portable.

10.4. JPA locking query hints

JPA 2.0 introduced two query hints:

javax.persistence.lock.timeout

it gives the number of milliseconds a lock acquisition request will wait before throwing an exception

javax.persistence.lock.scope

defines the scope of the lock acquisition request. The scope can either be NORMAL (default value) or EXTENDED. The EXTENDED scope will cause a lock acquisition request to be passed to other owned table structured (e.g. @Inheritance(strategy=InheritanceType.JOINED), @ElementCollection)

Example 368. javax.persistence.lock.timeout example
entityManager.find(
    Person.class, id, LockModeType.PESSIMISTIC_WRITE,
    Collections.singletonMap( "javax.persistence.lock.timeout", 200 )
);
SELECT explicitlo0_.id     AS id1_0_0_,
       explicitlo0_."name" AS name2_0_0_
FROM   person explicitlo0_
WHERE  explicitlo0_.id = 1
FOR UPDATE wait 2

Not all JDBC database drivers support setting a timeout value for a locking request. If not supported, the Hibernate dialect ignores this query hint.

The javax.persistence.lock.scope is not yet supported as specified by the JPA standard.

10.5. The buildLockRequest API

Traditionally, Hibernate offered the Session#lock() method for acquiring an optimistic or a pessimistic lock on a given entity. Because varying the locking options was difficult when using a single LockMode parameter, Hibernate has added the Session#buildLockRequest() method API.

The following example shows how to obtain shared database lock without waiting for the lock acquisition request.

Example 369. buildLockRequest example
Person person = entityManager.find( Person.class, id );
Session session = entityManager.unwrap( Session.class );
session
    .buildLockRequest( LockOptions.NONE )
    .setLockMode( LockMode.PESSIMISTIC_READ )
    .setTimeOut( LockOptions.NO_WAIT )
    .lock( person );
SELECT p.id AS id1_0_0_ ,
       p.name AS name2_0_0_
FROM   Person p
WHERE  p.id = 1

SELECT id
FROM   Person
WHERE  id = 1
FOR    SHARE NOWAIT

10.6. Follow-on-locking

When using Oracle, the FOR UPDATE exclusive locking clause cannot be used with:

  • DISTINCT

  • GROUP BY

  • UNION

  • inlined views (derived tables), therefore, affecting the legacy Oracle pagination mechanism as well.

For this reason, Hibernate uses secondary selects to lock the previously fetched entities.

Example 370. Follow-on-locking example
List<Person> persons = entityManager.createQuery(
    "select DISTINCT p from Person p", Person.class)
.setLockMode( LockModeType.PESSIMISTIC_WRITE )
.getResultList();
SELECT DISTINCT p.id as id1_0_, p."name" as name2_0_
FROM Person p

SELECT id
FROM Person
WHERE id = 1 FOR UPDATE

SELECT id
FROM Person
WHERE id = 1 FOR UPDATE

To avoid the N+1 query problem, a separate query can be used to apply the lock using the associated entity identifiers.

Example 371. Secondary query entity locking
List<Person> persons = entityManager.createQuery(
    "select DISTINCT p from Person p", Person.class)
.getResultList();

entityManager.createQuery(
    "select p.id from Person p where p in :persons")
.setLockMode( LockModeType.PESSIMISTIC_WRITE )
.setParameter( "persons", persons )
.getResultList();
SELECT DISTINCT p.id as id1_0_, p."name" as name2_0_
FROM Person p

SELECT p.id as col_0_0_
FROM Person p
WHERE p.id IN ( 1 , 2 )
FOR UPDATE

The lock request was moved from the original query to a secondary one which takes the previously fetched entities to lock their associated database records.

Prior to Hibernate 5.2.1, the the follow-on-locking mechanism was applied uniformly to any locking query executing on Oracle. Since 5.2.1, the Oracle Dialect tries to figure out if the current query demand the follow-on-locking mechanism.

Even more important is that you can overrule the default follow-on-locking detection logic and explicitly enable or disable it on a per query basis.

Example 372. Disabling the follow-on-locking mechanism explicitly
List<Person> persons = entityManager.createQuery(
    "select p from Person p", Person.class)
.setMaxResults( 10 )
.unwrap( Query.class )
.setLockOptions(
    new LockOptions( LockMode.PESSIMISTIC_WRITE )
        .setFollowOnLocking( false ) )
.getResultList();
SELECT *
FROM (
    SELECT p.id as id1_0_, p."name" as name2_0_
    FROM Person p
)
WHERE rownum <= 10
FOR UPDATE

The follow-on-locking mechanism should be explicitly enabled only if the current executing query fails because the FOR UPDATE clause cannot be applied, meaning that the Dialect resolving mechanism needs to be further improved.

11. Fetching

Fetching, essentially, is the process of grabbing data from the database and making it available to the application. Tuning how an application does fetching is one of the biggest factors in determining how an application will perform. Fetching too much data, in terms of width (values/columns) and/or depth (results/rows), adds unnecessary overhead in terms of both JDBC communication and ResultSet processing. Fetching too little data might cause additional fetching to be needed. Tuning how an application fetches data presents a great opportunity to influence the application overall performance.

11.1. The basics

The concept of fetching breaks down into two different questions.

  • When should the data be fetched? Now? Later?

  • How should the data be fetched?

"now" is generally termed eager or immediate. "later" is generally termed lazy or delayed.

There are a number of scopes for defining fetching:

static

Static definition of fetching strategies is done in the mappings. The statically-defined fetch strategies is used in the absence of any dynamically defined strategies

SELECT

Performs a separate SQL select to load the data. This can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed). This is the strategy generally termed N+1.

JOIN

Inherently an EAGER style of fetching. The data to be fetched is obtained through the use of an SQL outer join.

BATCH

Performs a separate SQL select to load a number of related data items using an IN-restriction as part of the SQL WHERE-clause based on a batch size. Again, this can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed).

SUBSELECT

Performs a separate SQL select to load associated data based on the SQL restriction used to load the owner. Again, this can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed).

dynamic (sometimes referred to as runtime)

Dynamic definition is really use-case centric. There are multiple ways to define dynamic fetching:

fetch profiles

defined in mappings, but can be enabled/disabled on the Session.

HQL/JPQL

and both Hibernate and JPA Criteria queries have the ability to specify fetching, specific to said query.

entity graphs

Starting in Hibernate 4.2 (JPA 2.1) this is also an option.

11.2. Direct fetching vs entity queries

To see the difference between direct fetching and entity queries in regard to eagerly fetched associations, consider the following entities:

Example 373. Domain model
@Entity(name = "Department")
public static class Department {

    @Id
    private Long id;

    //Getters and setters omitted for brevity
}

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

    @Id
    private Long id;

    @NaturalId
    private String username;

    @ManyToOne(fetch = FetchType.EAGER)
    private Department department;

    //Getters and setters omitted for brevity
}

The Employee entity has a @ManyToOne association to a Department which is fetched eagerly.

When issuing a direct entity fetch, Hibernate executed the following SQL query:

Example 374. Direct fetching example
Employee employee = entityManager.find( Employee.class, 1L );
select
    e.id as id1_1_0_,
    e.department_id as departme3_1_0_,
    e.username as username2_1_0_,
    d.id as id1_0_1_
from
    Employee e
left outer join
    Department d
        on e.department_id=d.id
where
    e.id = 1

The LEFT JOIN clause is added to the generated SQL query because this association is required to be fetched eagerly.

On the other hand, if you are using an entity query that does not contain a JOIN FETCH directive to the Department association:

Example 375. Entity query fetching example
Employee employee = entityManager.createQuery(
        "select e " +
        "from Employee e " +
        "where e.id = :id", Employee.class)
.setParameter( "id", 1L )
.getSingleResult();
select
    e.id as id1_1_,
    e.department_id as departme3_1_,
    e.username as username2_1_
from
    Employee e
where
    e.id = 1

select
    d.id as id1_0_0_
from
    Department d
where
    d.id = 1

Hibernate uses a secondary select instead. This is because the entity query fetch policy cannot be overridden, so Hibernate requires a secondary select to ensure that the EAGER association is fetched prior to returning the result to the user.

If you forget to JOIN FETCH all EAGER associations, Hibernate is going to issue a secondary select for each and every one of those which, in turn, can lean to N+1 query issues.

For this reason, you should prefer LAZY associations.

11.3. Applying fetch strategies

Let’s consider these topics as it relates to an simple domain model and a few use cases.

Example 376. Sample domain model
@Entity(name = "Department")
public static class Department {

    @Id
    private Long id;

    @OneToMany(mappedBy = "department")
    private List<Employee> employees = new ArrayList<>();

    //Getters and setters omitted for brevity
}

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

    @Id
    private Long id;

    @NaturalId
    private String username;

    @Column(name = "pswd")
    @ColumnTransformer(
        read = "decrypt( 'AES', '00', pswd  )",
        write = "encrypt('AES', '00', ?)"
    )
    private String password;

    private int accessLevel;

    @ManyToOne(fetch = FetchType.LAZY)
    private Department department;

    @ManyToMany(mappedBy = "employees")
    private List<Project> projects = new ArrayList<>();

    //Getters and setters omitted for brevity
}

@Entity(name = "Project")
public class Project {

    @Id
    private Long id;

    @ManyToMany
    private List<Employee> employees = new ArrayList<>();

    //Getters and setters omitted for brevity
}

The Hibernate recommendation is to statically mark all associations lazy and to use dynamic fetching strategies for eagerness. This is unfortunately at odds with the JPA specification which defines that all one-to-one and many-to-one associations should be eagerly fetched by default. Hibernate, as a JPA provider, honors that default.

11.4. No fetching

For the first use case, consider the application login process for an Employee. Let’s assume that login only requires access to the Employee information, not Project nor Department information.

Example 377. No fetching example
Employee employee = entityManager.createQuery(
    "select e " +
    "from Employee e " +
    "where " +
    "    e.username = :username and " +
    "    e.password = :password",
    Employee.class)
.setParameter( "username", username)
.setParameter( "password", password)
.getSingleResult();

In this example, the application gets the Employee data. However, because all associations from Employee are declared as LAZY (JPA defines the default for collections as LAZY) no other data is fetched.

If the login process does not need access to the Employee information specifically, another fetching optimization here would be to limit the width of the query results.

Example 378. No fetching (scalar) example
Integer accessLevel = entityManager.createQuery(
    "select e.accessLevel " +
    "from Employee e " +
    "where " +
    "    e.username = :username and " +
    "    e.password = :password",
    Integer.class)
.setParameter( "username", username)
.setParameter( "password", password)
.getSingleResult();

11.5. Dynamic fetching via queries

For the second use case, consider a screen displaying the Projects for an Employee. Certainly access to the Employee is needed, as is the collection of Projects for that Employee. Information about Departments, other Employees or other Projects is not needed.

Example 379. Dynamic JPQL fetching example
Employee employee = entityManager.createQuery(
    "select e " +
    "from Employee e " +
    "left join fetch e.projects " +
    "where " +
    "    e.username = :username and " +
    "    e.password = :password",
    Employee.class)
.setParameter( "username", username)
.setParameter( "password", password)
.getSingleResult();
Example 380. Dynamic query fetching example
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> query = builder.createQuery( Employee.class );
Root<Employee> root = query.from( Employee.class );
root.fetch( "projects", JoinType.LEFT);
query.select(root).where(
    builder.and(
        builder.equal(root.get("username"), username),
        builder.equal(root.get("password"), password)
    )
);
Employee employee = entityManager.createQuery( query ).getSingleResult();

In this example we have an Employee and their Projects loaded in a single query shown both as an HQL query and a JPA Criteria query. In both cases, this resolves to exactly one database query to get all that information.

11.6. Dynamic fetching via JPA entity graph

JPA 2.1 introduced entity graphs so the application developer has more control over fetch plans.

Example 381. Fetch graph example
@Entity(name = "Employee")
@NamedEntityGraph(name = "employee.projects",
    attributeNodes = @NamedAttributeNode("projects")
)
Employee employee = entityManager.find(
    Employee.class,
    userId,
    Collections.singletonMap(
        "javax.persistence.fetchgraph",
        entityManager.getEntityGraph( "employee.projects" )
    )
);

Although the JPA standard specifies that you can override an EAGER fetching association at runtime using the javax.persistence.fetchgraph hint, currently, Hibernate does not implement this feature, so EAGER associations cannot be fetched lazily. For more info, check out the HHH-8776 Jira issue.

When executing a JPQL query, if an EAGER association is omitted, Hibernate will issue a secondary select for every association needed to be fetched eagerly, which can lead dto N+1 query issues.

For this reason, it’s better to use LAZY associations, and only fetch them eagerly on a per-query basis.

11.6.1. JPA entity subgraphs

An entity graph specifies which attributes to be fetched, but it limited to a single entity only. To fetch associations from a child entity, you need to use the @NamedSubgraph annotation.

If we have a Project parent entity which has an employees child associations, and we’d like to fetch the department for the Employee child association.

Example 382. Fetch graph with a subgraph mapping
@Entity(name = "Project")
@NamedEntityGraph(name = "project.employees",
    attributeNodes = @NamedAttributeNode(
        value = "employees",
        subgraph = "project.employees.department"
    ),
    subgraphs = @NamedSubgraph(
        name = "project.employees.department",
        attributeNodes = @NamedAttributeNode( "department" )
    )
)
public static class Project {

    @Id
    private Long id;

    @ManyToMany
    private List<Employee> employees = new ArrayList<>();

    //Getters and setters omitted for brevity
}

When fetching this entity graph, Hibernate generates the following SQL query:

Example 383. Fetch graph with a subgraph mapping
Project project = doInJPA( this::entityManagerFactory, entityManager -> {
    return entityManager.find(
        Project.class,
        1L,
        Collections.singletonMap(
            "javax.persistence.fetchgraph",
            entityManager.getEntityGraph( "project.employees" )
        )
    );
} );
select
    p.id as id1_2_0_, e.id as id1_1_1_, d.id as id1_0_2_,
    e.accessLevel as accessLe2_1_1_,
    e.department_id as departme5_1_1_,
    decrypt( 'AES', '00', e.pswd  ) as pswd3_1_1_,
    e.username as username4_1_1_,
    p_e.projects_id as projects1_3_0__,
    p_e.employees_id as employee2_3_0__
from
    Project p
inner join
    Project_Employee p_e
        on p.id=p_e.projects_id
inner join
    Employee e
        on p_e.employees_id=e.id
inner join
    Department d
        on e.department_id=d.id
where
    p.id = ?

-- binding parameter [1] as [BIGINT] - [1]

11.7. Dynamic fetching via Hibernate profiles

Suppose we wanted to leverage loading by natural-id to obtain the Employee information in the "projects for and employee" use-case. Loading by natural-id uses the statically defined fetching strategies, but does not expose a means to define load-specific fetching. So we would leverage a fetch profile.

Example 384. Fetch profile example
@Entity(name = "Employee")
@FetchProfile(
    name = "employee.projects",
    fetchOverrides = {
        @FetchProfile.FetchOverride(
            entity = Employee.class,
            association = "projects",
            mode = FetchMode.JOIN
        )
    }
)
session.enableFetchProfile( "employee.projects" );
Employee employee = session.bySimpleNaturalId( Employee.class ).load( username );

Here the Employee is obtained by natural-id lookup and the Employee’s Project data is fetched eagerly. If the Employee data is resolved from cache, the Project data is resolved on its own. However, if the Employee data is not resolved in cache, the Employee and Project data is resolved in one SQL query via join as we saw above.

11.8. Batch fetching

Hibernate offers the @BatchSize annotation, which can be used when fetching uninitialized entity proxies.

Considering the following entity mapping:

Example 385. @BatchSize mapping example
@Entity(name = "Department")
public static class Department {

    @Id
    private Long id;

    @OneToMany(mappedBy = "department")
    //@BatchSize(size = 5)
    private List<Employee> employees = new ArrayList<>();

    //Getters and setters omitted for brevity

}

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

    @Id
    private Long id;

    @NaturalId
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    private Department department;

    //Getters and setters omitted for brevity
}

Considering that we have previously fetched several Department entities, and now we need to initialize the employees entity collection for each particular Department, the @BatchSize annotations allows us to load multiple Employee entities in a single database roundtrip.

Example 386. @BatchSize fetching example
List<Department> departments = entityManager.createQuery(
    "select d " +
    "from Department d " +
    "inner join d.employees e " +
    "where e.name like 'John%'", Department.class)
.getResultList();

for ( Department department : departments ) {
    log.infof(
        "Department %d has {} employees",
        department.getId(),
        department.getEmployees().size()
    );
}
SELECT
    d.id as id1_0_
FROM
    Department d
INNER JOIN
    Employee employees1_
    ON d.id=employees1_.department_id

SELECT
    e.department_id as departme3_1_1_,
    e.id as id1_1_1_,
    e.id as id1_1_0_,
    e.department_id as departme3_1_0_,
    e.name as name2_1_0_
FROM
    Employee e
WHERE
    e.department_id IN (
        0, 2, 3, 4, 5
    )

SELECT
    e.department_id as departme3_1_1_,
    e.id as id1_1_1_,
    e.id as id1_1_0_,
    e.department_id as departme3_1_0_,
    e.name as name2_1_0_
FROM
    Employee e
WHERE
    e.department_id IN (
        6, 7, 8, 9, 1
    )

As you can see in the example above, there are only two SQL statements used to fetch the Employee entities associated to multiple Department entities.

Without @BatchSize, you’d run into a N+1 query issue, so, instead of 2 SQL statements, there would be 10 queries needed for fetching the Employee child entities.

However, although @BatchSize is better than running into an N+1 query issue, most of the time, a DTO projection or a JOIN FETCH is a much better alternative since it allows you to fetch all the required data with a single query.

11.9. The @Fetch annotation mapping

Besides the FetchType.LAZY or FetchType.EAGER JPA annotations, you can also use the Hibernate-specific @Fetch annotation that accepts one of the following FetchMode(s):

SELECT

The association is going to be fetched lazily using a secondary select for each individual entity, collection, or join load. It’s equivalent to JPA FetchType.LAZY fetching strategy.

JOIN

Use an outer join to load the related entities, collections or joins when using direct fetching. It’s equivalent to JPA FetchType.EAGER fetching strategy.

SUBSELECT

Available for collections only. When accessing a non-initialized collection, this fetch mode will trigger loading all elements of all collections of the same role for all owners associated with the persistence context using a single secondary select.

11.10. FetchMode.SELECT

To demonstrate how FetchMode.SELECT works, consider the following entity mapping:

Example 387. FetchMode.SELECT mapping example
@Entity(name = "Department")
public static class Department {

    @Id
    private Long id;

    @OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
    @Fetch(FetchMode.SELECT)
    private List<Employee> employees = new ArrayList<>();

    //Getters and setters omitted for brevity

}

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

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    private String username;

    @ManyToOne(fetch = FetchType.LAZY)
    private Department department;

    //Getters and setters omitted for brevity

}

Considering there are multiple Department entities, each one having multiple Employee entities, when executing the following test case, Hibernate fetches every uninitialized Employee collection using a secondary SELECT statement upon accessing the child collection for the first time:

Example 388. FetchMode.SELECT mapping example
List<Department> departments = entityManager.createQuery(
    "select d from Department d", Department.class )
.getResultList();

log.infof( "Fetched %d Departments", departments.size());

for (Department department : departments ) {
    assertEquals( 3, department.getEmployees().size() );
}
SELECT
    d.id as id1_0_
FROM
    Department d

-- Fetched 2 Departments

SELECT
    e.department_id as departme3_1_0_,
    e.id as id1_1_0_,
    e.id as id1_1_1_,
    e.department_id as departme3_1_1_,
    e.username as username2_1_1_
FROM
    Employee e
WHERE
    e.department_id = 1

SELECT
    e.department_id as departme3_1_0_,
    e.id as id1_1_0_,
    e.id as id1_1_1_,
    e.department_id as departme3_1_1_,
    e.username as username2_1_1_
FROM
    Employee e
WHERE
    e.department_id = 2

The more Department entities are fetched by the first query, the more secondary SELECT statements are executed to initialize the employees collections. Therefore, FetchMode.SELECT can lead to N+1 query issues.

11.11. FetchMode.SUBSELECT

To demonstrate how FetchMode.SUBSELECT works, we are going to modify the FetchMode.SELECT mapping example to use FetchMode.SUBSELECT:

Example 389. FetchMode.SUBSELECT mapping example
@OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
@Fetch(FetchMode.SUBSELECT)
private List<Employee> employees = new ArrayList<>();

Now, we are going to fetch all Department entities that match a given filtering criteria and then navigate their employees collections.

Hibernate is going to avoid the N+1 query issue by generating a single SQL statement to initialize all employees collections for all Department entities that were previously fetched. Instead of using passing all entity identifiers, Hibernate simply reruns the previous query that fetched the Department entities.

Example 390. FetchMode.SUBSELECT mapping example
List<Department> departments = entityManager.createQuery(
    "select d " +
    "from Department d " +
    "where d.name like :token", Department.class )
.setParameter( "token", "Department%" )
.getResultList();

log.infof( "Fetched %d Departments", departments.size());

for (Department department : departments ) {
    assertEquals( 3, department.getEmployees().size() );
}
SELECT
    d.id as id1_0_
FROM
    Department d
where
    d.name like 'Department%'

-- Fetched 2 Departments

SELECT
    e.department_id as departme3_1_1_,
    e.id as id1_1_1_,
    e.id as id1_1_0_,
    e.department_id as departme3_1_0_,
    e.username as username2_1_0_
FROM
    Employee e
WHERE
    e.department_id in (
        SELECT
            fetchmodes0_.id
        FROM
            Department fetchmodes0_
        WHERE
            d.name like 'Department%'
    )

11.12. FetchMode.JOIN

To demonstrate how FetchMode.JOIN works, we are going to modify the FetchMode.SELECT mapping example to use FetchMode.JOIN instead:

Example 391. FetchMode.JOIN mapping example
@OneToMany(mappedBy = "department")
@Fetch(FetchMode.JOIN)
private List<Employee> employees = new ArrayList<>();

Now, we are going to fetch one Department and navigate its employees collections.

The reason why we are not using a JPQL query to fetch multiple Department entities is because the FetchMode.JOIN strategy would be overridden by the query fetching directive.

To fetch multiple relationships with a JPQL query, the JOIN FETCH directive must be used instead.

Therefore, FetchMode.JOIN is useful for when entities are fetched directly, via their identifier or natural-id.

Also, the FetchMode.JOIN acts as a FetchType.EAGER strategy. Even if we mark the association as FetchType.LAZY, the FetchMode.JOIN will load the association eagerly.

Hibernate is going to avoid the secondary query by issuing an OUTER JOIN for the employees collection.

Example 392. FetchMode.JOIN mapping example
Department department = entityManager.find( Department.class, 1L );

log.infof( "Fetched department: %s", department.getId());

assertEquals( 3, department.getEmployees().size() );
SELECT
    d.id as id1_0_0_,
    e.department_id as departme3_1_1_,
    e.id as id1_1_1_,
    e.id as id1_1_2_,
    e.department_id as departme3_1_2_,
    e.username as username2_1_2_
FROM
    Department d
LEFT OUTER JOIN
    Employee e
        on d.id = e.department_id
WHERE
    d.id = 1

-- Fetched department: 1

This time, there was no secondary query because the child collection was loaded along with the parent entity.

11.13. @LazyCollection

The @LazyCollection annotation is used to specify the lazy fetching behavior of a given collection. The possible values are given by the LazyCollectionOption enumeration:

TRUE

Load it when the state is requested.

FALSE

Eagerly load it.

EXTRA

Prefer extra queries over full collection loading.

The TRUE and FALSE values are deprecated since you should be using the JPA FetchType attribute of the @ElementCollection, @OneToMany, or @ManyToMany collection.

The EXTRA value has no equivalent in the JPA specification, and it’s used to avoid loading the entire collection even when the collection is accessed for the first time. Each element is fetched individually using a secondary query.

Example 393. LazyCollectionOption.EXTRA mapping example
@Entity(name = "Department")
public static class Department {

    @Id
    private Long id;

    @OneToMany(mappedBy = "department", cascade = CascadeType.ALL)
    @OrderColumn(name = "order_id")
    @LazyCollection( LazyCollectionOption.EXTRA )
    private List<Employee> employees = new ArrayList<>();

    //Getters and setters omitted for brevity

}

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

    @Id
    private Long id;

    @NaturalId
    private String username;

    @ManyToOne(fetch = FetchType.LAZY)
    private Department department;

    //Getters and setters omitted for brevity

}

LazyCollectionOption.EXTRA only works for ordered collections, either List(s) that are annotated with @OrderColumn or Map(s).

For bags (e.g. regular List(s) of entities that do not preserve any certain ordering), the @LazyCollection(LazyCollectionOption.EXTRA)` behaves like any other FetchType.LAZY collection (the collection is fetched entirely upon being accessed for the first time).

Now, considering we have the following entities:

Example 394. LazyCollectionOption.EXTRA Domain Model example
Department department = new Department();
department.setId( 1L );
entityManager.persist( department );

for (long i = 1; i <= 3; i++ ) {
    Employee employee = new Employee();
    employee.setId( i );
    employee.setUsername( String.format( "user_%d", i ) );
    department.addEmployee(employee);
}

When fetching the employee collection entries by their position in the List, Hibernate generates the following SQL statements:

Example 395. LazyCollectionOption.EXTRA fetching example
Department department = entityManager.find(Department.class, 1L);

int employeeCount = department.getEmployees().size();

for(int i = 0; i < employeeCount; i++ ) {
    log.infof( "Fetched employee: %s", department.getEmployees().get( i ).getUsername());
}
SELECT
    max(order_id) + 1
FROM
    Employee
WHERE
    department_id = ?

-- binding parameter [1] as [BIGINT] - [1]

SELECT
    e.id as id1_1_0_,
    e.department_id as departme3_1_0_,
    e.username as username2_1_0_
FROM
    Employee e
WHERE
    e.department_id=?
    AND e.order_id=?

-- binding parameter [1] as [BIGINT]  - [1]
-- binding parameter [2] as [INTEGER] - [0]

SELECT
    e.id as id1_1_0_,
    e.department_id as departme3_1_0_,
    e.username as username2_1_0_
FROM
    Employee e
WHERE
    e.department_id=?
    AND e.order_id=?

-- binding parameter [1] as [BIGINT]  - [1]
-- binding parameter [2] as [INTEGER] - [1]

SELECT
    e.id as id1_1_0_,
    e.department_id as departme3_1_0_,
    e.username as username2_1_0_
FROM
    Employee e
WHERE
    e.department_id=?
    AND e.order_id=?

-- binding parameter [1] as [BIGINT]  - [1]
-- binding parameter [2] as [INTEGER] - [2]

Therefore, the child entities were fetched one after the other without triggering a full collection initialization.

For this reason, caution is advised because LazyCollectionOption.EXTRA lazy collections are prone to N+1 query issues.

12. Batching

12.1. JDBC batching

JDBC offers support for batching together SQL statements that can be represented as a single PreparedStatement. Implementation wise this generally means that drivers will send the batched operation to the server in one call, which can save on network calls to the database. Hibernate can leverage JDBC batching. The following settings control this behavior.

hibernate.jdbc.batch_size

Controls the maximum number of statements Hibernate will batch together before asking the driver to execute the batch. Zero or a negative number disables this feature.

hibernate.jdbc.batch_versioned_data

Some JDBC drivers return incorrect row counts when a batch is executed. If your JDBC driver falls into this category this setting should be set to false. Otherwise, it is safe to enable this which will allow Hibernate to still batch the DML for versioned entities and still use the returned row counts for optimistic lock checks. Since 5.0, it defaults to true. Previously (versions 3.x and 4.x), it used to be false.

hibernate.jdbc.batch.builder

Names the implementation class used to manage batching capabilities. It is almost never a good idea to switch from Hibernate’s default implementation. But if you wish to, this setting would name the org.hibernate.engine.jdbc.batch.spi.BatchBuilder implementation to use.

hibernate.order_updates

Forces Hibernate to order SQL updates by the entity type and the primary key value of the items being updated. This allows for more batching to be used. It will also result in fewer transaction deadlocks in highly concurrent systems. Comes with a performance hit, so benchmark before and after to see if this actually helps or hurts your application.

hibernate.order_inserts

Forces Hibernate to order inserts to allow for more batching to be used. Comes with a performance hit, so benchmark before and after to see if this actually helps or hurts your application.

Since version 5.2, Hibernate allows overriding the global JDBC batch size given by the hibernate.jdbc.batch_size configuration property for a given Session.

Example 396. Hibernate specific JDBC batch size configuration on a per Session basis
entityManager
    .unwrap( Session.class )
    .setJdbcBatchSize( 10 );

12.2. Session batching

The following example shows an anti-pattern for batch inserts.

Example 397. Naive way to insert 100 000 entities with Hibernate
EntityManager entityManager = null;
EntityTransaction txn = null;
try {
    entityManager = entityManagerFactory().createEntityManager();

    txn = entityManager.getTransaction();
    txn.begin();

    for ( int i = 0; i < 100_000; i++ ) {
        Person Person = new Person( String.format( "Person %d", i ) );
        entityManager.persist( Person );
    }

    txn.commit();
} catch (RuntimeException e) {
    if ( txn != null && txn.isActive()) txn.rollback();
        throw e;
} finally {
    if (entityManager != null) {
        entityManager.close();
    }
}

There are several problems associated with this example:

  1. Hibernate caches all the newly inserted Customer instances in the session-level c1ache, so, when the transaction ends, 100 000 entities are managed by the persistence context. If the maximum memory allocated to the JVM is rather low, this example could fails with an OutOfMemoryException. The Java 1.8 JVM allocated either 1/4 of available RAM or 1Gb, which can easily accommodate 100 000 objects on the heap.

  2. long-running transactions can deplete a connection pool so other transactions don’t get a chance to proceed.

  3. JDBC batching is not enabled by default, so every insert statement requires a database roundtrip. To enable JDBC batching, set the hibernate.jdbc.batch_size property to an integer between 10 and 50.

Hibernate disables insert batching at the JDBC level transparently if you use an identity identifier generator.

12.2.1. Batch inserts

When you make new objects persistent, employ methods flush() and clear() to the session regularly, to control the size of the first-level cache.

Example 398. Flushing and clearing the Session
EntityManager entityManager = null;
EntityTransaction txn = null;
try {
    entityManager = entityManagerFactory().createEntityManager();

    txn = entityManager.getTransaction();
    txn.begin();

    int batchSize = 25;

    for ( int i = 0; i < entityCount; ++i ) {
        Person Person = new Person( String.format( "Person %d", i ) );
        entityManager.persist( Person );

        if ( i > 0 && i % batchSize == 0 ) {
            //flush a batch of inserts and release memory
            entityManager.flush();
            entityManager.clear();
        }
    }

    txn.commit();
} catch (RuntimeException e) {
    if ( txn != null && txn.isActive()) txn.rollback();
        throw e;
} finally {
    if (entityManager != null) {
        entityManager.close();
    }
}

12.2.2. Session scroll

When you retrieve and update data, flush() and clear() the session regularly. In addition, use method scroll() to take advantage of server-side cursors for queries that return many rows of data.

Example 399. Using scroll()
EntityManager entityManager = null;
EntityTransaction txn = null;
ScrollableResults scrollableResults = null;
try {
    entityManager = entityManagerFactory().createEntityManager();

    txn = entityManager.getTransaction();
    txn.begin();

    int batchSize = 25;

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

    scrollableResults = session
        .createQuery( "select p from Person p" )
        .setCacheMode( CacheMode.IGNORE )
        .scroll( ScrollMode.FORWARD_ONLY );

    int count = 0;
    while ( scrollableResults.next() ) {
        Person Person = (Person) scrollableResults.get( 0 );
        processPerson(Person);
        if ( ++count % batchSize == 0 ) {
            //flush a batch of updates and release memory:
            entityManager.flush();
            entityManager.clear();
        }
    }

    txn.commit();
} catch (RuntimeException e) {
    if ( txn != null && txn.isActive()) txn.rollback();
        throw e;
} finally {
    if (scrollableResults != null) {
        scrollableResults.close();
    }
    if (entityManager != null) {
        entityManager.close();
    }
}

If left unclosed by the application, Hibernate will automatically close the underlying resources (e.g. ResultSet and PreparedStatement) used internally by the ScrollableResults when the current transaction is ended (either commit or rollback).

However, it is good practice to close the ScrollableResults explicitly.

12.2.3. StatelessSession

StatelessSession is a command-oriented API provided by Hibernate. Use it to stream data to and from the database in the form of detached objects. A StatelessSession has no persistence context associated with it and does not provide many of the higher-level life cycle semantics.

Some of the things not provided by a StatelessSession include:

  • a first-level cache

  • interaction with any second-level or query cache

  • transactional write-behind or automatic dirty checking

Limitations of StatelessSession:

  • Operations performed using a stateless session never cascade to associated instances.

  • Collections are ignored by a stateless session.

  • Lazy loading of associations is not supported.

  • Operations performed via a stateless session bypass Hibernate’s event model and interceptors.

  • Due to the lack of a first-level cache, Stateless sessions are vulnerable to data aliasing effects.

  • A stateless session is a lower-level abstraction that is much closer to the underlying JDBC.

Example 400. Using a StatelessSession
StatelessSession statelessSession = null;
Transaction txn = null;
ScrollableResults scrollableResults = null;
try {
    SessionFactory sessionFactory = entityManagerFactory().unwrap( SessionFactory.class );
    statelessSession = sessionFactory.openStatelessSession();

    txn = statelessSession.getTransaction();
    txn.begin();

    scrollableResults = statelessSession
        .createQuery( "select p from Person p" )
        .scroll(ScrollMode.FORWARD_ONLY);

    while ( scrollableResults.next() ) {
        Person Person = (Person) scrollableResults.get( 0 );
        processPerson(Person);
        statelessSession.update( Person );
    }

    txn.commit();
} catch (RuntimeException e) {
    if ( txn != null && txn.getStatus() == TransactionStatus.ACTIVE) txn.rollback();
        throw e;
} finally {
    if (scrollableResults != null) {
        scrollableResults.close();
    }
    if (statelessSession != null) {
        statelessSession.close();
    }
}

The Customer instances returned by the query are immediately detached. They are never associated with any persistence context.

The insert(), update(), and delete() operations defined by the StatelessSession interface operate directly on database rows. They cause the corresponding SQL operations to be executed immediately. They have different semantics from the save(), saveOrUpdate(), and delete() operations defined by the Session interface.

12.3. Hibernate Query Language for DML

DML, or Data Manipulation Language, refers to SQL statements such as INSERT, UPDATE, and DELETE. Hibernate provides methods for bulk SQL-style DML statement execution, in the form of Hibernate Query Language (HQL).

12.3.1. HQL/JPQL for UPDATE and DELETE

Both the Hibernate native Query Language and JPQL (Java Persistence Query Language) provide support for bulk UPDATE and DELETE.

Example 401. Psuedo-syntax for UPDATE and DELETE statements using HQL
UPDATE FROM EntityName e WHERE e.name = ?

DELETE FROM EntityName e WHERE e.name = ?

The FROM and WHERE clauses are each optional, but it’s good practice to use them.

The FROM clause can only refer to a single entity, which can be aliased. If the entity name is aliased, any property references must be qualified using that alias. If the entity name is not aliased, then it is illegal for any property references to be qualified.

Joins, either implicit or explicit, are prohibited in a bulk HQL query. You can use sub-queries in the WHERE clause, and the sub-queries themselves can contain joins.

Example 402. Executing a JPQL UPDATE, using the Query.executeUpdate()
int updatedEntities = entityManager.createQuery(
    "update Person p " +
    "set p.name = :newName " +
    "where p.name = :oldName" )
.setParameter( "oldName", oldName )
.setParameter( "newName", newName )
.executeUpdate();
Example 403. Executing an HQL UPDATE, using the Query.executeUpdate()
int updatedEntities = session.createQuery(
    "update Person " +
    "set name = :newName " +
    "where name = :oldName" )
.setParameter( "oldName", oldName )
.setParameter( "newName", newName )
.executeUpdate();

In keeping with the EJB3 specification, HQL UPDATE statements, by default, do not effect the version or the timestamp property values for the affected entities. You can use a versioned update to force Hibernate to reset the version or timestamp property values, by adding the VERSIONED keyword after the UPDATE keyword.

Example 404. Updating the version of timestamp
int updatedEntities = session.createQuery(
    "update versioned Person " +
    "set name = :newName " +
    "where name = :oldName" )
.setParameter( "oldName", oldName )
.setParameter( "newName", newName )
.executeUpdate();

If you use the VERSIONED statement, you cannot use custom version types, which use class org.hibernate.usertype.UserVersionType.

This feature is only available in HQL since it’s not standardized by JPA.

Example 405. A JPQL DELETE statement
int deletedEntities = entityManager.createQuery(
    "delete Person p " +
    "where p.name = :name" )
.setParameter( "name", name )
.executeUpdate();
Example 406. An HQL DELETE statement
int deletedEntities = session.createQuery(
    "delete Person " +
    "where name = :name" )
.setParameter( "name", name )
.executeUpdate();

Method Query.executeUpdate() returns an int value, which indicates the number of entities effected by the operation. This may or may not correlate to the number of rows effected in the database. An JPQL/HQL bulk operation might result in multiple SQL statements being executed, such as for joined-subclass. In the example of joined-subclass, a DELETE against one of the subclasses may actually result in deletes in the tables underlying the join, or further down the inheritance hierarchy.

12.3.2. HQL syntax for INSERT

Example 407. Pseudo-syntax for INSERT statements
INSERT INTO EntityName
	properties_list
SELECT properties_list
FROM ...

Only the INSERT INTO …​ SELECT …​ form is supported. You cannot specify explicit values to insert.

The properties_list is analogous to the column specification in the SQL INSERT statement. For entities involved in mapped inheritance, you can only use properties directly defined on that given class-level in the properties_list. Superclass properties are not allowed and subclass properties are irrelevant. In other words, INSERT statements are inherently non-polymorphic.

The SELECT statement can be any valid HQL select query, but the return types must match the types expected by the INSERT. Hibernate verifies the return types during query compilation, instead of expecting the database to check it. Problems might result from Hibernate types which are equivalent, rather than equal. One such example is a mismatch between a property defined as an org.hibernate.type.DateType and a property defined as an org.hibernate.type.TimestampType, even though the database may not make a distinction, or may be capable of handling the conversion.

If id property is not specified in the properties_list, Hibernate generates a value automatically. Automatic generation is only available if you use ID generators which operate on the database. Otherwise, Hibernate throws an exception during parsing. Available in-database generators are org.hibernate.id.SequenceGenerator and its subclasses, and objects which implement org.hibernate.id.PostInsertIdentifierGenerator. The most notable exception is org.hibernate.id.TableHiLoGenerator, which does not expose a selectable way to get its values.

For properties mapped as either version or timestamp, the insert statement gives you two options. You can either specify the property in the properties_list, in which case its value is taken from the corresponding select expressions, or omit it from the properties_list, in which case the seed value defined by the org.hibernate.type.VersionType is used.

Example 408. HQL INSERT statement
int insertedEntities = session.createQuery(
    "insert into Partner (id, name) " +
    "select p.id, p.name " +
    "from Person p ")
.executeUpdate();

This section is only a brief overview of HQL. For more information, see HQL.

12.3.3. Bulk-id strategies

This article is about the HHH-11262 JIRA issue which now allows the bulk-id strategies to work even when you cannot create temporary tables.

Class diagram

Considering we have the following entities:

Entity class diagram

The Person entity is the base class of this entity inheritance model, and is mapped as follows:

Example 409. Bulk-id base class entity
@Entity(name = "Person")
@Inheritance(strategy = InheritanceType.JOINED)
public static class Person {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private boolean employed;

    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 boolean isEmployed() {
        return employed;
    }

    public void setEmployed(boolean employed) {
        this.employed = employed;
    }
}

Both the Doctor and Engineer entity classes extend the Person base class:

Example 410. Bulk-id subclass entities
@Entity(name = "Doctor")
public static class Doctor extends Person {
}

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

    private boolean fellow;

    public boolean isFellow() {
        return fellow;
    }

    public void setFellow(boolean fellow) {
        this.fellow = fellow;
    }
}
Inheritance tree bulk processing

Now, when you try to execute a bulk entity delete query:

Example 411. Bulk-id delete query example
int updateCount = session.createQuery(
    "delete from Person where employed = :employed" )
.setParameter( "employed", false )
.executeUpdate();
create temporary table
    HT_Person
(
    id int4 not null,
    companyName varchar(255) not null
)

insert
into
    HT_Person
    select
        p.id as id,
        p.companyName as companyName
    from
        Person p
    where
        p.employed = ?

delete
from
    Engineer
where
    (
        id, companyName
    ) IN (
        select
            id,
            companyName
        from
            HT_Person
    )

delete
from
    Doctor
where
    (
        id, companyName
    ) IN (
        select
            id,
            companyName
        from
            HT_Person
    )

delete
from
    Person
where
    (
        id, companyName
    ) IN (
        select
            id,
            companyName
        from
            HT_Person
    )

HT_Person is a temporary table that Hibernate creates to hold all the entity identifiers that are to be updated or deleted by the bulk id operation. The temporary table can be either global or local, depending on the underlying database capabilities.

Non-temporary table bulk-id strategies

As the HHH-11262 issue describes, there are use cases when the application developer cannot use temporary tables because the database user lacks this privilege.

In this case, we defined several options which you can choose depending on your database capabilities:

  • InlineIdsInClauseBulkIdStrategy

  • InlineIdsSubSelectValueListBulkIdStrategy

  • InlineIdsOrClauseBulkIdStrategy

  • CteValuesListBulkIdStrategy

InlineIdsInClauseBulkIdStrategy

To use this strategy, you need to configure the following configuration property:

<property name="hibernate.hql.bulk_id_strategy"
          value="org.hibernate.hql.spi.id.inline.InlineIdsInClauseBulkIdStrategy"
/>

Now, when running the previous test case, Hibernate generates the following SQL statements:

Example 412. InlineIdsInClauseBulkIdStrategy delete entity query example
select
    p.id as id,
    p.companyName as companyName
from
    Person p
where
    p.employed = ?

delete
from
    Engineer
where
        ( id, companyName )
    in (
        ( 1,'Red Hat USA' ),
        ( 3,'Red Hat USA' ),
        ( 1,'Red Hat Europe' ),
        ( 3,'Red Hat Europe' )
    )

delete
from
    Doctor
where
        ( id, companyName )
    in (
        ( 1,'Red Hat USA' ),
        ( 3,'Red Hat USA' ),
        ( 1,'Red Hat Europe' ),
        ( 3,'Red Hat Europe' )
    )

delete
from
    Person
where
        ( id, companyName )
    in (
        ( 1,'Red Hat USA' ),
        ( 3,'Red Hat USA' ),
        ( 1,'Red Hat Europe' ),
        ( 3,'Red Hat Europe' )
    )

So, the entity identifiers are selected first and used for each particular update or delete statement.

The IN clause row value expression has long been supported by Oracle, PostgreSQL, and nowadays by MySQL 5.7. However, SQL Server 2014 does not support this syntax, so you’ll have to use a different strategy.

InlineIdsSubSelectValueListBulkIdStrategy

To use this strategy, you need to configure the following configuration property:

<property name="hibernate.hql.bulk_id_strategy"
          value="org.hibernate.hql.spi.id.inline.InlineIdsSubSelectValueListBulkIdStrategy"
/>

Now, when running the previous test case, Hibernate generates the following SQL statements:

Example 413. InlineIdsSubSelectValueListBulkIdStrategy delete entity query example
select
    p.id as id,
    p.companyName as companyName
from
    Person p
where
    p.employed = ?

delete
from
    Engineer
where
    ( id, companyName ) in (
        select
            id,
            companyName
        from (
        values
            ( 1,'Red Hat USA' ),
            ( 3,'Red Hat USA' ),
            ( 1,'Red Hat Europe' ),
            ( 3,'Red Hat Europe' )
        ) as HT
            (id, companyName)
    )

delete
from
    Doctor
where
    ( id, companyName ) in (
         select
            id,
            companyName
        from (
        values
            ( 1,'Red Hat USA' ),
            ( 3,'Red Hat USA' ),
            ( 1,'Red Hat Europe' ),
            ( 3,'Red Hat Europe' )
        ) as HT
            (id, companyName)
    )

delete
from
    Person
where
    ( id, companyName ) in (
        select
            id,
            companyName
        from (
        values
            ( 1,'Red Hat USA' ),
            ( 3,'Red Hat USA' ),
            ( 1,'Red Hat Europe' ),
            ( 3,'Red Hat Europe' )
        ) as HT
            (id, companyName)
    )

The underlying database must support the VALUES list clause, like PostgreSQL or SQL Server 2008. However, this strategy requires the IN-clause row value expression for composite identifiers so you can use this strategy only with PostgreSQL.

InlineIdsOrClauseBulkIdStrategy

To use this strategy, you need to configure the following configuration property:

<property name="hibernate.hql.bulk_id_strategy"
          value="org.hibernate.hql.spi.id.inline.InlineIdsOrClauseBulkIdStrategy"
/>

Now, when running the previous test case, Hibernate generates the following SQL statements:

Example 414. InlineIdsOrClauseBulkIdStrategy delete entity query example
select
    p.id as id,
    p.companyName as companyName
from
    Person p
where
    p.employed = ?

delete
from
    Engineer
where
    ( id = 1 and companyName = 'Red Hat USA' )
or  ( id = 3 and companyName = 'Red Hat USA' )
or  ( id = 1 and companyName = 'Red Hat Europe' )
or  ( id = 3 and companyName = 'Red Hat Europe' )

delete
from
    Doctor
where
    ( id = 1 and companyName = 'Red Hat USA' )
or  ( id = 3 and companyName = 'Red Hat USA' )
or  ( id = 1 and companyName = 'Red Hat Europe' )
or  ( id = 3 and companyName = 'Red Hat Europe' )

delete
from
    Person
where
    ( id = 1 and companyName = 'Red Hat USA' )
or  ( id = 3 and companyName = 'Red Hat USA' )
or  ( id = 1 and companyName = 'Red Hat Europe' )
or  ( id = 3 and companyName = 'Red Hat Europe' )

This strategy has the advantage of being supported by all the major relational database systems (e.g. Oracle, SQL Server, MySQL, and PostgreSQL).

CteValuesListBulkIdStrategy

To use this strategy, you need to configure the following configuration property:

<property name="hibernate.hql.bulk_id_strategy"
          value="org.hibernate.hql.spi.id.inline.CteValuesListBulkIdStrategy"
/>

Now, when running the previous test case, Hibernate generates the following SQL statements:

Example 415. CteValuesListBulkIdStrategy delete entity query example
select
    p.id as id,
    p.companyName as companyName
from
    Person p
where
    p.employed = ?

with HT_Person (id,companyName ) as (
    select id, companyName
    from (
    values
        (?, ?),
        (?, ?),
        (?, ?),
        (?, ?)
    ) as HT (id, companyName) )
delete
from
    Engineer
where
    ( id, companyName ) in (
        select
            id, companyName
        from
            HT_Person
    )

with HT_Person (id,companyName ) as (
    select id, companyName
    from (
    values
        (?, ?),
        (?, ?),
        (?, ?),
        (?, ?)
    ) as HT (id, companyName) )
delete
from
    Doctor
where
    ( id, companyName ) in (
        select
            id, companyName
        from
            HT_Person
    )


with HT_Person (id,companyName ) as (
    select id, companyName
    from (
    values
        (?, ?),
        (?, ?),
        (?, ?),
        (?, ?)
    ) as HT (id, companyName) )
delete
from
    Person
where
    ( id, companyName ) in (
        select
            id, companyName
        from
            HT_Person
    )

The underlying database must support the CTE (Common Table Expressions) that can be referenced from non-query statements as well, like PostgreSQL since 9.1 or SQL Server since 2005. The underlying database must also support the VALUES list clause, like PostgreSQL or SQL Server 2008.

However, this strategy requires the IN-clause row value expression for composite identifiers, so you can only use this strategy only with PostgreSQL.

If you can use temporary tables, that’s probably the best choice. However, if you are not allowed to create temporary tables, you must pick one of these four strategies that works with your underlying database. Before making your mind, you should benchmark which one works best for your current workload. For instance, CTE are optimization fences in PostgreSQL, so make sure you measure before taking a decision.

If you’re using Oracle or MySQL 5.7, you can choose either InlineIdsOrClauseBulkIdStrategy or InlineIdsInClauseBulkIdStrategy. For older version of MySQL, then you can only use InlineIdsOrClauseBulkIdStrategy.

If you’re using SQL Server, InlineIdsOrClauseBulkIdStrategy is the only option for you.

If you’re using PostgreSQL, then you have the luxury of choosing any of these four strategies.

13. Caching

At runtime, Hibernate handles moving data into and out of the second-level cache in response to the operations performed by the Session, which acts as a transaction-level cache of persistent data. Once an entity becomes managed, that object is added to the internal cache of the current persistence context (EntityManager or Session). The persistence context is also called the first-level cache, and it’s enabled by default.

It is possible to configure a JVM-level (SessionFactory-level) or even a cluster cache on a class-by-class and collection-by-collection basis.

Be aware that caches are not aware of changes made to the persistent store by other applications. They can, however, be configured to regularly expire cached data.

13.1. Configuring second-level caching

Hibernate can integrate with various caching providers for the purpose of caching data outside the context of a particular Session. This section defines the settings which control this behavior.

13.1.1. RegionFactory

org.hibernate.cache.spi.RegionFactory defines the integration between Hibernate and a pluggable caching provider. hibernate.cache.region.factory_class is used to declare the provider to use. Hibernate comes with built-in support for the Java caching standard JCache and also two popular caching libraries: Ehcache and Infinispan. Detailed information is provided later in this chapter.

13.1.2. Caching configuration properties

Besides specific provider configuration, there are a number of configurations options on the Hibernate side of the integration that control various caching behaviors:

hibernate.cache.use_second_level_cache

Enable or disable second level caching overall. Default is true, although the default region factory is NoCachingRegionFactory.

hibernate.cache.use_query_cache

Enable or disable second level caching of query results. Default is false.

hibernate.cache.query_cache_factory

Query result caching is handled by a special contract that deals with staleness-based invalidation of the results. The default implementation does not allow stale results at all. Use this for applications that would like to relax that. Names an implementation of org.hibernate.cache.spi.QueryCacheFactory

hibernate.cache.use_minimal_puts

Optimizes second-level cache operations to minimize writes, at the cost of more frequent reads. Providers typically set this appropriately.

hibernate.cache.region_prefix

Defines a name to be used as a prefix to all second-level cache region names.

hibernate.cache.default_cache_concurrency_strategy

In Hibernate second-level caching, all regions can be configured differently including the concurrency strategy to use when accessing that particular region. This setting allows to define a default strategy to be used. This setting is very rarely required as the pluggable providers do specify the default strategy to use. Valid values include:

  • read-only,

  • read-write,

  • nonstrict-read-write,

  • transactional

hibernate.cache.use_structured_entries

If true, forces Hibernate to store data in the second-level cache in a more human-friendly format. Can be useful if you’d like to be able to "browse" the data directly in your cache, but does have a performance impact.

hibernate.cache.auto_evict_collection_cache

Enables or disables the automatic eviction of a bidirectional association’s collection cache entry when the association is changed just from the owning side. This is disabled by default, as it has a performance impact to track this state. However if your application does not manage both sides of bidirectional association where the collection side is cached, the alternative is to have stale data in that collection cache.

hibernate.cache.use_reference_entries

Enable direct storage of entity references into the second level cache for read-only or immutable entities.

hibernate.cache.keys_factory

When storing entries into second-level cache as key-value pair, the identifiers can be wrapped into tuples <entity type, tenant, identifier> to guarantee uniqueness in case that second-level cache stores all entities in single space. These tuples are then used as keys in the cache. When the second-level cache implementation (incl. its configuration) guarantees that different entity types are stored separately and multi-tenancy is not used, you can omit this wrapping to achieve better performance. Currently, this property is only supported when Infinispan is configured as the second-level cache implementation. Valid values are:

  • default (wraps identitifers in the tuple)

  • simple (uses identifiers as keys without any wrapping)

  • fully qualified class name that implements org.hibernate.cache.spi.CacheKeysFactory

13.2. Configuring second-level cache mappings

The cache mappings can be configured via JPA annotations or XML descriptors or using the Hibernate-specific mapping files.

By default, entities are not part of the second level cache and we recommend you to stick to this setting. However, you can override this by setting the shared-cache-mode element in your persistence.xml file or by using the javax.persistence.sharedCache.mode property in your configuration file. The following values are possible:

ENABLE_SELECTIVE (Default and recommended value)

Entities are not cached unless explicitly marked as cacheable (with the @Cacheable annotation).

DISABLE_SELECTIVE

Entities are cached unless explicitly marked as non-cacheable.

ALL

Entities are always cached even if marked as non-cacheable.

NONE

No entity is cached even if marked as cacheable. This option can make sense to disable second-level cache altogether.

The cache concurrency strategy used by default can be set globally via the hibernate.cache.default_cache_concurrency_strategy configuration property. The values for this property are:

read-only

If your application needs to read, but not modify, instances of a persistent class, a read-only cache is the best choice. Application can still delete entities and these changes should be reflected in second-level cache so that the cache does not provide stale entities. Implementations may use performance optimizations based on the immutability of entities.

read-write

If the application needs to update data, a read-write cache might be appropriate. This strategy provides consistent access to single entity, but not a serializable transaction isolation level; e.g. when TX1 reads looks up an entity and does not find it, TX2 inserts the entity into cache and TX1 looks it up again, the new entity can be read in TX1.

nonstrict-read-write

Similar to read-write strategy but there might be occasional stale reads upon concurrent access to an entity. The choice of this strategy might be appropriate if the application rarely updates the same data simultaneously and strict transaction isolation is not required. Implementations may use performance optimizations that make use of the relaxed consistency guarantee.

transactional

Provides serializable transaction isolation level.

Rather than using a global cache concurrency strategy, it is recommended to define this setting on a per entity basis. Use the @org.hibernate.annotations.Cache annotation for that.

The @Cache annotation define three attributes:

usage

Defines the CacheConcurrencyStrategy

region

Defines a cache region where entries will be stored

include

If lazy properties should be included in the second level cache. The default value is all so lazy properties are cacheable. The other possible value is non-lazy so lazy properties are not cacheable.

13.3. Entity inheritance and second-level cache mapping

When using inheritance, the JPA @Cacheable and the Hibernate-specific @Cache annotations should be declared at the root-entity level only. That being said, it is not possible to customize the base class @Cacheable or @Cache definition in subclasses.

Although the JPA 2.1 specification says that the @Cacheable annotation could be overwritten by a subclass:

The value of the Cacheable annotation is inherited by subclasses; it can be overridden by specifying Cacheable on a subclass.

— Section 11.1.7 of the JPA 2.1 Specification

Hibernate requires that a given entity hierarchy share the same caching semantics.

The reasons why Hibernate requires that all entities belonging to an inheritance tree share the same caching definition can be summed as follows:

  • from a performance perspective, adding an additional check on a per entity type level would slow the bootstrap process.

  • providing different caching semantics for subclasses would violate the Liskov substitution principle.

    Assuming we have a base class, Payment and a subclass CreditCardPayment. If the Payment is not cacheable and the CreditCardPayment is cached, what should happen when executing the following code snippet:

    Payment payment = entityManager.find(Payment.class, creditCardPaymentId);
    CreditCardPayment creditCardPayment = (CreditCardPayment) CreditCardPayment;

    In this particular case, the second level cache key is formed of the entity class name and the identifier:

    keyToLoad = {org.hibernate.engine.spi.EntityKey@4712}
     identifier = {java.lang.Long@4716} "2"
     persister = {org.hibernate.persister.entity.SingleTableEntityPersister@4629}
      "SingleTableEntityPersister(org.hibernate.userguide.model.Payment)"

    Should Hibernate load the CreditCardPayment from the cache as indicated by the actual entity type, or it should not use the cache since the Payment is not supposed to be cached?

Because of all these intricacies, Hibernate only considers the base class @Cacheable and @Cache definition.

13.4. Entity cache

Example 416. Entity cache mapping
@Entity(name = "Phone")
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public static class Phone {

    @Id
    @GeneratedValue
    private Long id;

    private String mobile;

    @ManyToOne
    private Person person;

    @Version
    private int version;

    public Phone() {}

    public Phone(String mobile) {
        this.mobile = mobile;
    }

    public Long getId() {
        return id;
    }

    public String getMobile() {
        return mobile;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }
}

Hibernate stores cached entities in a dehydrated form, which is similar to the database representation. Aside from the foreign key column values of the @ManyToOne or @OneToOne child-side associations, entity relationships are not stored in the cache,

Once an entity is stored in the second-level cache, you can avoid a database hit and load the entity from the cache alone:

Example 417. Loading entity using JPA
Person person = entityManager.find( Person.class, 1L );
Example 418. Loading entity using Hibernate native API
Person person = session.get( Person.class, 1L );

The Hibernate second-level cache can also load entities by their natural id:

Example 419. Hibernate natural id entity mapping
@Entity(name = "Person")
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public static class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    @NaturalId
    @Column(name = "code", unique = true)
    private String code;

    public Person() {}

    public Person(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

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

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }
}
Example 420. Loading entity using Hibernate native natural id API
Person person = session
    .byNaturalId( Person.class )
    .using( "code", "unique-code")
    .load();

13.5. Collection cache

Hibernate can also cache collections, and the @Cache annotation must be on added to the collection property.

If the collection is made of value types (basic or embeddables mapped with @ElementCollection), the collection is stored as such. If the collection contains other entities (@OneToMany or @ManyToMany), the collection cache entry will store the entity identifiers only.

Example 421. Collection cache mapping
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private List<Phone> phones = new ArrayList<>(  );

Collections are read-through, meaning they are cached upon being accessed for the first time:

Example 422. Collection cache usage
Person person = entityManager.find( Person.class, 1L );
person.getPhones().size();

Subsequent collection retrievals will use the cache instead of going to the database.

The collection cache is not write-through so any modification will trigger a collection cache entry invalidation. On a subsequent access, the collection will be loaded from the database and re-cached.

13.6. Query cache

Aside from caching entities and collections, Hibernate offers a query cache too. This is useful for frequently executed queries with fixed parameter values.

Caching of query results introduces some overhead in terms of your applications normal transactional processing. For example, if you cache results of a query against Person, Hibernate will need to keep track of when those results should be invalidated because changes have been committed against any Person entity.

That, coupled with the fact that most applications simply gain no benefit from caching query results, leads Hibernate to disable caching of query results by default.

To use query caching, you will first need to enable it with the following configuration property:

Example 423. Enabling query cache
<property
    name="hibernate.cache.use_query_cache"
    value="true" />

As mentioned above, most queries do not benefit from caching or their results. So by default, individual queries are not cached even after enabling query caching. Each particular query that needs to be cached must be manually set as cacheable. This way, the query looks for existing cache results or adds the query results to the cache when being executed.

Example 424. Caching query using JPA
List<Person> persons = entityManager.createQuery(
    "select p " +
    "from Person p " +
    "where p.name = :name", Person.class)
.setParameter( "name", "John Doe")
.setHint( "org.hibernate.cacheable", "true")
.getResultList();
Example 425. Caching query using Hibernate native API
List<Person> persons = session.createQuery(
    "select p " +
    "from Person p " +
    "where p.name = :name")
.setParameter( "name", "John Doe")
.setCacheable(true)
.list();

The query cache does not cache the state of the actual entities in the cache; it caches only identifier values and results of value type.

Just as with collection caching, the query cache should always be used in conjunction with the second-level cache for those entities expected to be cached as part of a query result cache.

13.6.1. Query cache regions

This setting creates two new cache regions:

org.hibernate.cache.internal.StandardQueryCache

Holding the cached query results

org.hibernate.cache.spi.UpdateTimestampsCache

Holding timestamps of the most recent updates to queryable tables. These are used to validate the results as they are served from the query cache.

If you configure your underlying cache implementation to use expiration, it’s very important that the timeout of the underlying cache region for the UpdateTimestampsCache is set to a higher value than the timeouts of any of the query caches.

In fact, we recommend that the UpdateTimestampsCache region is not configured for expiration (time-based) or eviction (size/memory-based) at all. Note that an LRU (Least Recently Used) cache eviction policy is never appropriate for this particular cache region.

If you require fine-grained control over query cache expiration policies, you can specify a named cache region for a particular query.

Example 426. Caching query in custom region using JPA
List<Person> persons = entityManager.createQuery(
        "select p " +
        "from Person p " +
        "where p.id > :id", Person.class)
        .setParameter( "id", 0L)
        .setHint( QueryHints.HINT_CACHEABLE, "true")
        .setHint( QueryHints.HINT_CACHE_REGION, "query.cache.person" )
        .getResultList();
Example 427. Caching query in custom region using Hibernate native API
List<Person> persons = session.createQuery(
    "select p " +
    "from Person p " +
    "where p.id > :id")
.setParameter( "id", 0L)
.setCacheable(true)
.setCacheRegion( "query.cache.person" )
.list();

If you want to force the query cache to refresh one of its regions (disregarding any cached results it finds there), you can use custom cache modes.

Example 428. Using custom query cache mode with JPA
List<Person> persons = entityManager.createQuery(
    "select p " +
    "from Person p " +
    "where p.id > :id", Person.class)
.setParameter( "id", 0L)
.setHint( QueryHints.HINT_CACHEABLE, "true")
.setHint( QueryHints.HINT_CACHE_REGION, "query.cache.person" )
.setHint( "javax.persistence.cache.storeMode", CacheStoreMode.REFRESH )
.getResultList();
Example 429. Using custom query cache mode with Hibernate native API
List<Person> persons = session.createQuery(
    "select p " +
    "from Person p " +
    "where p.id > :id")
.setParameter( "id", 0L)
.setCacheable(true)
.setCacheRegion( "query.cache.person" )
.setCacheMode( CacheMode.REFRESH )
.list();

When using CacheStoreMode.REFRESH or CacheMode.REFRESH in conjunction with the region you have defined for the given query, Hibernate will selectively force the results cached in that particular region to be refreshed.

This is particularly useful in cases where underlying data may have been updated via a separate process and is a far more efficient alternative to bulk eviction of the region via SessionFactory eviction which looks as follows:

session.getSessionFactory().getCache().evictQueryRegion( "query.cache.person" );

13.7. Managing the cached data

Traditionally, Hibernate defined the CacheMode enumeration to describe the ways of interactions with the cached data. JPA split cache modes by storage (CacheStoreMode) and retrieval (CacheRetrieveMode).

The relationship between Hibernate and JPA cache modes can be seen in the following table:

Table 5. Cache modes relationships
Hibernate JPA Description

CacheMode.NORMAL

CacheStoreMode.USE and CacheRetrieveMode.USE

Default. Reads/writes data from/into cache

CacheMode.REFRESH

CacheStoreMode.REFRESH and CacheRetrieveMode.BYPASS

Doesn’t read from cache, but writes to the cache upon loading from the database

CacheMode.PUT

CacheStoreMode.USE and CacheRetrieveMode.BYPASS

Doesn’t read from cache, but writes to the cache as it reads from the database

CacheMode.GET

CacheStoreMode.BYPASS and CacheRetrieveMode.USE

Read from the cache, but doesn’t write to cache

CacheMode.IGNORE

CacheStoreMode.BYPASS and CacheRetrieveMode.BYPASS

Doesn’t read/write data from/into cache

Setting the cache mode can be done either when loading entities directly or when executing a query.

Example 430. Using custom cache modes with JPA
Map<String, Object> hints = new HashMap<>(  );
hints.put( "javax.persistence.cache.retrieveMode " , CacheRetrieveMode.USE );
hints.put( "javax.persistence.cache.storeMode" , CacheStoreMode.REFRESH );
Person person = entityManager.find( Person.class, 1L , hints);
Example 431. Using custom cache modes with Hibernate native API
session.setCacheMode( CacheMode.REFRESH );
Person person = session.get( Person.class, 1L );

The custom cache modes can be set for queries as well:

Example 432. Using custom cache modes for queries with JPA
List<Person> persons = entityManager.createQuery(
    "select p from Person p", Person.class)
.setHint( QueryHints.HINT_CACHEABLE, "true")
.setHint( "javax.persistence.cache.retrieveMode " , CacheRetrieveMode.USE )
.setHint( "javax.persistence.cache.storeMode" , CacheStoreMode.REFRESH )
.getResultList();
Example 433. Using custom cache modes for queries with Hibernate native API
List<Person> persons = session.createQuery(
    "select p from Person p" )
.setCacheable( true )
.setCacheMode( CacheMode.REFRESH )
.list();

13.7.1. Evicting cache entries

Because the second level cache is bound to the EntityManagerFactory or the SessionFactory, cache eviction must be done through these two interfaces.

JPA only supports entity eviction through the javax.persistence.Cache interface:

Example 434. Evicting entities with JPA
entityManager.getEntityManagerFactory().getCache().evict( Person.class );

Hibernate is much more flexible in this regard as it offers fine-grained control over what needs to be evicted. The org.hibernate.Cache interface defines various evicting strategies:

  • entities (by their class or region)

  • entities stored using the natural-id (by their class or region)

  • collections (by the region, and it might take the collection owner identifier as well)

  • queries (by region)

Example 435. Evicting entities with Hibernate native API
session.getSessionFactory().getCache().evictQueryRegion( "query.cache.person" );

13.8. Caching statistics

If you enable the hibernate.generate_statistics configuration property, Hibernate will expose a number of metrics via SessionFactory.getStatistics(). Hibernate can even be configured to expose these statistics via JMX.

This way, you can get access to the Statistics class which comprises all sort of second-level cache metrics.

Example 436. Caching statistics
Statistics statistics = session.getSessionFactory().getStatistics();
SecondLevelCacheStatistics secondLevelCacheStatistics =
        statistics.getSecondLevelCacheStatistics( "query.cache.person" );
long hitCount = secondLevelCacheStatistics.getHitCount();
long missCount = secondLevelCacheStatistics.getMissCount();
double hitRatio = (double) hitCount / ( hitCount + missCount );

13.9. JCache

Use of the build-in integration for JCache requires that the hibernate-jcache module jar (and all of its dependencies) are on the classpath. In addition a JCache implementation needs to be added as well. A list of compatible implementations can be found on the JCP website. An alternative source of compatible implementations can be found through the JSR-107 test zoo.

13.9.1. RegionFactory

The hibernate-jcache module defines the following region factory: JCacheRegionFactory.

To use the JCacheRegionFactory, you need to specify the following configuration property:

Example 437. JCacheRegionFactory configuration
<property
    name="hibernate.cache.region.factory_class"
    value="org.hibernate.cache.jcache.JCacheRegionFactory"/>

The JCacheRegionFactory configures a javax.cache.CacheManager.

13.9.2. JCache CacheManager

JCache mandates that CacheManagers sharing the same URI and class loader be unique in JVM.

If you do not specify additional properties, the JCacheRegionFactory will load the default JCache provider and create the default CacheManager. Also, Caches will be created using the default javax.cache.configuration.MutableConfiguration.

In order to control which provider to use and specify configuration for the CacheManager and Caches you can use the following two properties:

Example 438. JCache configuration
<property
    name="hibernate.javax.cache.provider"
    value="org.ehcache.jsr107.EhcacheCachingProvider"/>
<property
    name="hibernate.javax.cache.uri"
    value="file:/path/to/ehcache.xml"/>

Only by specifying the second property hibernate.javax.cache.uri will you be able to have a CacheManager per SessionFactory.

13.10. Ehcache

This integration covers Ehcache 2.x, in order to use Ehcache 3.x as second level cache, refer to the JCache integration.

Use of the build-in integration for Ehcache requires that the hibernate-ehcache module jar (and all of its dependencies) are on the classpath.

13.10.1. RegionFactory

The hibernate-ehcache module defines two specific region factories: EhCacheRegionFactory and SingletonEhCacheRegionFactory.

EhCacheRegionFactory

To use the EhCacheRegionFactory, you need to specify the following configuration property:

Example 439. EhCacheRegionFactory configuration
<property
    name="hibernate.cache.region.factory_class"
    value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>

The EhCacheRegionFactory configures a net.sf.ehcache.CacheManager for each SessionFactory, so the CacheManager is not shared among multiple SessionFactory instances in the same JVM.

SingletonEhCacheRegionFactory

To use the SingletonEhCacheRegionFactory, you need to specify the following configuration property:

Example 440. SingletonEhCacheRegionFactory configuration
<property
    name="hibernate.cache.region.factory_class"
    value="org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory"/>

The SingletonEhCacheRegionFactory configures a singleton net.sf.ehcache.CacheManager (see CacheManager#create()), shared among multiple SessionFactory instances in the same JVM.

Ehcache documentation recommends using multiple non-singleton CacheManager(s) when there are multiple Hibernate SessionFactory instances running in the same JVM.

13.11. Infinispan

Use of the build-in integration for Infinispan requires that the hibernate-infinispan module jar (and all of its dependencies) are on the classpath.

How to configure Infinispan to be the second level cache provider varies slightly depending on the deployment scenario:

13.11.1. Single Node Local

In standalone library mode, a JPA/Hibernate application runs inside a Java SE application or inside containers that don’t offer Infinispan integration.

Enabling Infinispan second level cache provider inside a JPA/Hibernate application that runs in single node is very straightforward. First, make sure the Hibernate Infinispan cache provider (and its dependencies) are available in the classpath, then modify the persistence.xml to include these properties:

<!-- Use Infinispan second level cache provider -->
<property name="hibernate.cache.region.factory_class"
          value="org.hibernate.cache.infinispan.InfinispanRegionFactory"/>

<!-- Optional: Force using local configuration when only using a single node.
     Otherwise a clustered configuration is loaded. -->
<property name="hibernate.cache.infinispan.cfg"
          value="org/hibernate/cache/infinispan/builder/infinispan-configs-local.xml"/>

Plugging in Infinispan as second-level cache provider requires at the bare minimum that hibernate.cache.region.factory_class is set to an Infinispan region factory implementation. Normally, this is org.hibernate.cache.infinispan.InfinispanRegionFactory but other region factories are possible in alternative scenarios (see Alternative Region Factory section for more info).

By default, the Infinispan second-level cache provider uses an Infinispan configuration that’s designed for clustered environments. However, since this section is focused on running Infinispan second-level cache provider in a single node, an Infinispan configuration designed for local environments is recommended. To enable that configuration, set hibernate.cache.infinispan.cfg to org/hibernate/cache/infinispan/builder/infinispan-configs-local.xml value.

The next section focuses on analysing how the default local configuration works. Changing Infinispan configuration options can be done following the instructions in Configuration Properties section.

Default Local Configuration

Infinispan second-level cache provider comes with a configuration designed for local, single node, environments. These are the characteristics of such configuration:

  • Entities, collections, queries and timestamps are stored in non-transactional local caches.

  • Entities and collections query caches are configured with the following eviction settings. You can change these settings on a per entity or collection basis or per individual entity or collection type. More information in the Advanced Configuration section below.

    • Eviction wake up interval is 5 seconds.

    • Max number of entries are 10,000

    • Max idle time before expiration is 100 seconds

    • Default eviction algorithm is LRU, least recently used.

  • No eviction/expiration is configured for timestamp caches, nor it’s allowed.

Local Cache Strategies

Before version 5.0, Infinispan only supported transactional and read-only strategies.

Starting with version 5.0, all cache strategies are supported: transactional, read-write, nonstrict-read-write and read-only.

13.11.2. Multi Node Cluster

When running a JPA/Hibernate in a multi-node environment and enabling Infinispan second-level cache, it is necessary to cluster the second-level cache so that cache consistency can be guaranteed. Clustering the Infinispan second-level cache provider is as simple as adding the following properties:

<!-- Use Infinispan second level cache provider -->
<property name="hibernate.cache.region.factory_class"
          value="org.hibernate.cache.infinispan.InfinispanRegionFactory"/>

As with the standalone local mode, at the bare minimum the region factory has to be configured to point to an Infinispan region factory implementation.

However, the default Infinispan configuration used by the second-level cache provider is already configured to work in a cluster environment, so no need to add any extra properties.

The next section focuses on analysing how the default cluster configuration works. Changing Infinispan configuration options can be done following the instructions in Configuration Properties section.

Default Cluster Configuration

Infinispan second-level cache provider default configuration is designed for multi-node clustered environments. The aim of this section is to explain the default settings for each of the different global data type caches (entity, collection, query and timestamps), why these were chosen and what are the available alternatives. These are the characteristics of such configuration:

  • For all entities and collections, whenever a new entity or collection is read from database and needs to be cached, it’s only cached locally in order to reduce intra-cluster traffic. This option can be changed so that entities/collections are cached cluster wide, by switching the entity/collection cache to be replicated or distributed. How to change this option is explained in the Configuration Properties section.

  • All entities and collections are configured to use a synchronous invalidation as clustering mode. This means that when an entity is updated, the updated cache will send a message to the other members of the cluster telling them that the entity has been modified. Upon receipt of this message, the other nodes will remove this data from their local cache, if it was stored there. This option can be changed so that both local and remote nodes contain the updates by configuring entities or collections to use a replicated or distributed cache. With replicated caches all nodes would contain the update, whereas with distributed caches only a subset of the nodes. How to change this option is explained in the Configuration Properties section.

  • All entities and collections have initial state transfer disabled since there’s no need for it.

  • Entities and collections are configured with the following eviction settings. You can change these settings on a per entity or collection basis or per individual entity or collection type. More information in the Configuration Properties section below.

    • Eviction wake up interval is 5 seconds.

    • Max number of entries are 10,000

    • Max idle time before expiration is 100 seconds

    • Default eviction algorithm is LRU, least recently used.

  • Assuming that query caching has been enabled for the persistence unit (see query cache section), the query cache is configured so that queries are only cached locally. Alternatively, you can configure query caching to use replication by selecting the replicated-query as query cache name. However, replication for query cache only makes sense if, and only if, all of this conditions are true:

    • Performing the query is quite expensive.

    • The same query is very likely to be repeatedly executed on different cluster nodes.

    • The query is unlikely to be invalidated out of the cache (Note: Hibernate must aggressively invalidate query results from the cache any time any instance of one of the entity types targeted by the query. All such query results are invalidated, even if the change made to the specific entity instance would not have affected the query result)

  • query cache uses the same eviction/expiration settings as for entities/collections.

  • query cache has initial state transfer disabled . It is not recommended that this is enabled.

  • The timestamps cache is configured with asynchronous replication as clustering mode. Local or invalidated cluster modes are not allowed, since all cluster nodes must store all timestamps. As a result, no eviction/expiration is allowed for timestamp caches either.

Asynchronous replication was selected as default for timestamps cache for performance reasons. A side effect of this choice is that when an entity/collection is updated, for a very brief period of time stale queries might be returned. It’s important to note that due to how Infinispan deals with asynchronous replication, stale queries might be found even query is done right after an entity/collection update on same node. The reason why asynchronous replication works this way is because there’s a single node that’s owner for a given key, and that enables changes to be applied in the same order in all nodes. Without it, it could happen that an older value could replace a newer value in certain nodes.

Hibernate must aggressively invalidate query results from the cache any time any instance of one of the entity types is modified. All cached query results referencing given entity type are invalidated, even if the change made to the specific entity instance would not have affected the query result. The timestamps cache plays here an important role - it contains last modification timestamp for each entity type. After a cached query results is loaded, its timestamp is compared to all timestamps of the entity types that are referenced in the query and if any of these is higher, the cached query result is discarded and the query is executed against DB.

Cluster Cache Strategies

Before version 5.0, Infinispan only supported transactional and read-only strategies on top of transactional invalidation caches.

Since version 5.0, Infinispan currently supports all cache concurrency modes in cluster mode, although not all combinations of configurations are compatible:

  • non-transactional invalidation caches are supported as well with read-write strategy. The actual setting of cache concurrency mode (read-write vs. transactional) is not honored, the appropriate strategy is selected based on the cache configuration (non-transactional vs. transactional).

  • read-write mode is supported on non-transactional distributed/replicated caches, however, eviction should not be used in this configuration. Use of eviction can lead to consistency issues. Expiration (with reasonably long max-idle times) can be used.

  • nonstrict-read-write mode is supported on non-transactional distributed/replicated caches, but the eviction should be turned off as well. In addition to that, the entities must use versioning. This mode mildly relaxes the consistency - between DB commit and end of transaction commit a stale read (see example) may occur in another transaction. However this strategy uses less RPCs and can be more performant than the other ones.

  • read-only mode is supported on both transactional and non-transactional invalidation caches and non-transactional distributed/replicated caches, but use of this mode currently does not bring any performance gains.

The available combinations are summarized in table below:

Table 6. Cache concurrency strategy/cache mode compatibility table
Concurrency strategy Cache transactions Cache mode Eviction

transactional

transactional

invalidation

yes

read-write

non-transactional

invalidation

yes

read-write

non-transactional

distributed/replicated

no

nonstrict-read-write

non-transactional

distributed/replicated

no

Changing caches to behave different to the default behaviour explained in previous section is explained in Configuration Properties section.

Example 441. Stale read with nonstrict-read-write strategy
A=0 (non-cached), B=0 (cached in 2LC)
TX1: write A = 1, write B = 1
TX1: start commit
TX1: commit A, B in DB
TX2: read A = 1 (from DB), read B = 0 (from 2LC) // breaks transactional atomicity
TX1: update A, B in 2LC
TX1: end commit
Tx3: read A = 1, B = 1 // reads after TX1 commit completes are consistent again

13.11.3. Alternative RegionFactory

In standalone environments or managed environments with no Infinispan integration, org.hibernate.cache.infinispan.InfinispanRegionFactory should be the choice for region factory implementation. However, it might be sometimes desirable for the Infinispan cache manager to be shared between different JPA/Hibernate applications, for example to share intra-cluster communications channels. In this case, the Infinispan cache manager could be bound into JNDI and the JPA/Hibernate applications could use an alternative region factory implementation:

Example 442. JndiInfinispanRegionFactory configuration
<property
    name="hibernate.cache.region.factory_class"
    value="org.hibernate.cache.infinispan.JndiInfinispanRegionFactory" />

<property
    name="hibernate.cache.infinispan.cachemanager"
    value="java:CacheManager" />

13.11.4. Inside Wildfly

In WildFly, Infinispan is the default second level cache provider for JPA/Hibernate. When using JPA in WildFly, region factory is automatically set upon configuring hibernate.cache.use_second_level_cache=true (by default second-level cache is not used).

You can find details about its configuration in the JPA reference guide, in particular, in the second level cache section.

The default second-level cache configurations used by Wildfly match the configurations explained above both for local and clustered environments. So, an Infinispan based second-level cache should behave exactly the same standalone and within containers that provide Infinispan second-level cache as default for JPA/Hibernate.

Remember that if deploying to Wildfly or Application Server, the way some Infinispan second level cache provider configuration is defined changes slightly because the properties must include deployment and persistence information. Check the Configuration section for more details.

13.11.5. Configuration properties

As explained above, Infinispan second-level cache provider comes with default configuration in infinispan-config.xml that is suited for clustered use. If there’s only single JVM accessing the DB, you can use more performant infinispan-config-local.xml by setting the hibernate.cache.infinispan.cfg property. If you require further tuning of the cache, you can provide your own configuration. Caches that are not specified in the provided configuration will default to infinispan-config.xml (if the provided configuration uses clustering) or infinispan-config-local.xml.

It is not possible to specify the configuration this way in WildFly. Cache configuration changes in Wildfly should be done either modifying the cache configurations inside the application server configuration, or creating new caches with the desired tweaks and plugging them accordingly. See examples below on how entity/collection specific configurations can be applied.

Example 443. Use custom Infinispan configuration
<property
    name="hibernate.cache.infinispan.cfg"
    value="my-infinispan-configuration.xml" />

If the cache is configured as transactional, InfinispanRegionFactory automatically sets transaction manager so that the TM used by Infinispan is the same as TM used by Hibernate.

Cache configuration can differ for each type of data stored in the cache. In order to override the cache configuration template, use property hibernate.cache.infinispan.data-type.cfg where data-type can be one of:

entity

Entities indexed by @Id or @EmbeddedId attribute.

immutable-entity

Entities tagged with @Immutable annotation or set as mutable=false in mapping file.

naturalid

Entities indexed by their @NaturalId attribute.

collection

All collections.

timestamps

Mapping entity typelast modification timestamp. Used for query caching.

query

Mapping queryquery result.

pending-puts

Auxiliary caches for regions using invalidation mode caches.

For specifying cache template for specific region, use region name instead of the data-type:

Example 444. Use custom cache template
<property
    name="hibernate.cache.infinispan.entities.cfg"
    value="custom-entities" />
<property
    name="hibernate.cache.infinispan.query.cfg"
    value="custom-query-cache" />
<property
    name="hibernate.cache.infinispan.com.example.MyEntity.cfg"
    value="my-entities" />
<property
    name="hibernate.cache.infinispan.com.example.MyEntity.someCollection.cfg"
    value="my-entities-some-collection" />
Use custom cache template in Wildfly

When applying entity/collection level changes inside JPA applications deployed in Wildfly, it is necessary to specify deployment name and persistence unit name:

<property
    name="hibernate.cache.infinispan._war_or_ear_name_._unit_name_.com.example.MyEntity.cfg"
    value="my-entities" />
<property
    name="hibernate.cache.infinispan._war_or_ear_name_._unit_name_.com.example.MyEntity.someCollection.cfg"
    value="my-entities-some-collection" />

Cache configurations are used only as a template for the cache created for given region (usually each entity hierarchy or collection has its own region). It is not possible to use the same cache for different regions.

Some options in the cache configuration can also be overridden directly through properties. These are:

hibernate.cache.infinispan.something.eviction.strategy

Available options are NONE, LRU and LIRS.

hibernate.cache.infinispan.something.eviction.max_entries

Maximum number of entries in the cache.

hibernate.cache.infinispan.something.expiration.lifespan

Lifespan of entry from insert into cache (in milliseconds)

hibernate.cache.infinispan.something.expiration.max_idle

Lifespan of entry from last read/modification (in milliseconds)

hibernate.cache.infinispan.something.expiration.wake_up_interval

Period of thread checking expired entries.

hibernate.cache.infinispan.statistics

Globally enables/disable Infinispan statistics collection, and their exposure via JMX.

Example:

<property name="hibernate.cache.infinispan.entity.eviction.strategy"
   value= "LRU"/>
<property name="hibernate.cache.infinispan.entity.eviction.wake_up_interval"
   value= "2000"/>
<property name="hibernate.cache.infinispan.entity.eviction.max_entries"
   value= "5000"/>
<property name="hibernate.cache.infinispan.entity.expiration.lifespan"
   value= "60000"/>
<property name="hibernate.cache.infinispan.entity.expiration.max_idle"
   value= "30000"/>

With the above configuration, you’re overriding whatever eviction/expiration settings were defined for the default entity cache name in the Infinispan cache configuration used, regardless of whether it’s the default one or user defined. More specifically, we’re defining the following:

  • All entities to use LRU eviction strategy

  • The eviction thread to wake up every 2 seconds (2000 milliseconds)

  • The maximum number of entities for each entity type to be 5000 entries

  • The lifespan of each entity instance to be 1 minute (600000 milliseconds).

  • The maximum idle time for each entity instance to be 30 seconds (30000 milliseconds).

You can also override eviction/expiration settings on a per entity/collection type basis in such way that the overriden settings only afftect that particular entity (i.e. com.acme.Person) or collection type (i.e. com.acme.Person.addresses). Example:

<property name="hibernate.cache.infinispan.com.acme.Person.eviction.strategy"
   value= "LIRS"/>

Inside of Wildfly, same as with the entity/collection configuration override, eviction/expiration settings would also require deployment name and persistence unit information:

<property name="hibernate.cache.infinispan._war_or_ear_name_._unit_name_.com.acme.Person.eviction.strategy"
   value= "LIRS"/>
<property name="hibernate.cache.infinispan._war_or_ear_name_._unit_name_.com.acme.Person.expiration.lifespan"
   value= "65000"/>

In versions prior to 5.1, hibernate.cache.infinispan.something.expiration.wake_up_interval was called hibernate.cache.infinispan.something.eviction.wake_up_interval. Eviction settings are checked upon each cache insert, it is expiration that needs to be triggered periodically. The old property still works, but its use is deprecated.

Property hibernate.cache.infinispan.use_synchronization that allowed to register Infinispan as XA resource in the transaction has been deprecated in 5.0 and is not honored anymore. Infinispan 2LC must register as synchronizations on transactional caches. Also, non-transactional cache modes hook into the current JTA/JDBC transaction as synchronizations.

13.11.6. Remote Infinispan Caching

Lately, several questions ( here and here ) have appeared in the Infinispan user forums asking whether it’d be possible to have an Infinispan second level cache that instead of living in the same JVM as the Hibernate code, it resides in a remote server, i.e. an Infinispan Hot Rod server. It’s important to understand that trying to set up second level cache in this way is generally not a good idea for the following reasons:

  • The purpose of a JPA/Hibernate second level cache is to store entities/collections recently retrieved from database or to maintain results of recent queries. So, part of the aim of the second level cache is to have data accessible locally rather than having to go to the database to retrieve it everytime this is needed. Hence, if you decide to set the second level cache to be remote as well, you’re losing one of the key advantages of the second level cache: the fact that the cache is local to the code that requires it.

  • Setting a remote second level cache can have a negative impact in the overall performance of your application because it means that cache misses require accessing a remote location to verify whether a particular entity/collection/query is cached. With a local second level cache however, these misses are resolved locally and so they are much faster to execute than with a remote second level cache.

There are however some edge cases where it might make sense to have a remote second level cache, for example:

  • You are having memory issues in the JVM where JPA/Hibernate code and the second level cache is running. Off loading the second level cache to remote Hot Rod servers could be an interesting way to separate systems and allow you find the culprit of the memory issues more easily.

  • Your application layer cannot be clustered but you still want to run multiple application layer nodes. In this case, you can’t have multiple local second level cache instances running because they won’t be able to invalidate each other for example when data in the second level cache is updated. In this case, having a remote second level cache could be a way out to make sure your second level cache is always in a consistent state, will all nodes in the application layer pointing to it.

  • Rather than having the second level cache in a remote server, you want to simply keep the cache in a separate VM still within the same machine. In this case you would still have the additional overhead of talking across to another JVM, but it wouldn’t have the latency of across a network.

    The benefit of doing this is that:

    • Size the cache separate from the application, since the cache and the application server have very different memory profiles. One has lots of short lived objects, and the other could have lots of long lived objects.

    • To pin the cache and the application server onto different CPU cores (using numactl ), and even pin them to different physically memory based on the NUMA nodes.

14. Interceptors and events

It is useful for the application to react to certain events that occur inside Hibernate. This allows for the implementation of generic functionality and the extension of Hibernate functionality.

14.1. Interceptors

The org.hibernate.Interceptor interface provides callbacks from the session to the application, allowing the application to inspect and/or manipulate properties of a persistent object before it is saved, updated, deleted or loaded.

One possible use for this is to track auditing information. The following example shows an Interceptor implementation that automatically logs when an entity is updated.

public static class LoggingInterceptor extends EmptyInterceptor {
    @Override
    public boolean onFlushDirty(
        Object entity,
        Serializable id,
        Object[] currentState,
        Object[] previousState,
        String[] propertyNames,
        Type[] types) {
            LOGGER.debugv( "Entity {0}#{1} changed from {2} to {3}",
                entity.getClass().getSimpleName(),
                id,
                Arrays.toString( previousState ),
                Arrays.toString( currentState )
            );
            return super.onFlushDirty( entity, id, currentState,
                previousState, propertyNames, types
        );
    }
}

You can either implement Interceptor directly or extend org.hibernate.EmptyInterceptor.

An Interceptor can be either Session-scoped or SessionFactory-scoped.

A Session-scoped interceptor is specified when a session is opened.

SessionFactory sessionFactory = entityManagerFactory.unwrap( SessionFactory.class );
Session session = sessionFactory
    .withOptions()
    .interceptor(new LoggingInterceptor() )
    .openSession();
session.getTransaction().begin();

Customer customer = session.get( Customer.class, customerId );
customer.setName( "Mr. John Doe" );
//Entity Customer#1 changed from [John Doe, 0] to [Mr. John Doe, 0]

session.getTransaction().commit();

A SessionFactory-scoped interceptor is registered with the Configuration object prior to building the SessionFactory. Unless a session is opened explicitly specifying the interceptor to use, the SessionFactory-scoped interceptor will be applied to all sessions opened from that SessionFactory. SessionFactory-scoped interceptors must be thread safe. Ensure that you do not store session-specific states since multiple sessions will use this interceptor potentially concurrently.

SessionFactory sessionFactory = new MetadataSources( new StandardServiceRegistryBuilder().build() )
    .addAnnotatedClass( Customer.class )
    .getMetadataBuilder()
    .build()
    .getSessionFactoryBuilder()
    .applyInterceptor( new LoggingInterceptor() )
    .build();

14.2. Native Event system

If you have to react to particular events in the persistence layer, you can also use the Hibernate event architecture. The event system can be used in place of or in addition to interceptors.

Many methods of the Session interface correlate to an event type. The full range of defined event types is declared as enum values on org.hibernate.event.spi.EventType. When a request is made of one of these methods, the Session generates an appropriate event and passes it to the configured event listener(s) for that type.

Applications are free to implement a customization of one of the listener interfaces (i.e., the LoadEvent is processed by the registered implementation of the LoadEventListener interface), in which case their implementation would be responsible for processing any load() requests made of the Session.

The listeners should be considered stateless; they are shared between requests, and should not save any state as instance variables.

A custom listener implements the appropriate interface for the event it wants to process and/or extend one of the convenience base classes (or even the default event listeners used by Hibernate out-of-the-box as these are declared non-final for this purpose).

Here is an example of a custom load event listener:

Example 445. Custom LoadListener example
EntityManagerFactory entityManagerFactory = entityManagerFactory();
SessionFactoryImplementor sessionFactory = entityManagerFactory.unwrap( SessionFactoryImplementor.class );
sessionFactory
    .getServiceRegistry()
    .getService( EventListenerRegistry.class )
    .prependListeners( EventType.LOAD, new SecuredLoadEntityListener() );

Customer customer = entityManager.find( Customer.class, customerId );

14.3. Mixing Events and Interceptors

When you want to customize the entity state transition behavior, you have to options:

  1. you provide a custom Interceptor, which is taken into consideration by the default Hibernate event listeners. For example, the Interceptor#onSave() method is invoked by Hibernate AbstractSaveEventListener. Or, the Interceptor#onLoad() is called by the DefaultPreLoadEventListener.

  2. you can replace any given default event listener with your own implementation. When doing this, you should probably extend the default listeners because otherwise you’d have to take care of all the low-level entity state transition logic. For example, if you replace the DefaultPreLoadEventListener with your own implementation, then, only if you call the Interceptor#onLoad() method explicitly, you can mix the custom load event listener with a custom Hibernate interceptor.

14.4. Hibernate declarative security

Usually, declarative security in Hibernate applications is managed in a session facade layer. Hibernate allows certain actions to be authorized via JACC and JAAS. This is an optional functionality that is built on top of the event architecture.

First, you must configure the appropriate event listeners, to enable the use of JACC authorization. Again, see Event Listener Registration for the details.

Below is an example of an appropriate org.hibernate.integrator.spi.Integrator implementation for this purpose.

Example 446. JACC listener registration example
public static class JaccIntegrator implements ServiceContributingIntegrator {

    private static final Logger log = Logger.getLogger( JaccIntegrator.class );

    private static final DuplicationStrategy DUPLICATION_STRATEGY =
            new DuplicationStrategy() {
        @Override
        public boolean areMatch(Object listener, Object original) {
            return listener.getClass().equals( original.getClass() ) &&
                    JaccSecurityListener.class.isInstance( original );
        }

        @Override
        public Action getAction() {
            return Action.KEEP_ORIGINAL;
        }
    };

    @Override
    public void prepareServices(
            StandardServiceRegistryBuilder serviceRegistryBuilder) {
        boolean isSecurityEnabled = serviceRegistryBuilder
                .getSettings().containsKey( AvailableSettings.JACC_ENABLED );
        final JaccService jaccService = isSecurityEnabled ?
                new StandardJaccServiceImpl() : new DisabledJaccServiceImpl();
        serviceRegistryBuilder.addService( JaccService.class, jaccService );
    }

    @Override
    public void integrate(
            Metadata metadata,
            SessionFactoryImplementor sessionFactory,
            SessionFactoryServiceRegistry serviceRegistry) {
        doIntegration(
                serviceRegistry
                        .getService( ConfigurationService.class ).getSettings(),
                // pass no permissions here, because atm actually injecting the
                // permissions into the JaccService is handled on SessionFactoryImpl via
                // the org.hibernate.boot.cfgxml.spi.CfgXmlAccessService
                null,
                serviceRegistry
        );
    }

    private void doIntegration(
            Map properties,
            JaccPermissionDeclarations permissionDeclarations,
            SessionFactoryServiceRegistry serviceRegistry) {
        boolean isSecurityEnabled = properties
                .containsKey( AvailableSettings.JACC_ENABLED );
        if ( ! isSecurityEnabled ) {
            log.debug( "Skipping JACC integration as it was not enabled" );
            return;
        }

        final String contextId = (String) properties
                .get( AvailableSettings.JACC_CONTEXT_ID );
        if ( contextId == null ) {
            throw new IntegrationException( "JACC context id must be specified" );
        }

        final JaccService jaccService = serviceRegistry
                .getService( JaccService.class );
        if ( jaccService == null ) {
            throw new IntegrationException( "JaccService was not set up" );
        }

        if ( permissionDeclarations != null ) {
            for ( GrantedPermission declaration : permissionDeclarations
                    .getPermissionDeclarations() ) {
                jaccService.addPermission( declaration );
            }
        }

        final EventListenerRegistry eventListenerRegistry =
                serviceRegistry.getService( EventListenerRegistry.class );
        eventListenerRegistry.addDuplicationStrategy( DUPLICATION_STRATEGY );

        eventListenerRegistry.prependListeners(
                EventType.PRE_DELETE, new JaccPreDeleteEventListener() );
        eventListenerRegistry.prependListeners(
                EventType.PRE_INSERT, new JaccPreInsertEventListener() );
        eventListenerRegistry.prependListeners(
                EventType.PRE_UPDATE, new JaccPreUpdateEventListener() );
        eventListenerRegistry.prependListeners(
                EventType.PRE_LOAD, new JaccPreLoadEventListener() );
    }

    @Override
    public void disintegrate(SessionFactoryImplementor sessionFactory,
                             SessionFactoryServiceRegistry serviceRegistry) {
        // nothing to do
    }
}

You must also decide how to configure your JACC provider. Consult your JACC provider documentation.

14.5. JPA Callbacks

JPA also defines a more limited set of callbacks through annotations.

Table 7. Callback annotations
Type Description

@PrePersist

Executed before the entity manager persist operation is actually executed or cascaded. This call is synchronous with the persist operation.

@PreRemove

Executed before the entity manager remove operation is actually executed or cascaded. This call is synchronous with the remove operation.

@PostPersist

Executed after the entity manager persist operation is actually executed or cascaded. This call is invoked after the database INSERT is executed.

@PostRemove

Executed after the entity manager remove operation is actually executed or cascaded. This call is synchronous with the remove operation.

@PreUpdate

Executed before the database UPDATE operation.

@PostUpdate

Executed after the database UPDATE operation.

@PostLoad

Executed after an entity has been loaded into the current persistence context or an entity has been refreshed.

There are two available approaches defined for specifying callback handling:

  • The first approach is to annotate methods on the entity itself to receive notification of particular entity life cycle event(s).

  • The second is to use a separate entity listener class. An entity listener is a stateless class with a no-arg constructor. The callback annotations are placed on a method of this class instead of the entity class. The entity listener class is then associated with the entity using the javax.persistence.EntityListeners annotation

Example 447. Example of specifying JPA callbacks
@Entity
@EntityListeners( LastUpdateListener.class )
public static class Person {

    @Id
    private Long id;

    private String name;

    private Date dateOfBirth;

    @Transient
    private long age;

    private Date lastUpdate;

    public void setLastUpdate(Date lastUpdate) {
        this.lastUpdate = lastUpdate;
    }

    /**
     * Set the transient property at load time based on a calculation.
     * Note that a native Hibernate formula mapping is better for this purpose.
     */
    @PostLoad
    public void calculateAge() {
        age = ChronoUnit.YEARS.between( LocalDateTime.ofInstant(
                Instant.ofEpochMilli( dateOfBirth.getTime()), ZoneOffset.UTC),
            LocalDateTime.now()
        );
    }
}

public static class LastUpdateListener {

    @PreUpdate
    @PrePersist
    public void setLastUpdate( Person p ) {
        p.setLastUpdate( new Date() );
    }
}

These approaches can be mixed, meaning you can use both together.

Regardless of whether the callback method is defined on the entity or on an entity listener, it must have a void-return signature. The name of the method is irrelevant as it is the placement of the callback annotations that makes the method a callback. In the case of callback methods defined on the entity class, the method must additionally have a no-argument signature. For callback methods defined on an entity listener class, the method must have a single argument signature; the type of that argument can be either java.lang.Object (to facilitate attachment to multiple entities) or the specific entity type.

A callback method can throw a RuntimeException. If the callback method does throw a RuntimeException, then the current transaction, if any, must be rolled back.

A callback method must not invoke EntityManager or Query methods!

It is possible that multiple callback methods are defined for a particular lifecycle event. When that is the case, the defined order of execution is well defined by the JPA spec (specifically section 3.5.4):

  • Any default listeners associated with the entity are invoked first, in the order they were specified in the XML. See the javax.persistence.ExcludeDefaultListeners annotation.

  • Next, entity listener class callbacks associated with the entity hierarchy are invoked, in the order they are defined in the EntityListeners. If multiple classes in the entity hierarchy define entity listeners, the listeners defined for a superclass are invoked before the listeners defined for its subclasses. See the `javax.persistence.ExcludeSuperclassListener`s annotation.

  • Lastly, callback methods defined on the entity hierarchy are invoked. If a callback type is annotated on both an entity and one or more of its superclasses without method overriding, both would be called, the most general superclass first. An entity class is also allowed to override a callback method defined in a superclass in which case the super callback would not get invoked; the overriding method would get invoked provided it is annotated.

14.6. Default entity listeners

The JPA specification allows you to define a default entity listener which is going to be applied for every entity in that particular system. Default entity listeners can only be defined in XML mapping files.

Example 448. Default event listner mapping
public class DefaultEntityListener {

    public void onPersist(Object entity) {
        if ( entity instanceof BaseEntity ) {
            BaseEntity baseEntity = (BaseEntity) entity;
            baseEntity.setCreatedOn( now() );
        }
    }

    public void onUpdate(Object entity) {
        if ( entity instanceof BaseEntity ) {
            BaseEntity baseEntity = (BaseEntity) entity;
            baseEntity.setUpdatedOn( now() );
        }
    }

    private Timestamp now() {
        return Timestamp.from(
            LocalDateTime.now().toInstant( ZoneOffset.UTC )
        );
    }
}
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm
                 http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd"
                 version="2.1">
    <persistence-unit-metadata>
        <persistence-unit-defaults>
            <entity-listeners>
                <entity-listener
                    class="org.hibernate.userguide.events.DefaultEntityListener">
                    <pre-persist method-name="onPersist"/>
                    <pre-update method-name="onUpdate"/>
                </entity-listener>
            </entity-listeners>
        </persistence-unit-defaults>
    </persistence-unit-metadata>
</entity-mappings>

Considering that all entities extend the BaseEntity class:

@MappedSuperclass
public abstract class BaseEntity {

    private Timestamp createdOn;

    private Timestamp updatedOn;

    public Timestamp getCreatedOn() {
        return createdOn;
    }

    void setCreatedOn(Timestamp createdOn) {
        this.createdOn = createdOn;
    }

    public Timestamp getUpdatedOn() {
        return updatedOn;
    }

    void setUpdatedOn(Timestamp updatedOn) {
        this.updatedOn = updatedOn;
    }
}
@Entity(name = "Person")
public static class Person extends BaseEntity {

    @Id
    private Long id;

    private String name;

    //Getters and setters omitted for brevity
}

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

    @Id
    private Long id;

    private String title;

    @ManyToOne
    private Person author;

    //Getters and setters omitted for brevity
}

When persisting a Person or Book entity, the createdOn is going to be set by the onPersist method of the DefaultEntityListener.

Example 449. Default event listner persist event
Person author = new Person();
author.setId( 1L );
author.setName( "Vlad Mihalcea" );

entityManager.persist( author );

Book book = new Book();
book.setId( 1L );
book.setTitle( "High-Performance Java Persistence" );
book.setAuthor( author );

entityManager.persist( book );
insert
into
    Person
    (createdOn, updatedOn, name, id)
values
    (?, ?, ?, ?)

-- binding parameter [1] as [TIMESTAMP] - [2017-06-08 19:23:48.224]
-- binding parameter [2] as [TIMESTAMP] - [null]
-- binding parameter [3] as [VARCHAR]   - [Vlad Mihalcea]
-- binding parameter [4] as [BIGINT]    - [1]

insert
into
    Book
    (createdOn, updatedOn, author_id, title, id)
values
    (?, ?, ?, ?, ?)

-- binding parameter [1] as [TIMESTAMP] - [2017-06-08 19:23:48.246]
-- binding parameter [2] as [TIMESTAMP] - [null]
-- binding parameter [3] as [BIGINT]    - [1]
-- binding parameter [4] as [VARCHAR]   - [High-Performance Java Persistence]
-- binding parameter [5] as [BIGINT]    - [1]

When updating a Person or Book entity, the updatedOn is going to be set by the onUpdate method of the DefaultEntityListener.

Example 450. Default event listner update event
Person author = entityManager.find( Person.class, 1L );
author.setName( "Vlad-Alexandru Mihalcea" );

Book book = entityManager.find( Book.class, 1L );
book.setTitle( "High-Performance Java Persistence 2nd Edition" );
update
    Person
set
    createdOn=?,
    updatedOn=?,
    name=?
where
    id=?

-- binding parameter [1] as [TIMESTAMP] - [2017-06-08 19:23:48.224]
-- binding parameter [2] as [TIMESTAMP] - [2017-06-08 19:23:48.316]
-- binding parameter [3] as [VARCHAR]   - [Vlad-Alexandru Mihalcea]
-- binding parameter [4] as [BIGINT]    - [1]

update
    Book
set
    createdOn=?,
    updatedOn=?,
    author_id=?,
    title=?
where
    id=?

-- binding parameter [1] as [TIMESTAMP] - [2017-06-08 19:23:48.246]
-- binding parameter [2] as [TIMESTAMP] - [2017-06-08 19:23:48.317]
-- binding parameter [3] as [BIGINT]    - [1]
-- binding parameter [4] as [VARCHAR]   - [High-Performance Java Persistence 2nd Edition]
-- binding parameter [5] as [BIGINT]    - [1]
</