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 vice 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
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:
- 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. TheEntityManagerFactory
is the JPA equivalent of aSessionFactory
and basically, those two converge into the sameSessionFactory
implementation.A
SessionFactory
is very expensive to create, so, for any given database, the application should have only one associatedSessionFactory
. TheSessionFactory
maintains services that Hibernate uses across allSession(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 anEntityManager
.Behind the scenes, the Hibernate
Session
wraps a JDBCjava.sql.Connection
and acts as a factory fororg.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 behavioral aspects 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 mapping a Java type to an SQL type as well as how to persist and fetch a given Java type to and from a relational database. 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 the context. |
To help understand the type categorizations, let’s look at a simple table and domain model that we wish to map.
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 firstName;
private String middleName;
private String lastName;
// 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 types.
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 (e.g., using
@Column
or@Table
) 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 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, |
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.
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 forjpa
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 followsImplicitNamingStrategyJpaCompliantImpl
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 But the point here is the separation of concerns. The |
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
/*
* 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.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl;
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
* @author Nathan Xu
*/
public class AcmeCorpPhysicalNamingStrategy extends PhysicalNamingStrategyStandardImpl {
private static final Map<String, String> ABBREVIATIONS;
static {
ABBREVIATIONS = new TreeMap<>( String.CASE_INSENSITIVE_ORDER );
ABBREVIATIONS.put( "account", "acct" );
ABBREVIATIONS.put( "number", "num" );
}
@Override
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) {
final List<String> parts = splitAndReplace( name.getText() );
return jdbcEnvironment.getIdentifierHelper().toIdentifier(
StringUtils.join( parts, '_' ),
name.isQuoted()
);
}
@Override
public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment jdbcEnvironment) {
final List<String> parts = splitAndReplace( name.getText() );
// Acme Corp says all sequences should end with _seq
if ( !"seq".equals( parts.get( parts.size() - 1 ) ) ) {
parts.add( "seq" );
}
return jdbcEnvironment.getIdentifierHelper().toIdentifier(
StringUtils.join( parts, '_' ),
name.isQuoted()
);
}
@Override
public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment jdbcEnvironment) {
final List<String> parts = splitAndReplace( name.getText() );
return jdbcEnvironment.getIdentifierHelper().toIdentifier(
StringUtils.join( parts, '_' ),
name.isQuoted()
);
}
private List<String> splitAndReplace(String name) {
return Arrays.stream( StringUtils.splitByCharacterTypeCamelCase( name ) )
.filter( StringUtils::isNotBlank )
.map( p -> ABBREVIATIONS.getOrDefault( p, p ).toLowerCase( Locale.ROOT ) )
.collect( Collectors.toList() );
}
}
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
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 |
character, char, java.lang.Character |
BooleanType |
BOOLEAN |
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 |
IntegerType |
INTEGER |
int, java.lang.Integer |
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.util.Date |
timestamp, java.sql.Timestamp, java.util.Date |
DbTimestampType |
TIMESTAMP |
java.util.Date |
dbtimestamp |
TimeType |
TIME |
java.util.Date |
time, java.sql.Time |
DateType |
DATE |
java.util.Date |
date, java.sql.Date |
CalendarType |
TIMESTAMP |
java.util.Calendar |
calendar, java.util.Calendar, java.util.GregorianCalendar |
CalendarDateType |
DATE |
java.util.Calendar |
calendar_date |
CalendarTimeType |
TIME |
java.util.Calendar |
calendar_time |
CurrencyType |
VARCHAR |
java.util.Currency |
currency, java.util.Currency |
LocaleType |
VARCHAR |
java.util.Locale |
locale, java.util.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 |
RowVersionType |
VARBINARY |
byte[] |
row_version |
ObjectType |
VARCHAR |
implementors of java.lang.Serializable |
object, java.lang.Object |
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 |
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, and the class names of Geometry and its subclasses |
GeolatteGeometryType |
depends on the dialect |
org.geolatte.geom.Geometry |
geolatte_geometry, and the class names of Geometry and its subclasses |
To use the Hibernate Spatial types, you must add the See the Spatial chapter for more details. |
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.
@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;
}
@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:
If provider portability is a concern, you should stick to just these basic types. Note that JPA 2.1 introduced the |
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 its 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 is fetched when the attribute is accessed. Hibernate ignores this setting for basic types unless you are using bytecode enhancement. See the Bytecode Enhancement 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 strategies.
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.
@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 an 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
(an 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 the implicit resolution.
A thorough discussion of Please see the Integration 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.
@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 |
First, we need to extend the AbstractSingleColumnStandardBasicType
like this:
BasicType
implementationpublic 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:
AbstractTypeDescriptor
implementationpublic 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:
BasicType
implementationconfiguration.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:
BasicType
mapping@Entity(name = "Product")
public static class Product {
@Id
private Integer id;
@Type( type = "bitset" )
private BitSet bitSet;
public Integer getId() {
return id;
}
//Getters and setters are omitted for brevity
}
Alternatively, you can use the @TypeDef
and skip the registration phase:
@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;
//Getters and setters are omitted for brevity
}
To validate this new BasicType
implementation, we can test it as follows:
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:
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.
UserType
implementationpublic 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:
UserType
mapping@Entity(name = "Product")
public static class Product {
@Id
private Integer id;
@Type( type = "bitset" )
private BitSet bitSet;
//Constructors, getters, and setters are omitted for brevity
}
In this example, the UserType
is registered under the bitset
name, and this is done like this:
UserType
implementationconfiguration.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 Without registering a name, the
|
When running the previous test case against the BitSetUserType
entity mapping, Hibernate executed the following SQL statements:
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
or @MapKeyEnumerated
for map keys annotations, working 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:
PhoneType
enumerationpublic enum PhoneType {
LAND_LINE,
MOBILE;
}
In the ORDINAL example, the phone_type
column is defined as a (nullable) INTEGER type and would hold:
NULL
-
For null values
0
-
For the
LAND_LINE
enum 1
-
For the
MOBILE
enum
@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:
@Enumerated(ORDINAL)
mappingPhone 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', 1, 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
@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 as in the @Enumerated(ORDINAL)
example, Hibernate generates the following SQL statement:
@Enumerated(STRING)
mappingINSERT 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.
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.
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 So, when using the |
Using the AttributeConverter entity property as a query parameter
Assuming you have the following entity:
Photo
entity with AttributeConverter
@Entity(name = "Photo")
public static class Photo {
@Id
private Integer id;
private String name;
@Convert(converter = CaptionConverter.class)
private Caption caption;
//Getters and setters are omitted for brevity
}
And the Caption
class looks as follows:
Caption
Java objectpublic static class Caption {
private String text;
public Caption(String text) {
this.text = text;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
Caption caption = (Caption) o;
return text != null ? text.equals( caption.text ) : caption.text == null;
}
@Override
public int hashCode() {
return text != null ? text.hashCode() : 0;
}
}
And we have an AttributeConverter
to handle the Caption
Java object:
Caption
Java object AttributeConverterpublic static class CaptionConverter
implements AttributeConverter<Caption, String> {
@Override
public String convertToDatabaseColumn(Caption attribute) {
return attribute.getText();
}
@Override
public Caption convertToEntityAttribute(String dbData) {
return new Caption( dbData );
}
}
Traditionally, you could only use the DB data Caption
representation, which in our case is a String
, when referencing the caption
entity property.
Caption
property using the DB data representationPhoto photo = entityManager.createQuery(
"select p " +
"from Photo p " +
"where upper(caption) = upper(:caption) ", Photo.class )
.setParameter( "caption", "Nicolae Grigorescu" )
.getSingleResult();
In order to use the Java object Caption
representation, you have to get the associated Hibernate Type
.
Caption
property using the Java Object representationSessionFactory sessionFactory = entityManager.getEntityManagerFactory()
.unwrap( SessionFactory.class );
MetamodelImplementor metamodelImplementor = (MetamodelImplementor) sessionFactory.getMetamodel();
Type captionType = metamodelImplementor
.entityPersister( Photo.class.getName() )
.getPropertyType( "caption" );
Photo photo = (Photo) entityManager.createQuery(
"select p " +
"from Photo p " +
"where upper(caption) = upper(:caption) ", Photo.class )
.unwrap( Query.class )
.setParameter( "caption", new Caption("Nicolae Grigorescu"), captionType)
.getSingleResult();
By passing the associated Hibernate Type
, you can use the Caption
object when binding the query parameter value.
Mapping an AttributeConverter using HBM mappings
When using HBM mappings, you can still make use of the JPA AttributeConverter
because Hibernate supports
such mapping via the type
attribute as demonstrated by the following example.
Let’s consider we have an application-specific Money
type:
Money
typepublic 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;
}
}
Now, we want to use the Money
type when mapping the Account
entity:
Account
entity using the Money
typepublic class Account {
private Long id;
private String owner;
private Money balance;
//Getters and setters are omitted for brevity
}
Since Hibernate has no knowledge how to persist the Money
type, we could use a JPA AttributeConverter
to transform the Money
type as a Long
. For this purpose, we are going to use the following
MoneyConverter
utility:
MoneyConverter
implementing the JPA AttributeConverter
interfacepublic 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 );
}
}
To map the MoneyConverter
using HBM configuration files you need to use the converted::
prefix in the type
attribute of the property
element.
AttributeConverter
<?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 package="org.hibernate.userguide.mapping.converter.hbm">
<class name="Account" table="account" >
<id name="id"/>
<property name="owner"/>
<property name="balance"
type="converted::org.hibernate.userguide.mapping.converter.hbm.MoneyConverter"/>
</class>
</hibernate-mapping>
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.
@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.
Mapping CLOB
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).
Considering we have the following database table:
CREATE TABLE Product (
id INTEGER NOT NULL,
name VARCHAR(255),
warranty CLOB,
PRIMARY KEY (id)
)
Let’s first map this using the @Lob
JPA annotation and the java.sql.Clob
type:
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 the ClobProxy
Hibernate utility:
java.sql.Clob
entityString warranty = "My product warranty";
final Product product = new Product();
product.setId( 1 );
product.setName( "Mobile phone" );
product.setWarranty( ClobProxy.generateProxy( warranty ) );
entityManager.persist( product );
To retrieve the Clob
content, you need to transform the underlying java.io.Reader
:
java.sql.Clob
entityProduct 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[]
.
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 However, some drivers are trickier (e.g. PostgreSQL), and, in such cases, you may have to do some extra steps to get LOBs working. Such discussions are beyond the scope of this guide. |
We might even want the materialized data as a char array (although this might not be a very good idea).
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
}
Mapping BLOB
BLOB
data is mapped in a similar fashion.
Considering we have the following database table:
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.
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 the BlobProxy
Hibernate utility:
java.sql.Blob
entitybyte[] image = new byte[] {1, 2, 3};
final Product product = new Product();
product.setId( 1 );
product.setName( "Mobile phone" );
product.setImage( BlobProxy.generateProxy( image ) );
entityManager.persist( product );
To retrieve the Blob
content, you need to transform the underlying java.io.InputStream
:
java.sql.Blob
entityProduct 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[]
).
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
Considering we have the following database table:
NVARCHAR
- SQLCREATE 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.
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:
NCLOB
- SQLCREATE TABLE Product (
id INTEGER NOT NULL ,
name VARCHAR(255) ,
warranty nclob ,
PRIMARY KEY ( id )
)
Hibernate can map the NCLOB
to a java.sql.NClob
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 an NClob
using the NClobProxy
Hibernate utility:
java.sql.NClob
entityString warranty = "My product warranty";
final Product product = new Product();
product.setId( 1 );
product.setName( "Mobile phone" );
product.setWarranty( NClobProxy.generateProxy( warranty ) );
entityManager.persist( product );
To retrieve the NClob
content, you need to transform the underlying java.io.Reader
:
java.sql.NClob
entityProduct 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[]
.
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.
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 your application and database use nationalization, you may instead want to enable nationalized character data as the default. You can do this via the |
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 the binary one because it uses a more efficient column storage. However, many applications prefer the readability of the character-based column storage.
To switch the default mapping, simply call |
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 a 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. PostgreSQL-specific UUID
When using one of the PostgreSQL Dialects, the PostgreSQL-specific UUID Hibernate type becomes the default UUID mapping. |
Maps the UUID using the PostgreSQL-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 the user’s behalf. For details, see the discussion of generators in Identifiers.
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 |
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:
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:
java.util.Date
mappingDateEvent 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 fields were saved into the database.
If we change the @Temporal
type to TIME
:
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
:
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 If the |
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
andjava.time.ZonedDateTime
Because the mapping between the Java 8 Date/Time classes and the SQL types is implicit, there is not need to specify the Setting it on the 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.time.Period
is going to be mapped to a VARCHAR
database column.
java.time.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.
java.time.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:
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 known 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;
//Getters and setters are omitted for brevity
}
@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
}
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 );
}
}
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 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.
@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
}
@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 backticks to quote these column names.
When saving the following Product entity
, Hibernate generates the following SQL insert statement:
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:
@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 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:
@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.
@Generated
persist examplePerson 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.
@Generated
update examplePerson 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:
@GeneratorType
mapping examplepublic 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.
@GeneratorType
persist exampleCurrentUser.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.
@GeneratorType
update exampleCurrentUser.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
@CreationTimestamp
mapping example@Entity(name = "Event")
public static class Event {
@Id
@GeneratedValue
private Long id;
@Column(name = "`timestamp`")
@CreationTimestamp
private Date timestamp;
//Constructors, getters, and setters are omitted for brevity
}
When the Event
entity is persisted, Hibernate is going to populate the underlying timestamp
column with the current JVM timestamp value:
@CreationTimestamp
persist exampleEvent 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
@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:
@UpdateTimestamp
persist exampleBid 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:
@UpdateTimestamp
update exampleBid 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 added to the 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):
ValueGenerationType
mapping for database generation@Entity(name = "Event")
public static class Event {
@Id
@GeneratedValue
private Long id;
@Column(name = "`timestamp`")
@FunctionCreationTimestamp
private Date timestamp;
//Constructors, getters, and setters are omitted for brevity
}
@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:
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;
//Constructors, getters, and setters are omitted for brevity
}
@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.
@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
}
If a property uses more than one column, you must use the forColumn
attribute to specify which column the @ColumnTransformer
read and write expressions are targeting.
@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.
@ColumnTransformer
and a composite typedoInJPA( 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
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
:
@Formula
mappingdoInJPA( 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 defined by the |
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 to as Throughout this chapter and thereafter, for brevity sake, embeddable types may also be referred to as embeddable. |
@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 a 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.
@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;
//Getters and setters, equals and hashCode methods 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:
|
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.
@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 to set 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 Embeddable type is used multiple times in some entity, you need to use the
@AttributeOverride
and
@AssociationOverride
annotations
to override the default column names defined by the Embeddable.
Considering you have the following Publisher
embeddable type
which defines a @ManyToOne
association with the Country
entity:
@ManyToOne
association@Embeddable
public static class Publisher {
private String name;
@ManyToOne(fetch = FetchType.LAZY)
private Country country;
//Getters and setters, equals and hashCode methods 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 versions,
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:
@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
The |
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.
@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;
//Getters and setters, equals and hashCode methods 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.
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 valued collections (as embeddable types are value types). Value collections are covered in detail in Collections of value types.
2.4.6. Embeddable type as a Map key
Embeddable types can also be used as Map
keys.
This topic is converted in detail in Map - key.
2.4.7. Embeddable type as identifier
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.4.8. @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 entity 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.
@Target
mapping usagepublic 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:
@Target
persist exampledoInJPA( 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:
@Target
fetching exampledoInJPA( 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.4.9. @Parent
mapping
The Hibernate-specific @Parent
annotation allows you to reference the owner entity from within an embeddable.
@Parent
mapping usage@Embeddable
public static class GPS {
private double latitude;
private double longitude;
@Parent
private City city;
//Getters and setters omitted for brevity
}
@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:
@Parent
persist exampledoInJPA( 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:
@Parent
fetching exampledoInJPA( 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.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 to as Throughout this chapter and thereafter, entity types will be simply referred to 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 referencing 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 with 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 Bytecode Enhancement 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 the 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. Providing identifier attribute(s)
Historically, providing identifier attributes was considered optional. However, not defining identifier attributes 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 wrapper (i.e., non-primitive) type (e.g. |
The placement of the @Id
annotation marks the persistence state access strategy.
@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 the name
attribute which is used to give a specific entity name for use in JPQL queries.
By default, if the name attribute of the @Entity
annotation is missing, the unqualified name of the entity class itself will be used as the entity name.
Because the entity name is given by the unqualified name of the class, Hibernate does not allow registering multiple entities with the same name even if the entity classes reside in different packages. Without imposing this restriction, Hibernate would not know which entity class is referenced in a JPQL query if the unqualified entity name is associated with more then one entity classes. |
In the following example, the entity name (e.g. Book
) is given by the unqualified name of the entity class name.
@Entity
mapping with an implicit name@Entity
public class Book {
@Id
private Long id;
private String title;
private String author;
//Getters and setters are omitted for brevity
}
However, the entity name can also be set explicitly as illustrated by the following example.
@Entity
mapping with an explicit name@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.
@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
}
Mapping the catalog of the associated table
Without specifying the catalog of the associated database table a given entity is mapped to, Hibernate will use the default catalog associated with the current database connection.
However, if your database hosts multiple catalogs, you can specify the catalog where a given table is located using the catalog
attribute of the JPA @Table
annotation.
Let’s assume we are using MySQL and want to map a Book
entity to the book
table located in the public
catalog
which looks as follows.
book
table located in the public
catalogcreate table public.book (
id bigint not null,
author varchar(255),
title varchar(255),
primary key (id)
) engine=InnoDB
Now, to map the Book
entity to the book
table in the public
catalog we can use the catalog
attribute of the @Table
JPA annotation.
@Table
annotation@Entity(name = "Book")
@Table(
catalog = "public",
name = "book"
)
public static class Book {
@Id
private Long id;
private String title;
private String author;
//Getters and setters are omitted for brevity
}
Mapping the schema of the associated table
Without specifying the schema of the associated database table a given entity is mapped to, Hibernate will use the default schema associated with the current database connection.
However, if your database supports schemas, you can specify the schema where a given table is located using the schema
attribute of the JPA @Table
annotation.
Let’s assume we are using PostgreSQL and want to map a Book
entity to the book
table located in the library
schema
which looks as follows.
book
table located in the library
schemacreate table library.book (
id int8 not null,
author varchar(255),
title varchar(255),
primary key (id)
)
Now, to map the Book
entity to the book
table in the library
schema we can use the schema
attribute of the @Table
JPA annotation.
@Table
annotation@Entity(name = "Book")
@Table(
schema = "library",
name = "book"
)
public static class Book {
@Id
private Long id;
private String title;
private String author;
//Getters and setters are omitted for brevity
}
The Therefore, if you’re using MySQL or MariaDB, which do not support schemas natively (schemas being just an alias for catalog), you need to use the
|
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:
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:
@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
}
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:
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 references are not going to be equal since their references are different.
Consider yet another case:
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:
@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:
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 the 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
:
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.
@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:
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 It’s possible to use the entity identifier for equality check, but it needs a workaround:
|
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.
@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(atr.cents) as balance " +
"from account a " +
"join client c on c.id = a.client_id " +
"join account_transaction atr on a.id = atr.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 match the proper amount of money in this Account
.
@Subselect
entitydoInJPA( 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:
@Subselect
entitydoInJPA( 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 With the |
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.
Identifiable
interfacepublic 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 illustrated by the following example:
Identifiable
interfacedoInHibernate( 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",
book instanceof Identifiable
);
assertFalse(
"Proxy class was not created",
book instanceof Book
);
} );
insert
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.
@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 Cuisine
entity and the Country
embeddable types are going to be instantiated as Java dynamic proxies,
as you can see in the following DynamicInstantiator
example:
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.
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.
@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 your own EntityPersister
and CollectionPersister
implementations,
you can control how entities and collections are persisted into 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.
When placed on the identifier getter, Hibernate will use property-based access.
To avoid issues such as HCANN-63 - Property name beginning with at least two uppercase characters has odd functionality in HQL, you should pay attention to Java Bean specification in regard to naming properties. |
Embeddable types inherit the access strategy from their parent entities.
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 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
@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.
@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:
@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:
@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:
@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.
@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
@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 thejavax.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" (e.g. Hibernate does allow composite identifiers to be defined without a "primary key class" via multiple |
The attributes making up the composition can be either basic, composite, @ManyToOne
.
Note especially that collection and one-to-one 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.
@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:
@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 However, that is not portably supported by the JPA specification. In JPA terms, one would use "derived identifiers". For more 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".
@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).
@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.
@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 which allows auto-generated values in composite identifiers 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.
@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.
Book book = entityManager.find( Book.class, new Book(
author,
publisher,
"High-Performance Java Persistence"
) );
assertEquals( "Vlad Mihalcea", book.getAuthor().getName() );
2.6.6. Composite identifiers with generated properties
When using composite identifiers, the underlying identifier properties must be manually assigned by the user.
Automatically generated properties are not supported to be used to generate the value of an underlying property that makes the composite identifier.
Therefore, you cannot use any of the automatic property generator described by the generated properties section like @Generated
, @CreationTimestamp
or @ValueGenerationType
or database-generated values.
Nevertheless, you can still generate the identifier properties prior to constructing the composite identifier, as illustrated by the following examples.
Assuming we have the following EventId
composite identifier and an Event
entity which uses the aforementioned composite identifier.
@Entity
class Event {
@Id
private EventId id;
@Column(name = "event_key")
private String key;
@Column(name = "event_value")
private String value;
//Getters and setters are omitted for brevity
}
@Embeddable
class EventId implements Serializable {
private Integer category;
private Timestamp createdOn;
//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;
}
EventId that = (EventId) o;
return Objects.equals( category, that.category ) &&
Objects.equals( createdOn, that.createdOn );
}
@Override
public int hashCode() {
return Objects.hash( category, createdOn );
}
}
In-memory generated composite identifier properties
If you want to generate the composite identifier properties in-memory, you need to do that as follows:
EventId id = new EventId();
id.setCategory( 1 );
id.setCreatedOn( new Timestamp( System.currentTimeMillis() ) );
Event event = new Event();
event.setId( id );
event.setKey( "Temperature" );
event.setValue( "9" );
entityManager.persist( event );
Notice that the createdOn
property of the EventId
composite identifier was generated by the data access code and assigned to the
identifier prior to persisting the Event
entity.
Database generated composite identifier properties
If you want to generate the composite identifier properties using a database function or stored procedure, you could to do it as illustrated by the following example.
Timestamp currentTimestamp = (Timestamp) entityManager
.createNativeQuery(
"SELECT CURRENT_TIMESTAMP" )
.getSingleResult();
EventId id = new EventId();
id.setCategory( 1 );
id.setCreatedOn( currentTimestamp );
Event event = new Event();
event.setId( id );
event.setKey( "Temperature" );
event.setValue( "9" );
entityManager.persist( event );
Notice that the createdOn
property of the EventId
composite identifier was generated by calling the CURRENT_TIMESTAMP
database function,
and we assigned it to the composite identifier prior to persisting the Event
entity.
2.6.7. Generated identifier values
You can also auto-generate values for non-identifier attributes. For more details, see the Generated properties section. |
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 indicated 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 Starting with Hibernate 5, this is set to |
In Hibernate 5.3, Hibernate attempts to delay the insert of entities if the flush-mode does not equal In Hibernate 5.4, Hibernate attempts to remedy the problem using an algorithm to decide if the insert should be delayed or if it requires immediate insertion. We wanted to restore the behavior prior to 5.3 only for very specific use cases where it made sense. Entity mappings can sometimes be complex and it is possible a corner case was overlooked. Hibernate offers a
way to completely disable the 5.3 behavior in the event problems occur with This configuration option is meant to act as a temporary fix and bridge the gap between the changes in this behavior across Hibernate 5.x releases. If this configuration setting is necessary for a mapping, please open a JIRA and report the mapping so that the algorithm can be reviewed. |
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 the table identifier generator.
2.6.8. 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 a 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 toSequenceStyleGenerator
. 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 mapsAUTO
to thenative
generator strategy which uses the Dialect#getNativeIdentifierGeneratorStrategy to resolve the actual identifier generator (e.g.identity
orsequence
).
2.6.9. 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.
@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.
@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
annotation allows you to specify additional configurations as well.
@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.10. 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 be 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 using IDENTITY columns 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 (long conversations). Because of the runtime imposition/inconsistency, Hibernate suggests other forms of identifier value generation be used (e.g. SEQUENCE). |
There is yet another important runtime impact of choosing IDENTITY generation: Hibernate will not be able to batch INSERT statements for the entities using the 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 entity type using the IDENTITY generator, then this limitation will be less important since batching would not have been very helpful anyway. |
2.6.11. Using the 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.
@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.
@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:
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.12. 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).
@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
.
@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.13. 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-than-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 |
2.6.14. 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:
@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:
@GenericGenerator
mappingfor ( 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 with just 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.15. 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.
@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.
@MapsId
persist exampledoInJPA( 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
.
@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 |
2.6.16. @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.
@RowId
entity mapping@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.
@RowId
exampleProduct 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.
@ManyToOne
association@Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue
private Long id;
//Getters and setters are omitted for brevity
}
@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;
//Getters and setters are omitted for brevity
}
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.
@ManyToOne
association lifecyclePerson 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.
@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<>();
//Getters and setters are omitted for brevity
}
@Entity(name = "Phone")
public static class Phone {
@Id
@GeneratedValue
private Long id;
@Column(name = "`number`")
private String number;
//Getters and setters are omitted for brevity
}
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
associationPerson 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 the example above, upon flushing the persistence context, Hibernate deletes all database rows from the link table (e.g. On the other hand, a bidirectional |
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.
@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<>();
//Getters and setters are omitted for brevity
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;
//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;
}
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 |
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.
@OneToMany
with an owner @ManyToOne
side lifecyclePerson 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 Person
( id )
VALUES ( 1 )
INSERT INTO Phone
( "number", person_id, id )
VALUES ( '123-456-7890', 1, 2 )
INSERT INTO Phone
( "number", person_id, id )
VALUES ( '321-654-0987', 1, 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 orphanRemoval
attribute and dissociating 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
@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;
//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;
//Getters and setters are omitted for brevity
}
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 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
@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;
//Getters and setters are omitted for brevity
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;
//Getters and setters are omitted for brevity
}
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.
@OneToOne
lifecyclePhone 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.
@OneToOne
unique constraintPhoneDetails 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.
@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;
//Getters and setters are omitted for brevity
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;
//Getters and setters are omitted for brevity
}
For more about how to enable Bytecode enhancement, see the Bytecode Enhancement chapter.
2.7.4. @ManyToMany
The @ManyToMany
association requires a link table that joins two entities.
Like the @OneToMany
association, @ManyToMany
can be either unidirectional or bidirectional.
Unidirectional @ManyToMany
@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<>();
//Getters and setters are omitted for brevity
}
@Entity(name = "Address")
public static class Address {
@Id
@GeneratedValue
private Long id;
private String street;
@Column(name = "`number`")
private String number;
//Getters and setters are omitted for brevity
}
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.
@ManyToMany
lifecyclePerson 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 For example, if
|
By simply removing the parent-side, Hibernate can safely remove the associated link records as you can see in the following example:
@ManyToMany
entity removalPerson 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.
@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<>();
//Getters and setters are omitted for brevity
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<>();
//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;
}
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:
@ManyToMany
lifecyclePerson 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.
Bidirectional many-to-many with a link entity
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.
@Entity(name = "Person")
public static class Person implements Serializable {
@Id
@GeneratedValue
private Long id;
@NaturalId
private String registrationNumber;
@OneToMany(
mappedBy = "person",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PersonAddress> addresses = new ArrayList<>();
//Getters and setters are omitted for brevity
public void addAddress(Address address) {
PersonAddress personAddress = new PersonAddress( this, address );
addresses.add( personAddress );
address.getOwners().add( personAddress );
}
public void removeAddress(Address address) {
PersonAddress personAddress = new PersonAddress( this, address );
address.getOwners().remove( personAddress );
addresses.remove( personAddress );
personAddress.setPerson( null );
personAddress.setAddress( null );
}
@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 = "PersonAddress")
public static class PersonAddress implements Serializable {
@Id
@ManyToOne
private Person person;
@Id
@ManyToOne
private Address address;
//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;
}
PersonAddress that = (PersonAddress) o;
return Objects.equals( person, that.person ) &&
Objects.equals( address, that.address );
}
@Override
public int hashCode() {
return Objects.hash( person, address );
}
}
@Entity(name = "Address")
public static class Address implements Serializable {
@Id
@GeneratedValue
private Long id;
private String street;
@Column(name = "`number`")
private String number;
private String postalCode;
@OneToMany(
mappedBy = "address",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PersonAddress> owners = new ArrayList<>();
//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;
}
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 PersonAddress (
person_id BIGINT NOT NULL ,
address_id BIGINT NOT NULL ,
PRIMARY KEY ( person_id, address_id )
)
ALTER TABLE Person
ADD CONSTRAINT UK_23enodonj49jm8uwec4i7y37f
UNIQUE (registrationNumber)
ALTER TABLE PersonAddress
ADD CONSTRAINT FK8b3lru5fyej1aarjflamwghqq
FOREIGN KEY (person_id) REFERENCES Person
ALTER TABLE PersonAddress
ADD CONSTRAINT FK7p69mgialumhegyl4byrh65jk
FOREIGN KEY (address_id) REFERENCES Address
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 For more details, see the composite identifiers with associations section. |
The entity state transitions are better managed than in the previous bidirectional @ManyToMany
case.
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" );
entityManager.persist( person1 );
entityManager.persist( person2 );
entityManager.persist( address1 );
entityManager.persist( address2 );
person1.addAddress( address1 );
person1.addAddress( address2 );
person2.addAddress( address1 );
entityManager.flush();
log.info( "Removing address" );
person1.removeAddress( address1 );
INSERT INTO Person ( registrationNumber, id )
VALUES ( 'ABC-123', 1 )
INSERT INTO Person ( registrationNumber, id )
VALUES ( 'DEF-456', 2 )
INSERT INTO Address ( number, postalCode, street, id )
VALUES ( '12A', '4005A', '12th Avenue', 3 )
INSERT INTO Address ( number, postalCode, street, id )
VALUES ( '18B', '4007B', '18th Avenue', 4 )
INSERT INTO PersonAddress ( person_id, address_id )
VALUES ( 1, 3 )
INSERT INTO PersonAddress ( person_id, address_id )
VALUES ( 1, 4 )
INSERT INTO PersonAddress ( person_id, address_id )
VALUES ( 2, 3 )
DELETE FROM PersonAddress
WHERE person_id = 1 AND address_id = 3
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
.
The |
Considering the following City
and Person
entity mappings:
@NotFound
mapping example@Entity
@Table( name = "Person" )
public static class Person {
@Id
private Long id;
private String name;
private String cityName;
@ManyToOne
@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:
@NotFound
persist exampleCity _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:
@NotFound
find existing entity examplePerson person = entityManager.find( Person.class, 1L );
assertEquals( "New York", person.getCity().getName() );
However, if we change the cityName
attribute to a non-existing city’s name:
@NotFound
change to non-existing City exampleperson.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:
@NotFound
find non-existing City examplePerson person = entityManager.find( Person.class, 1L );
assertEquals( "Atlantis", person.getCityName() );
assertNull( null, person.getCity() );
2.7.6. @Any
mapping
The @Any
mapping is useful to emulate a unidirectional @ManyToOne
association when there can be multiple target entities.
Because the @Any
mapping defines a polymorphic association to classes from multiple tables,
this association type requires the FK column which provides the associated parent identifier and
a metadata information for the associated entity type.
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:
Property
class hierarchypublic 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;
@Override
public String getName() {
return name;
}
@Override
public Integer getValue() {
return value;
}
//Getters and setters omitted for brevity
}
@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;
@Override
public String getName() {
return name;
}
@Override
public String getValue() {
return value;
}
//Getters and setters omitted for brevity
}
A PropertyHolder
can reference any such property, and, because each Property
belongs to a separate table, the @Any
annotation is, therefore, required.
@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.
The package-info.java
contains the @AnyMetaDef
mapping:
@AnyMetaDef
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.associations.any;
import org.hibernate.annotations.AnyMetaDef;
import org.hibernate.annotations.MetaValue;
Although the |
To see the @Any
annotation in action, consider the next examples.
If we persist an IntegerProperty
as well as a StringProperty
entity, and associate
the StringProperty
entity with a PropertyHolder
,
Hibernate will generate the following SQL queries:
@Any
mapping persist exampleIntegerProperty ageProperty = new IntegerProperty();
ageProperty.setId( 1L );
ageProperty.setName( "age" );
ageProperty.setValue( 23 );
session.persist( ageProperty );
StringProperty nameProperty = new StringProperty();
nameProperty.setId( 1L );
nameProperty.setName( "name" );
nameProperty.setValue( "John Doe" );
session.persist( nameProperty );
PropertyHolder namePropertyHolder = new PropertyHolder();
namePropertyHolder.setId( 1L );
namePropertyHolder.setProperty( nameProperty );
session.persist( namePropertyHolder );
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 )
When fetching the PropertyHolder
entity and navigating its property
association,
Hibernate will fetch the associated StringProperty
entity like this:
@Any
mapping query examplePropertyHolder propertyHolder = session.get( PropertyHolder.class, 1L );
assertEquals("name", propertyHolder.getProperty().getName());
assertEquals("John Doe", propertyHolder.getProperty().getValue());
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
While 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.
@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 the @ManyToAny
annotation in action, consider the next examples.
If we persist an IntegerProperty
as well as a StringProperty
entity,
and associate both of them with a PropertyRepository
parent entity,
Hibernate will generate the following SQL queries:
@ManyToAny
mapping persist exampleIntegerProperty ageProperty = new IntegerProperty();
ageProperty.setId( 1L );
ageProperty.setName( "age" );
ageProperty.setValue( 23 );
session.persist( ageProperty );
StringProperty nameProperty = new StringProperty();
nameProperty.setId( 1L );
nameProperty.setName( "name" );
nameProperty.setValue( "John Doe" );
session.persist( nameProperty );
PropertyRepository propertyRepository = new PropertyRepository();
propertyRepository.setId( 1L );
propertyRepository.getProperties().add( ageProperty );
propertyRepository.getProperties().add( nameProperty );
session.persist( propertyRepository );
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 )
When fetching the PropertyRepository
entity and navigating its properties
association,
Hibernate will fetch the associated IntegerProperty
and StringProperty
entities like this:
@ManyToAny
mapping query examplePropertyRepository propertyRepository = session.get( PropertyRepository.class, 1L );
assertEquals(2, propertyRepository.getProperties().size());
for(Property property : propertyRepository.getProperties()) {
assertNotNull( property.getValue() );
}
SELECT pr.id AS id1_1_0_
FROM property_repository pr
WHERE pr.id = 1
SELECT ip.id AS id1_0_0_ ,
ip."name" AS name2_0_0_ ,
ip."value" AS value3_0_0_
FROM integer_property ip
WHERE ip.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.7.7. @JoinFormula
mapping
The @JoinFormula
annotation is used to customize the join between a child Foreign Key and a parent row Primary Key.
@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;
//Getters and setters, equals and hashCode methods omitted for brevity
}
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:
@JoinFormula
mapping usageCountry 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:
@JoinFormula
mapping usagedoInJPA( 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.7.8. @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
.
@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;
//Getters and setters, equals and hashCode methods omitted for brevity
}
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:
@JoinColumnOrFormula
persist exampleCountry 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:
@JoinColumnOrFormula
fetching exampledoInJPA( 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.8. Collections
Naturally Hibernate also allows persisting collections. These persistent collections can contain almost any other Hibernate type, including basic types, custom types, embeddables, 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 lifecycle being fully dependant on the collection owner), or it might be a reference to another entity with its own lifecycle. 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.
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@ElementCollection
private List<String> phones = new ArrayList<>();
//Getters and setters are omitted for brevity
}
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 to basic types since 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.
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.
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.
@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 |
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.
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@ElementCollection
private List<Phone> phones = new ArrayList<>();
//Getters and setters are omitted for brevity
}
@Embeddable
public static class Phone {
private String type;
@Column(name = "`number`")
private String number;
//Getters and setters are omitted for brevity
}
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:
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@OneToMany(cascade = CascadeType.ALL)
private List<Phone> phones = new ArrayList<>();
//Getters and setters are omitted for brevity
}
@Entity(name = "Phone")
public static class Phone {
@Id
private Long id;
private String type;
@Column(name = "`number`")
private String number;
//Getters and setters are omitted for brevity
}
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. The cascading mechanism allows you to 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.
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 deletes all link table rows associated with the parent entity and re-adds the remaining ones that are found in 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.
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
private List<Phone> phones = new ArrayList<>();
//Getters and setters are omitted for brevity
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;
//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;
}
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
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
@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 dissociating 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:
@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<>();
//Getters and setters are omitted for brevity
}
@Entity(name = "Phone")
public static class Phone {
@Id
private Long id;
private String type;
@Column(name = "`number`")
private String number;
//Getters and setters are omitted for brevity
}
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:
@OrderBy
list select statementSELECT
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 If no property is specified (e.g. |
Another ordering option is to use the @OrderColumn
annotation:
@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:
@OrderColumn
list select statementselect
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
.
@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:
@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.
@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.
@ListIndexBase
persist examplePerson 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
annotation uses the CHAR_LENGTH
SQL function to order the Article
entities
by the number of characters of the name
attribute.
@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;
//Getters and setters are omitted for brevity
}
When fetching the articles
collection, Hibernate uses the ORDER BY SQL clause provided by the mapping:
@OrderBy
fetching examplePerson 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:
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@OneToMany(cascade = CascadeType.ALL)
private Set<Phone> phones = new HashSet<>();
//Getters and setters are omitted for brevity
}
@Entity(name = "Phone")
public static class Phone {
@Id
private Long id;
private String type;
@NaturalId
@Column(name = "`number`")
private String number;
//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;
}
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 than 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.
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
private Set<Phone> phones = new HashSet<>();
//Getters and setters are omitted for brevity
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;
//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;
}
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.
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@OneToMany(cascade = CascadeType.ALL)
@SortNatural
private SortedSet<Phone> phones = new TreeSet<>();
//Getters and setters are omitted for brevity
}
@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 );
}
}
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:
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@OneToMany(cascade = CascadeType.ALL)
@SortComparator(ReverseComparator.class)
private SortedSet<Phone> phones = new TreeSet<>();
//Getters and setters are omitted for brevity
}
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;
//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 );
}
}
Bidirectional sorted sets
The @SortNatural
and @SortComparator
work the same for bidirectional sorted sets too:
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
@SortNatural
private SortedSet<Phone> phones = new TreeSet<>();
@SortComparator(ReverseComparator.class)
private SortedSet<Phone> phones = new TreeSet<>();
2.8.8. Maps
A java.util.Map
is a ternary association because it requires 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 aCalendar
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.
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")
private Map<Phone, Date> phoneRegister = new HashMap<>();
//Getters and setters are omitted for brevity
}
@Embeddable
public static class Phone {
private PhoneType type;
@Column(name = "`number`")
private String number;
//Getters and setters are omitted for brevity
}
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:
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_timestamp_epoch)
)
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 the Unix epoch.
The |
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:
@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<>();
//Getters and setters are omitted for brevity
}
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 not 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:
PhoneNumber
interface and the MobilePhone
class typepublic 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.
@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:
@MapKeyClass
persist examplePerson 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:
@MapKeyClass
fetch examplePerson 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 |
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<>();
//Getters and setters are omitted for brevity
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;
//Getters and setters are omitted for brevity
}
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.
@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<>();
//Getters and setters are omitted for brevity
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;
//Getters and setters are omitted for brevity
}
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
.
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
private String[] phones;
//Getters and setters are omitted for brevity
}
CREATE TABLE Person (
id BIGINT NOT NULL ,
phones VARBINARY(255) ,
PRIMARY KEY ( id )
)
If you want to map arrays such as 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.
@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.
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:
@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
However, the custom collection type can still be customized as long as the base type is one of the aforementioned persistent types. |
This way, the Phone
collection can be used as a java.util.Queue
:
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 one or more persistent attributes.
@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
}
@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 );
}
}
@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 associated 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. |
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 applies 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 Book
entities in the first two examples define "simple" natural ids, we can load them as follows:
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.
@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 you are certain that none of the mutable natural ids already associated with the current |
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.
@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 and 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 Map
s 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.
<!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:
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:
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 the quick turnaround time for prototyping without the need for entity class implementation. The main downfall 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 the 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.
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.
@MappedSuperclass
inheritance@MappedSuperclass
public static class Account {
@Id
private Long id;
private String owner;
private BigDecimal balance;
private BigDecimal interestRate;
//Getters and setters are omitted for brevity
}
@Entity(name = "DebitAccount")
public static class DebitAccount extends Account {
private BigDecimal overdraftFee;
//Getters and setters are omitted for brevity
}
@Entity(name = "CreditAccount")
public static class CreditAccount extends Account {
private BigDecimal creditLimit;
//Getters and setters are omitted for brevity
}
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 |
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. |
@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;
//Getters and setters are omitted for brevity
}
@Entity(name = "DebitAccount")
public static class DebitAccount extends Account {
private BigDecimal overdraftFee;
//Getters and setters are omitted for brevity
}
@Entity(name = "CreditAccount")
public static class CreditAccount extends Account {
private BigDecimal creditLimit;
//Getters and setters are omitted for brevity
}
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.
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.
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 |
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
The The second option, |
There used to be a |
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:
@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;
//Getters and setters are omitted for brevity
}
@Entity(name = "DebitAccount")
@DiscriminatorValue(value = "Debit")
public static class DebitAccount extends Account {
private String debitKey;
private BigDecimal overdraftFee;
//Getters and setters are omitted for brevity
}
@Entity(name = "CreditAccount")
@DiscriminatorValue(value = "Credit")
public static class CreditAccount extends Account {
private String creditKey;
private BigDecimal creditLimit;
//Getters and setters are omitted for brevity
}
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:
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;
//Getters and setters are omitted for brevity
}
@Entity(name = "DebitAccount")
@DiscriminatorValue( "Debit" )
public static class DebitAccount extends Account {
private BigDecimal overdraftFee;
//Getters and setters are omitted for brevity
}
@Entity(name = "CreditAccount")
@DiscriminatorValue( "Credit" )
public static class CreditAccount extends Account {
private BigDecimal creditLimit;
//Getters and setters are omitted for brevity
}
@Entity(name = "OtherAccount")
@DiscriminatorValue( "not null" )
public static class OtherAccount extends Account {
private boolean active;
//Getters and setters are omitted for brevity
}
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:
null
and not-null
entity persistenceDebitAccount 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.
@Entity(name = "Account")
@Inheritance(strategy = InheritanceType.JOINED)
public static class Account {
@Id
private Long id;
private String owner;
private BigDecimal balance;
private BigDecimal interestRate;
//Getters and setters are omitted for brevity
}
@Entity(name = "DebitAccount")
public static class DebitAccount extends Account {
private BigDecimal overdraftFee;
//Getters and setters are omitted for brevity
}
@Entity(name = "CreditAccount")
public static class CreditAccount extends Account {
private BigDecimal creditLimit;
//Getters and setters are omitted for brevity
}
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 keys of the The table name still defaults to the non-qualified class name.
Also, if |
@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;
//Getters and setters are omitted for brevity
}
@Entity(name = "DebitAccount")
@PrimaryKeyJoinColumn(name = "account_id")
public static class DebitAccount extends Account {
private BigDecimal overdraftFee;
//Getters and setters are omitted for brevity
}
@Entity(name = "CreditAccount")
@PrimaryKeyJoinColumn(name = "account_id")
public static class CreditAccount extends Account {
private BigDecimal creditLimit;
//Getters and setters are omitted for brevity
}
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.
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 to use polymorphic associations (e.g. an association to the superclass of your hierarchy), you need to use the union subclass mapping.
@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;
//Getters and setters are omitted for brevity
}
@Entity(name = "DebitAccount")
public static class DebitAccount extends Account {
private BigDecimal overdraftFee;
//Getters and setters are omitted for brevity
}
@Entity(name = "CreditAccount")
public static class CreditAccount extends Account {
private BigDecimal creditLimit;
//Getters and setters are omitted for brevity
}
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.
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. Unfortunately, not all database systems support UNION ALL, in which case, UNION is going to be used instead of UNION ALL. The following Hibernate dialects support UNION ALL:
|
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:
public interface DomainModelEntity<ID> {
ID getId();
Integer getVersion();
}
If we have two entity mappings, a Book
and a Blog
,
and the Blog
entity is mapped with the
@Polymorphism
annotation
and taking the PolymorphismType.EXPLICIT
setting:
@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:
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):
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.
@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:
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.
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:
@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.
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:
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:
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" );
}
javax.persistence.RollbackException: Error while committing the transaction
Caused by: javax.persistence.PersistenceException: org.hibernate.HibernateException:
Caused by: org.hibernate.HibernateException: changed an immutable collection instance: [
org.hibernate.userguide.immutability.CollectionImmutabilityTest$Batch.events#1
]
While immutable entity changes are simply discarded, modifying an immutable collection will end up in a |
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.
During the bootstrap process, you might want to customize Hibernate behavior so make sure you check the Configurations section as well. |
3.1. Native Bootstrapping
This section discusses the process of bootstrapping a Hibernate SessionFactory
.
Specifically, it addresses the bootstrapping APIs as redesigned in 5.0.
For a discussion of the legacy bootstrapping API, see Legacy Bootstrapping.
3.1.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 controls 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 |
If you wish to alter how the BootstrapServiceRegistry
is built, that is controlled through the org.hibernate.boot.registry.BootstrapServiceRegistryBuilder
:
BootstrapServiceRegistry
buildingBootstrapServiceRegistryBuilder 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 |
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
:
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:
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.1.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.
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.1.3. Building the Metadata
The second step in native bootstrapping is the building of an 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::
MetadataSources
with method chainingServiceRegistry 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 |
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.
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.1.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.
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:
-
Build the
StandardServiceRegistry
-
Build the
Metadata
-
Use those 2 to build the
SessionFactory
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 a 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();
3.2. 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.2.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.
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:
EntityManagerFactory
@PersistenceUnit(
unitName = "CRM"
)
private EntityManagerFactory entityManagerFactory;
The META-INF/persistence.xml
file looks as follows:
<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:
EntityManagerFactory
// Create an EMF for our CRM persistence-unit.
EntityManagerFactory emf = Persistence.createEntityManagerFactory( "CRM" );
If you don’t want to provide a |
To inject the default Persistence Context, you can use the @PersistenceContext
annotation.
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.
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 |
3.2.2. Externalizing XML mapping files
JPA offers two mapping options:
-
annotations
-
XML mappings
Although annotations are much more common, there are projects where 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 take precedence over its annotation counterpart.
The JPA specification requires the XML mappings to be located on the classpath:
An object/relational mapping XML file named
orm.xml
may be specified in theMETA-INF
directory in the root of the persistence unit or in theMETA-INF
directory of any jar file referenced by thepersistence.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 classpath.
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 classpath.
Hibernate is more lenient in this regard so you can use any external location even outside of the application configured classpath.
<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.3. Configuring the SessionFactory
Metadata
via the JPA bootstrap
As previously seen, the Hibernate native bootstrap mechanism allows you to customize a great variety of configurations which are passed via the Metadata
object.
When using Hibernate as a JPA provider, the EntityManagerFactory
is backed by a SessionFactory
. For this reason, you might still want to use the Metadata
object to pass various settings which cannot be supplied via the standard Hibernate configuration settings.
For this reason, you can use the
MetadataBuilderContributor
class as you can see in the following examples.
MetadataBuilderContributor
public class SqlFunctionMetadataBuilderContributor
implements MetadataBuilderContributor {
@Override
public void contribute(MetadataBuilder metadataBuilder) {
metadataBuilder.applySqlFunction(
"instr", new StandardSQLFunction( "instr", StandardBasicTypes.STRING )
);
}
}
The above MetadataBuilderContributor
is used to register a SqlFuction
which is not defined by the currently running Hibernate Dialect
, but which we need to reference in our JPQL queries.
By having access to the
MetadataBuilder
class that’s used by the underlying SessionFactory
, the JPA bootstrap becomes just as flexible as the Hibernate native bootstrap mechanism.
You can then pass the custom MetadataBuilderContributor
via the hibernate.metadata_builder_contributor
configuration property as explained in the Configuration chapter.
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:
@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;
//Getters and setters are omitted for brevity
}
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
private String name;
@OneToMany(mappedBy = "author")
private List<Book> books = new ArrayList<>();
//Getters and setters are omitted for brevity
}
@Entity(name = "Book")
public static class Book {
@Id
private Long id;
private String title;
@NaturalId
private String isbn;
@ManyToOne
private Person author;
//Getters and setters are omitted for brevity
}
If the hibernate.hbm2ddl.auto
configuration is set to create
, Hibernate is going to generate the following 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:
create sequence book_sequence start with 1 increment by 1
If we configure Hibernate to import the script above:
<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:
<?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:
@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.
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 a database column
With Hibernate, you can specify a default value for a given database column using the @ColumnDefault
annotation.
@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 |
This way, when omitting the name
and the clientId
attribute, the database is going to set them according to their default values.
@ColumnDefault
mapping exampledoInJPA( 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:
@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.
@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:
@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 Context
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
orpersistent
-
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 among 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.
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 alternative 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 the 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.
@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;
//Getters and setters are omitted for brevity
}
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 the image
attribute, 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 |
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 the flush time, Hibernate asks your entity what has changed rather than 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:
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
private String name;
@OneToMany(mappedBy = "author")
private List<Book> books = new ArrayList<>();
//Getters and setters are omitted for brevity
}
@Entity(name = "Book")
public static class Book {
@Id
private Long id;
private String title;
@NaturalId
private String isbn;
@ManyToOne
private Person author;
//Getters and setters are omitted for brevity
}
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:
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.
5.2.2. Performing enhancement
Runtime enhancement
Currently, runtime 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 runtime enhancement, specify one of the following configuration properties:
hibernate.enhancer.enableDirtyTracking
(e.g.true
orfalse
(default value))-
Enable dirty tracking feature in runtime bytecode enhancement.
hibernate.enhancer.enableLazyInitialization
(e.g.true
orfalse
(default value))-
Enable lazy loading feature in runtime bytecode enhancement. This way, even basic types (e.g.
@Basic(fetch = FetchType.LAZY
)) can be fetched lazily. hibernate.enhancer.enableAssociationManagement
(e.g.true
orfalse
(default value))-
Enable association management feature in runtime bytecode enhancement which automatically synchronizes a bidirectional association when only one side is changed.
Also, at the moment, only annotated classes support runtime 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:
apply plugin: 'org.hibernate.orm'
ext {
hibernateVersion = 'hibernate-version-you-want'
}
buildscript {
dependencies {
classpath "org.hibernate:hibernate-gradle-plugin:$hibernateVersion"
}
}
hibernate {
enhance {
enableLazyInitialization = true
enableDirtyTracking = true
enableAssociationManagement = true
}
}
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. The default behavior is to fail the build, but it can be set so that only a warning is issued.
<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 an org.hibernate.Session
or a javax.persistence.EntityManager
.
Person person = new Person();
person.setId( 1L );
person.setName("John Doe");
entityManager.persist( person );
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 semantics 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.
entityManager.remove( person );
session.delete( person );
Hibernate itself can handle deleting entities in detached state. JPA, however, disallows this behavior. The implication here is that the entity instance passed to the |
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.
Book book = new Book();
book.setAuthor( entityManager.getReference( Person.class, personId ) );
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.
Unless the entity class is declared |
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).
Person person = entityManager.find( Person.class, personId );
Person person = session.get( Person.class, personId );
byId()
Hibernate APIPerson 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:
byId()
Hibernate APIOptional<Person> optionalPerson = session.byId( Person.class ).loadOptional( personId );
5.7. Obtain multiple entities by their identifiers
If you want to load multiple entities by providing their identifiers, calling the EntityManager#find
method multiple times is not only inconvenient,
but also inefficient.
While the JPA standard does not support retrieving multiple entities at once, other than running a JPQL or Criteria API query,
Hibernate offers this functionality via the
byMultipleIds
method of the Hibernate Session
.
The byMultipleIds
method returns a
MultiIdentifierLoadAccess
which you can use to customize the multi-load request.
The MultiIdentifierLoadAccess
interface provides several methods which you can use to
change the behavior of the multi-load call:
enableOrderedReturn(boolean enabled)
-
This setting controls whether the returned
List
is ordered and positional in relation to the incoming ids. If enabled (the default), the returnList
is ordered and positional relative to the incoming ids. In other words, a request tomultiLoad([2,1,3])
will return[Entity#2, Entity#1, Entity#3]
.An important distinction is made here in regards to the handling of unknown entities depending on this "ordered return" setting. If enabled, a null is inserted into the
List
at the proper position(s). If disabled, the nulls are not put into the return List.In other words, consumers of the returned ordered List would need to be able to handle null elements.
enableSessionCheck(boolean enabled)
-
This setting, which is disabled by default, tells Hibernate to check the first-level cache (a.k.a
Session
or Persistence Context) first and, if the entity is found and already managed by the HibernateSession
, the cached entity will be added to the returnedList
, therefore skipping it from being fetched via the multi-load query. enableReturnOfDeletedEntities(boolean enabled)
-
This setting instructs Hibernate if the multi-load operation is allowed to return entities that were deleted by the current Persistence Context. A deleted entity is one which has been passed to this
Session.delete
orSession.remove
method, but theSession
was not flushed yet, meaning that the associated row was not deleted in the database table.The default behavior is to handle them as null in the return (see
enableOrderedReturn
). When enabled, the result set will contain deleted entities. When disabled (which is the default behavior), deleted entities are not included in the returningList
. with(LockOptions lockOptions)
-
This setting allows you to pass a given
LockOptions
mode to the multi-load query. with(CacheMode cacheMode)
-
This setting allows you to pass a given
CacheMode
strategy so that we can load entities from the second-level cache, therefore skipping the cached entities from being fetched via the multi-load query. withBatchSize(int batchSize)
-
This setting allows you to specify a batch size for loading the entities (e.g. how many at a time).
The default is to use a batch sizing strategy defined by the
Dialect.getDefaultBatchLoadSizingStrategy()
method.Any greater-than-one value here will override that default behavior.
with(RootGraph<T> graph)
-
The
RootGraph
is a Hibernate extension to the JPAEntityGraph
contract, and this method allows you to pass a specificRootGraph
to the multi-load query so that it can fetch additional relationships of the current loading entity.
Now, assuming we have 3 Person
entities in the database, we can load all of them with a single call
as illustrated by the following example:
byMultipleIds()
Hibernate APISession session = entityManager.unwrap( Session.class );
List<Person> persons = session
.byMultipleIds( Person.class )
.multiLoad( 1L, 2L, 3L );
assertEquals( 3, persons.size() );
List<Person> samePersons = session
.byMultipleIds( Person.class )
.enableSessionCheck( true )
.multiLoad( 1L, 2L, 3L );
assertEquals( persons, samePersons );
SELECT p.id AS id1_0_0_,
p.name AS name2_0_0_
FROM Person p
WHERE p.id IN ( 1, 2, 3 )
Notice that only one SQL SELECT statement was executed since the second call uses the
enableSessionCheck
method of the MultiIdentifierLoadAccess
to instruct Hibernate to skip entities that are already loaded in the current Persistence Context.
If the entities are not available in the current Persistence Context but they could be loaded from the second-level cache, you can use the
with(CacheMode)
method of the MultiIdentifierLoadAccess
object.
SessionFactory sessionFactory = entityManagerFactory().unwrap( SessionFactory.class );
Statistics statistics = sessionFactory.getStatistics();
sessionFactory.getCache().evictAll();
statistics.clear();
sqlStatementInterceptor.clear();
assertEquals( 0, statistics.getQueryExecutionCount() );
doInJPA( this::entityManagerFactory, entityManager -> {
Session session = entityManager.unwrap( Session.class );
List<Person> persons = session
.byMultipleIds( Person.class )
.multiLoad( 1L, 2L, 3L );
assertEquals( 3, persons.size() );
} );
assertEquals( 0, statistics.getSecondLevelCacheHitCount() );
assertEquals( 3, statistics.getSecondLevelCachePutCount() );
assertEquals( 1, sqlStatementInterceptor.getSqlQueries().size() );
doInJPA( this::entityManagerFactory, entityManager -> {
Session session = entityManager.unwrap( Session.class );
sqlStatementInterceptor.clear();
List<Person> persons = session.byMultipleIds( Person.class )
.with( CacheMode.NORMAL )
.multiLoad( 1L, 2L, 3L );
assertEquals( 3, persons.size() );
} );
assertEquals( 3, statistics.getSecondLevelCacheHitCount() );
assertEquals( 0, sqlStatementInterceptor.getSqlQueries().size() );
In the example above, we first make sure that we clear the second-level cache to demonstrate that the multi-load query will put the returning entities into the second-level cache.
After executing the first byMultipleIds
call, Hibernate is going to fetch the requested entities,
and as illustrated by the getSecondLevelCachePutCount
method call, 3 entities were indeed added to the
shared cache.
Afterward, when executing the second byMultipleIds
call for the same entities in a new Hibernate Session
,
we set the
CacheMode.NORMAL
second-level cache mode so that entities are going to be returned from the second-level cache.
The getSecondLevelCacheHitCount
statistics method returns 3 this time, since the 3 entities were loaded from the second-level cache, and, as illustrated by sqlStatementInterceptor.getSqlQueries()
, no multi-load SELECT statement was executed this time.
5.8. Obtain an entity by natural-id
In addition to allowing to load the entity by its identifier, Hibernate allows applications to load entities by the declared natural identifier.
@Entity(name = "Book")
public static class Book {
@Id
private Long id;
private String title;
@NaturalId
private String isbn;
@ManyToOne
private Person author;
//Getters and setters are omitted for brevity
}
We can also opt to fetch the entity or just retrieve a reference to it when using the natural identifier loading methods.
Book book = session.bySimpleNaturalId( Book.class ).getReference( isbn );
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:
Optional<Book> optionalBook = session
.byNaturalId( Book.class )
.using( "isbn", isbn )
.loadOptional( );
Hibernate offers 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 defines an overloading variant accepting a org.hibernate.LockOptions
argument.
Locking is discussed in a separate chapter.
5.9. Filtering entities and associations
Hibernate offers two options if you want to filter entities or entity associations:
- static (e.g.
@Where
and@WhereJoinTable
) -
which are defined at mapping time and cannot change at runtime.
- dynamic (e.g.
@Filter
and@FilterJoinTable
) -
which are applied and configured at runtime.
5.9.1. @Where
Sometimes, you want to filter out entities or collections using custom SQL criteria.
This can be achieved using the @Where
annotation, which can be applied to entities and collections.
@Where
mapping usagepublic 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:
@Where
mappingdoInJPA( 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.
@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.
@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
5.9.2. @WhereJoinTable
Just like @Where
annotation, @WhereJoinTable
is used to filter out collections using a joined table (e.g. @ManyToMany association).
@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:
@WhereJoinTable
test dataBook 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 only one entry:
@WhereJoinTable
fetch exampleBook book = entityManager.find( Book.class, 1L );
assertEquals( 1, book.getCurrentWeekReaders().size() );
5.9.3. @Filter
The @Filter
annotation is another way to filter out entities or collections using custom SQL criteria.
Unlike the @Where
annotation, @Filter
allows you to parameterize the filter clause at runtime.
Now, considering we have the following Account
entity:
@Filter
mapping entity-level usage@Entity(name = "Account")
@FilterDef(
name="activeAccount",
parameters = @ParamDef(
name="active",
type="boolean"
)
)
@Filter(
name="activeAccount",
condition="active_status = :active"
)
public static class Account {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private Client client;
@Column(name = "account_type")
@Enumerated(EnumType.STRING)
private AccountType type;
private Double amount;
private Double rate;
@Column(name = "active_status")
private boolean active;
//Getters and setters omitted for brevity
}
Notice that the This mapping was done to show you that the |
As already explained, we can also apply the @Filter
annotation for collections as illustrated by the Client
entity:
@Filter
mapping collection-level usage@Entity(name = "Client")
public static class Client {
@Id
private Long id;
private String name;
@OneToMany(
mappedBy = "client",
cascade = CascadeType.ALL
)
@Filter(
name="activeAccount",
condition="active_status = :active"
)
private List<Account> accounts = new ArrayList<>( );
//Getters and setters omitted for brevity
public void addAccount(Account account) {
account.setClient( this );
this.accounts.add( account );
}
}
If we persist a Client
with three associated Account
entities,
Hibernate will execute the following SQL statements:
@Filter
mappingClient client = new Client()
.setId( 1L )
.setName( "John Doe" );
client.addAccount(
new Account()
.setId( 1L )
.setType( AccountType.CREDIT )
.setAmount( 5000d )
.setRate( 1.25 / 100 )
.setActive( true )
);
client.addAccount(
new Account()
.setId( 2L )
.setType( AccountType.DEBIT )
.setAmount( 0d )
.setRate( 1.05 / 100 )
.setActive( false )
);
client.addAccount(
new Account()
.setType( AccountType.DEBIT )
.setId( 3L )
.setAmount( 250d )
.setRate( 1.05 / 100 )
.setActive( true )
);
entityManager.persist( client );
INSERT INTO Client (name, id)
VALUES ('John Doe', 1)
INSERT INTO Account (active_status, amount, client_id, rate, account_type, id)
VALUES (true, 5000.0, 1, 0.0125, 'CREDIT', 1)
INSERT INTO Account (active_status, amount, client_id, rate, account_type, id)
VALUES (false, 0.0, 1, 0.0105, 'DEBIT', 2)
INSERT INTO Account (active_status, 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.
@Filter
List<Account> accounts = entityManager.createQuery(
"select a from Account a", Account.class)
.getResultList();
assertEquals( 3, accounts.size());
SELECT
a.id as id1_0_,
a.active_status 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
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.
@Filter
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_status 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_status = 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
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
.
@Filter
Client client = entityManager.find( Client.class, 1L );
assertEquals( 3, 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_status 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
When activating the @Filter
and fetching the accounts
collections, Hibernate is going to apply the filter condition to the associated collection entries.
@Filter
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_status 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_status = true
and a.client_id = 1
The main advantage of |
It’s not possible to combine the If caching were 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. |
5.9.4. @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.
@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:
@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]
5.9.5. @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 illustrated in the following mapping:
@FilterJoinTable
mapping usage@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(cascade = CascadeType.ALL)
@OrderColumn(name = "order_id")
@FilterJoinTable(
name="firstAccounts",
condition="order_id <= :maxOrderId"
)
private List<Account> accounts = new ArrayList<>( );
//Getters and setters omitted for brevity
public void addAccount(Account account) {
this.accounts.add( account );
}
}
@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;
//Getters and setters omitted for brevity
}
The firstAccounts
filter will allow us to get only the Account
entities that have the order_id
(which tells the position of every entry inside the accounts
collection)
less than a given number (e.g. maxOrderId
).
Let’s assume our database contains the following entities:
@FilterJoinTable
mappingClient client = new Client()
.setId( 1L )
.setName( "John Doe" );
client.addAccount(
new Account()
.setId( 1L )
.setType( AccountType.CREDIT )
.setAmount( 5000d )
.setRate( 1.25 / 100 )
);
client.addAccount(
new Account()
.setId( 2L )
.setType( AccountType.DEBIT )
.setAmount( 0d )
.setRate( 1.05 / 100 )
);
client.addAccount(
new Account()
.setType( AccountType.DEBIT )
.setId( 3L )
.setAmount( 250d )
.setRate( 1.05 / 100 )
);
entityManager.persist( client );
INSERT INTO Client (name, id)
VALUES ('John Doe', 1)
INSERT INTO Account (amount, client_id, rate, account_type, id)
VALUES (5000.0, 1, 0.0125, 'CREDIT', 1)
INSERT INTO Account (amount, client_id, rate, account_type, id)
VALUES (0.0, 1, 0.0105, 'DEBIT', 2)
INSERT INTO Account (amount, client_id, rate, account_type, id)
VALUES (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 only if the associated filter is enabled on the currently running Hibernate Session
.
@FilterJoinTable
without enabling the filterClient client = entityManager.find( Client.class, 1L );
assertEquals( 3, 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.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]
If we enable the filter and set the maxOrderId
to 1
when fetching the accounts
collections, Hibernate is going to apply the @FilterJoinTable
clause filtering criteria, and we will get just
2
Account
entities, with the order_id
values of 0
and 1
.
@FilterJoinTable
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.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]
5.10. 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.
Person person = entityManager.find( Person.class, personId );
person.setName("John Doe");
entityManager.flush();
Person person = session.byId( Person.class ).load( personId );
person.setName("John Doe");
session.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:
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:
Product
entityProduct 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:
Product
entitydoInJPA( 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.10.1. Dynamic updates
To enable dynamic updates, you need to annotate the entity with the @DynamicUpdate
annotation:
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 rerunning the previous test case, Hibernate generates the following SQL UPDATE statement:
Product
entity with a dynamic updateUPDATE
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 that were modified in the associated entity.
5.11. Refresh entity state
You can reload an entity instance and its collections at any time.
Person person = entityManager.find( Person.class, personId );
entityManager.createQuery( "update Person set name = UPPER(name)" ).executeUpdate();
entityManager.refresh( person );
assertEquals("JOHN DOE", person.getName() );
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 |
Traditionally, Hibernate allowed detached entities to be refreshed.
Unfortunately, JPA prohibits this practice and specifies that an For this reason, when bootstrapping the Hibernate However, this default behavior can be overwritten through the For more about the |
5.11.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:
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.12. 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 modifications, and the application will need to intervene to make the changes persistent again.
5.12.1. Reattaching detached data
Reattachment is the process of taking an incoming entity instance that is in the detached state and re-associating it with the current persistence context.
JPA does not support reattaching detached data. This is only available through Hibernate |
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 );
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 |
Provided the entity is detached, update
and saveOrUpdate
operate exactly the same.
5.12.2. Merging detached data
Merging is the process of taking an incoming entity instance that is in the 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.
public Person merge(Person detached) {
Person newReference = session.byId( Person.class ).load( detached.getId() );
newReference.setName( detached.getName() );
return newReference;
}
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 );
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 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 The only way to exclude particular entity classes or associations that contain critical data is to provide a custom implementation of |
Hibernate provides limited DEBUG logging capabilities that can help determine the entity classes for which entity copies were found.
By setting
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 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 |
5.13. Checking persistent state
An application can verify the state of entities and collections in relation to the persistence context.
boolean contained = entityManager.contains( person );
boolean contained = session.contains( person );
PersistenceUnitUtil persistenceUnitUtil = entityManager.getEntityManagerFactory().getPersistenceUnitUtil();
boolean personInitialized = persistenceUnitUtil.isLoaded( person );
boolean personBooksInitialized = persistenceUnitUtil.isLoaded( person.getBooks() );
boolean personNameInitialized = persistenceUnitUtil.isLoaded( person, "name" );
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).
PersistenceUtil persistenceUnitUtil = Persistence.getPersistenceUtil();
boolean personInitialized = persistenceUnitUtil.isLoaded( person );
boolean personBooksInitialized = persistenceUnitUtil.isLoaded( person.getBooks() );
boolean personNameInitialized = persistenceUnitUtil.isLoaded( person, "name" );
5.14. 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.
EntityManager
for(Person person : entityManager.createQuery("select p from Person p", Person.class)
.getResultList()) {
dtos.add(toDTO(person));
entityManager.detach( person );
}
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.
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.
entityManager.contains( person );
session.contains( person );
5.15. 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<>();
//Getters and setters are omitted for brevity
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;
//Getters and setters are omitted for brevity
}
5.15.1. CascadeType.PERSIST
The CascadeType.PERSIST
allows us to persist a child entity along with the parent one.
CascadeType.PERSIST
examplePerson 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.15.2. CascadeType.MERGE
The CascadeType.MERGE
allows us to merge a child entity along with the parent one.
CascadeType.MERGE
examplePhone 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.15.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.
CascadeType.REMOVE
examplePerson person = entityManager.find( Person.class, 1L );
entityManager.remove( person );
DELETE FROM Phone WHERE id = 1
DELETE FROM Person WHERE id = 1
5.15.4. CascadeType.DETACH
CascadeType.DETACH
is used to propagate the detach operation from a parent entity to a child.
CascadeType.DETACH
examplePerson 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.15.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.
CascadeType.LOCK
examplePerson 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.15.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.
CascadeType.REFRESH
examplePerson 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.15.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.
CascadeType.REPLICATE
examplePerson 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.15.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.
@OnDelete
@ManyToOne
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
entities are going to be deleted automatically via the Foreign Key cascade.
@OnDelete
@ManyToOne
delete examplePerson person = entityManager.find( Person.class, 1L );
entityManager.remove( person );
delete from Person where id = ?
-- binding parameter [1] as [BIGINT] - [1]
The @OnDelete
annotation can also be placed on a collection, as
illustrated in the following example.
@OnDelete
@OneToMany
mapping@Entity(name = "Person")
public static class Person {
@Id
private Long id;
private String name;
@OneToMany(mappedBy = "owner", cascade = CascadeType.ALL)
@OnDelete(action = OnDeleteAction.CASCADE)
private List<Phone> phones = new ArrayList<>();
//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)
private Person owner;
//Getters and setters are omitted for brevity
}
Now, when removing the Person
entity, all the associated Phone
child entities are deleted via the Foreign Key cascade even if the @OneToMany
collection was using the CascadeType.ALL
attribute.
@OnDelete
@ManyToOne
delete examplePerson person = entityManager.find( Person.class, 1L );
entityManager.remove( person );
delete from Person where id = ?
-- binding parameter [1] as [BIGINT] - [1]
Without the This way, only the parent entity gets deleted, and all the associated child records are removed by the database engine, instead of being deleted explicitly via |
5.16. Exception handling
If the JPA EntityManager
or the Hibernate-specific Session
throws an exception, including any JDBC SQLException
, you have to immediately rollback the database transaction and close the current EntityManager
or Session
.
Certain methods of the JPA EntityManager
or the Hibernate Session
will not leave the Persistence Context in a consistent state. As a rule of thumb, no exception thrown by Hibernate can be treated as recoverable. Ensure that the Session will be closed by calling the close()
method in a finally block.
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 JPA PersistenceException
or the
HibernateException
wraps most of the errors that can occur in a Hibernate persistence layer.
Both the PersistenceException
and the HibernateException
are runtime exceptions because, in our opinion, we should not force the application developer to catch an unrecoverable exception at a low layer. In most systems, unchecked and fatal exceptions are handled in one of the first frames of the method call stack (i.e., in higher layers) and either an error message is presented to the application user or some other appropriate action is taken. Note that Hibernate might also throw other unchecked exceptions that are not a HibernateException
. These are not recoverable either, and appropriate action should be taken.
Hibernate wraps the JDBC SQLException
, thrown while interacting with the database, in a
JDBCException
.
In fact, Hibernate will attempt to convert the exception into a more meaningful subclass of JDBCException
. The underlying SQLException
is always available via JDBCException.getSQLException()
. Hibernate converts the SQLException
into an appropriate JDBCException subclass using the
SQLExceptionConverter
attached to the current SessionFactory
.
By default, the SQLExceptionConverter
is defined by the configured Hibernate Dialect
via the
buildSQLExceptionConversionDelegate
method
which is overridden by several database-specific Dialect
s.
However, it is also possible to plug in a custom implementation. See the
hibernate.jdbc.sql_exception_converter
configuration property for more details.
The standard JDBCException
subtypes are:
- ConstraintViolationException
-
indicates some form of integrity constraint violation.
- DataException
-
indicates that evaluation of the valid SQL statement against the given data resulted in some illegal operation, mismatched types, truncation or incorrect cardinality.
- GenericJDBCException
-
a generic exception which did not fall into any of the other categories.
- JDBCConnectionException
-
indicates an error with the underlying JDBC communication.
- LockAcquisitionException
-
indicates an error acquiring a lock level necessary to perform the requested operation.
- LockTimeoutException
-
indicates that the lock acquisition request has timed out.
- PessimisticLockException
-
indicates that a lock acquisition request has failed.
- QueryTimeoutException
-
indicates that the current executing query has timed out.
- SQLGrammarException
-
indicates a grammar or syntax problem with the issued SQL.
Starting with Hibernate 5.2, the Hibernate If your |
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 the 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 currentTransaction
is committed, although it might flush prematurely too. - MANUAL
-
The
Session
flushing is delegated to the application, which must callSession.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.
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 |
6.1.2. AUTO
flush on JPQL/HQL query
A flush may also be triggered when executing an entity query.
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 that there’s no overlapping between the Advertisement
and the Person
tables:
@Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue
private Long id;
private String name;
//Getters and setters are omitted for brevity
}
@Entity(name = "Advertisement")
public static class Advertisement {
@Id
@GeneratedValue
private Long id;
private String title;
//Getters and setters are omitted for brevity
}
When querying for a Person
entity, the flush is triggered prior to executing the entity query.
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 persisting 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.
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 );
If you bootstrap Hibernate natively, and not through JPA, by default,
the Session
API will trigger a flush automatically when executing a native query.
Session
assertTrue(((Number) session
.createNativeQuery( "select count(*) from Person")
.getSingleResult()).intValue() == 0 );
Person person = new Person( "John Doe" );
session.persist( person );
assertTrue(((Number) session
.createNativeQuery( "select count(*) from Person")
.uniqueResult()).intValue() == 0 );
To flush the Session
, the query must use a synchronization:
Session
synchronizationassertTrue(((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.
When executing a JPQL query, the persistence context is only flushed when the current running transaction is committed.
COMMIT
flushing on JPQLPerson 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.
COMMIT
flushing on native SQLPerson 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 |
The ALWAYS
flush mode triggers a persistence context flush even when executing a native SQL query against the Session
API.
COMMIT
flushing on native SQLPerson 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 provides a MANUAL
flush mode so the persistence context can only be flushed manually.
MANUAL
flushingPerson 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 there was no manual flush()
call.
The |
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 with a given SQL statement.
INSERT
-
The
INSERT
statement is generated either by theEntityInsertAction
orEntityIdentityInsertAction
. These actions are scheduled by thepersist
operation, either explicitly or through cascading thePersistEvent
from a parent to a child entity. DELETE
-
The
DELETE
statement is generated by theEntityDeleteAction
orOrphanRemovalAction
. UPDATE
-
The
UPDATE
statement is generated byEntityUpdateAction
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:
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 |
The ActionQueue
executes all operations in the following order:
-
OrphanRemovalAction
-
EntityInsertAction
orEntityIdentityInsertAction
-
EntityUpdateAction
-
QueuedOperationCollectionAction
-
CollectionRemoveAction
-
CollectionUpdateAction
-
CollectionRecreateAction
-
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:
-
If
hibernate.connection.provider_class
is set, it takes precedence -
else if
hibernate.connection.datasource
is set → Using DataSources -
else if any setting prefixed by
hibernate.c3p0.
is set → Using c3p0 -
else if any setting prefixed by
hibernate.proxool.
is set → Using Proxool -
else if any setting prefixed by
hibernate.hikari.
is set → Using HikariCP -
else if any setting prefixed by
hibernate.vibur.
is set → Using Vibur DBCP -
else if any setting prefixed by
hibernate.agroal.
is set → Using Agroal -
else if
hibernate.connection.url
is set → Using Hibernate’s built-in (and unsupported) pooling -
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 |
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. Driver Configuration
hibernate.connection.driver_class
-
The name of the JDBC Driver class to use
hibernate.connection.url
-
The JDBC connection url
hibernate.connection.*
-
All such setting names (except the predefined ones) will have the
hibernate.connection.
prefix stripped. The remaining name and the original value will be passed to the driver as a JDBC connection property
Not all properties apply to all situations. For example, if you are providing a data source, hibernate.connection.driver_class setting will not be used.
|
7.4. Using c3p0
To use the c3p0 integration, the application must include the |
Hibernate also provides support for applications to use c3p0 connection pooling. When c3p0 support is enabled, a number of c3p0-specific configuration settings are recognized in addition to the general ones described in Driver Configuration.
Transaction isolation of the Connections is managed by the ConnectionProvider
itself. See ConnectionProvider support for transaction isolation setting.
hibernate.c3p0.min_size
orc3p0.minPoolSize
-
The minimum size of the c3p0 pool. See c3p0 minPoolSize
hibernate.c3p0.max_size
orc3p0.maxPoolSize
-
The maximum size of the c3p0 pool. See c3p0 maxPoolSize
hibernate.c3p0.timeout
orc3p0.maxIdleTime
-
The Connection idle time. See c3p0 maxIdleTime
hibernate.c3p0.max_statements
orc3p0.maxStatements
-
Controls the c3p0 PreparedStatement cache size (if using). See c3p0 maxStatements
hibernate.c3p0.acquire_increment
orc3p0.acquireIncrement
-
Number of connections c3p0 should acquire at a time when the pool is exhausted. See c3p0 acquireIncrement
hibernate.c3p0.idle_test_period
orc3p0.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.5. Using Proxool
To use the Proxool integration, the application must include the |
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.1. 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.5.2. 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.5.3. 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.6. Using HikariCP
To use the HikariCP this integration, the application must include the |
Hibernate also provides support for applications to use HikariCP 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.7. Using Vibur DBCP
To use the Vibur DBCP integration, the application must include the |
Hibernate also provides support for applications to use Vibur DBCP connection pool.
Set all of your Vibur settings in Hibernate prefixed by hibernate.vibur.
and this ConnectionProvider
will pick them up and pass them along to Vibur DBCP.
Additionally, this ConnectionProvider
will pick up the following Hibernate-specific properties and map them to the corresponding Vibur ones (any hibernate.vibur.
prefixed ones have precedence):
hibernate.connection.driver_class
-
Mapped to Vibur’s
driverClassName
setting hibernate.connection.url
-
Mapped to Vibur’s
jdbcUrl
setting hibernate.connection.username
-
Mapped to Vibur’s
username
setting hibernate.connection.password
-
Mapped to Vibur’s
password
setting hibernate.connection.isolation
-
Mapped to Vibur’s
defaultTransactionIsolationValue
setting. See ConnectionProvider support for transaction isolation setting. hibernate.connection.autocommit
-
Mapped to Vibur’s
defaultAutoCommit
setting
7.8. Using Agroal
To use the Agroal integration, the application must include the |
Hibernate also provides support for applications to use Agroal connection pool.
Set all of your Agroal settings in Hibernate prefixed by hibernate.agroal.
and this ConnectionProvider
will pick them up and pass them along to Agroal connection pool.
Additionally, this ConnectionProvider
will pick up the following Hibernate-specific properties and map them to the corresponding Agroal ones (any hibernate.agroal.
prefixed ones have precedence):
hibernate.connection.driver_class
-
Mapped to Agroal’s
driverClassName
setting hibernate.connection.url
-
Mapped to Agroal’s
jdbcUrl
setting hibernate.connection.username
-
Mapped to Agroal’s
principal
setting hibernate.connection.password
-
Mapped to Agroal’s
credential
setting hibernate.connection.isolation
-
Mapped to Agroal’s
jdbcTransactionIsolation
setting. See ConnectionProvider support for transaction isolation setting. hibernate.connection.autocommit
-
Mapped to Agroal’s
autoCommit
setting
7.9. Using Hibernate’s built-in (and unsupported) pooling
The built-in connection pool is not supported for use in a production system. |
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
forjava.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
forjava.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. Connection handling
The connection handling mode is defined by the
PhysicalConnectionHandlingMode
enumeration which provides the following strategies:
IMMEDIATE_ACQUISITION_AND_HOLD
-
The
Connection
will be acquired as soon as theSession
is opened and held until theSession
is closed. DELAYED_ACQUISITION_AND_HOLD
-
The
Connection
will be acquired as soon as it is needed and then held until theSession
is closed. DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT
-
The
Connection
will be acquired as soon as it is needed and will be released after each statement is executed. DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION
-
The
Connection
will be acquired as soon as it is needed and will be released after each transaction is completed.
If you don’t want to use the default connection handling mode, you can specify a connection handling mode via the hibernate.connection.handling_mode
configuration property. For more details, check out the
Database connection properties section.
7.12.1. Transaction type and connection handling
By default, the connection handling mode is given by the underlying transaction coordinator. There are two types of transactions: RESOURCE_LOCAL
(which involves a single database Connection
and the transaction is controlled via the commit
and rollback
Connection
methods) and JTA
(which may involve multiple resources including database connections, JMS queues, etc).
RESOURCE_LOCAL transaction connection handling
For RESOURCE_LOCAL
transactions, the connection handling mode is DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION
meaning that the database connection is acquired when needed and released after the current running transaction is either committed or rolled back.
However, because Hibernate needs to make sure that the default autocommit mode is disabled on the JDBC Connection
when starting a new transaction, the Connection
is acquired and the autocommit mode is set to false
.
If you are using a connection pool |
JTA transaction connection handling
For JTA
transactions, the connection handling mode is DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT
meaning that the database connection is acquired when needed and released after each statement execution.
The reason for releasing the database connection after statement execution is because some Java EE application servers
report a connection leak when a method call goes from one EJB to another. However, even if the JDBC Connection
is released to the pool, the Connection
is still allocated to the current executing Thread
, hence when executing a subsequent statement in the current running transaction, the same Connection
object reference will be obtained from the pool.
If the Java EE application server or JTA transaction manager supports switching from one EJB to another while the transaction gets propagated from the outer EJB to the inner one,
and no connection leak false positive is being reported, then you should consider switching to |
7.12.2. User-provided connections
If the current Session
was created using the
SessionBuilder
and a JDBC Connection
was provided via the
SessionBuilder#connection
method, then the user-provided Connection
is going to be used, and
the connection handling mode will be IMMEDIATE_ACQUISITION_AND_HOLD
.
Therefore for user-provided connection, the connection is acquired right away and held until the current Session
is closed, without being influenced by the JPA or Hibernate transaction context.
7.13. 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.
Dialect (short name) | Remarks |
---|---|
Cache71 |
Support for the Caché database, version 2007.1. |
CockroachDB192 |
Support for the CockroachDB database version 19.2. |
CockroachDB201 |
Support for the CockroachDB database version 20.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. |
DB2400V7R3 |
Support for DB2 Universal Database for i, also known as DB2/400, version 7.3 |
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 |
HANACloudColumnStore |
Support for the SAP HANA Cloud database column store. |
HANAColumnStore |
Support for the SAP HANA database column store, version 2.x. This is the recommended dialect for the SAP HANA database. May work with SAP HANA, version 1.x |
HANARowStore |
Support for the SAP HANA database row store, version 2.x. May work with SAP HANA, version 1.x |
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.
-
It might refer to the physical transaction with the database.
-
It might refer to the logical notion of a transaction as related to a persistence context.
-
It 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 mechanisms 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.
The 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 |
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-running 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 concept is referred to as |
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, |
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:
Atomikos
-
JtaPlatform
for Atomikos. 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 to 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
andsetTimeout
-
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 singleSynchronization
with theTransactionManager
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 setups.
Let’s take a look at using the Transaction API in the various environments.
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();
}
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();
}
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. 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 aSession
.
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.
The hibernate.current_session_context_class
configuration parameter defines which org.hibernate.context.spi.CurrentSessionContext
implementation should be used.
For backward 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.5.1. 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.5.2. 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 theorg.hibernate.context.internal.JTASessionContext
implementation of theorg.hibernate.context.spi.CurrentSessionContext
contract. Using this implementation, aSession
will be opened the first timegetCurrentSession
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 theorg.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 theSession
. In the end, itsunbind()
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 |
8.5.3. Conversations (application-level transactions)
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 |
The Hibernate |
Session-per-request-with-detached-objects and session-per-conversation each have advantages and disadvantages.
8.5.4. Session-per-application anti-pattern
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.
For more details, check out the exception handling section in Persistence Context chapter.
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 forTransactionManager
,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. Seejavax.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 |
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
orInteger
-
short
orShort
-
long
orLong
-
java.sql.Timestamp
However, Hibernate allows you to use even Java 8 Date/Time types, such as Instant
.
@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.
@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 |
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.
@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 database 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.
@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:
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.
@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 thread 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.
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 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
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:
OptimisticLockType.ALL
update examplePerson 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 |
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.
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:
OptimisticLockType.DIRTY
update examplePerson 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 When using |
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 |
---|---|---|
|
|
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 |
|
|
The entity version is checked towards the end of the currently running transaction. |
|
|
The entity version is incremented automatically even if the entity has not changed. |
|
|
The entity is locked pessimistically and its version is incremented automatically even if the entity has not changed. |
|
|
The entity is locked pessimistically using a shared lock if the database supports such a feature. Otherwise, an explicit lock is used. |
|
|
The entity is locked using an explicit lock. |
|
|
The lock acquisition request fails fast if the row s already locked. |
|
|
The lock acquisition request skips the already locked rows. It uses a |
The explicit user request mentioned above occurs as a consequence of any of the following actions:
-
a call to
Session.load()
, specifying aLockMode
. -
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) orEXTENDED
. TheEXTENDED
scope will cause a lock acquisition request to be passed to other owned table structured (e.g.@Inheritance(strategy=InheritanceType.JOINED)
,@ElementCollection
)
javax.persistence.lock.timeout
exampleentityManager.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 |
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 a shared database lock without waiting for the lock acquisition request.
buildLockRequest
examplePerson 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.
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. |
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 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 demands 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.
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 currently executing query fails because the |
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 overall application 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 while "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 are 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)
-
The 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
-
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:
@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:
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:
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 lead 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 a simple domain model and a few use cases.
@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.
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.
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.
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();
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 graph
so the application developer has more control over fetch plans. It has two modes to choose from:
- fetch graph
-
In this case, all attributes specified in the entity graph will be treated as FetchType.EAGER, and all attributes not specified will ALWAYS be treated as FetchType.LAZY.
- load graph
-
In this case, all attributes specified in the entity graph will be treated as FetchType.EAGER, but attributes not specified use their static mapping specification.
Below is an fetch graph
dynamic fetching 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" )
)
);
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 to N+1 query issues. For this reason, it’s better to use LAZY associations, and only fetch them eagerly on a per-query basis. |
An EntityGraph is the root of a "load plan" and must correspond to an EntityType.
11.6.1. JPA (key) subgraphs
A sub-graph is used to control the fetching of sub-attributes of the AttributeNode it is applied to.
It is generally defined via 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.
@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:
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]
Specifying a sub-graph is only valid for an attribute (or its "key") whose type is a ManagedType. So while an EntityGraph must correspond to an EntityType, a Subgraph is legal for any ManagedType. An attribute’s key is defined as either:
-
For a singular attribute, the attribute’s type must be an IdentifiableType and that IdentifiableType must have a composite identifier. The "key sub-graph" is applied to the identifier type. The non-key sub-graph applies to the attribute’s value, which must be a ManagedType.
-
For a plural attribute, the attribute must be a Map and the Map’s key value must be a ManagedType. The "key sub-graph" is applied to the Map’s key type. In this case, the non-key sub-graph applies to the plural attribute’s value/element.
11.6.2. JPA SubGraph sub-typing
SubGraphs can also be sub-type specific. Given an attribute whose value is an inheritance hierarchy, we can refer to attributes of a specific sub-type using the forms of sub-graph definition that accept the sub-type Class.
11.6.3. Creating and applying JPA graphs from text representations
Hibernate allows the creation of JPA fetch/load graphs by parsing a textual representation
of the graph. Generally speaking, the textual representation of a graph is a comma-separated
list of attribute names, optionally including any sub-graph specifications.
org.hibernate.graph.EntityGraphParser
is the starting point for such parsing operations.
Parsing a textual representation of a graph is not (yet) a part of the JPA specification. So the syntax described here is specific to Hibernate. We do hope to eventually make this syntax part of the JPA specification proper. |
final EntityGraph<Project> graph = GraphParser.parse(
Project.class,
"employees( department )",
entityManager
);
This example actually functions exactly as Fetch graph with a subgraph mapping, just using a parsed graph rather than a named graph.
The syntax also supports defining "key sub-graphs". To specify a key sub-graph, .key
is added
to the end of the attribute name.
final EntityGraph<Movie> graph = GraphParser.parse(
Movie.class,
"cast.key( name )",
entityManager
);
final EntityGraph<Ticket> graph = GraphParser.parse(
Ticket.class,
"showing.key( movie( cast ) )",
entityManager
);
Parsing can also handle sub-type specific sub-graphs. For example, given an entity hierarchy of
LegalEntity
← (Corporation
| Person
| NonProfit
) and an attribute named responsibleParty
whose
type is the LegalEntity
base type we might have:
responsibleParty(Corporation: ceo)
We can even duplicate the attribute names to apply different sub-type sub-graphs:
responsibleParty(taxIdNumber), responsibleParty(Corporation: ceo), responsibleParty(NonProfit: sector)
The duplicated attribute names are handled according to the JPA specification which says that duplicate specification of the attribute node results in the originally registered AttributeNode to be re-used effectively merging the 2 AttributeNode specifications together. In other words, the above specification creates a single AttributeNode with 3 distinct SubGraphs. It is functionally the same as calling:
Class<Invoice> invoiceClass = ...;
javax.persistence.EntityGraph<Invoice> invoiceGraph = entityManager.createEntityGraph( invoiceClass );
invoiceGraph.addAttributeNode( "responsibleParty" );
invoiceGraph.addSubgraph( "responsibleParty" ).addAttributeNode( "taxIdNumber" );
invoiceGraph.addSubgraph( "responsibleParty", Corporation.class ).addAttributeNode( "ceo" );
invoiceGraph.addSubgraph( "responsibleParty", NonProfit.class ).addAttributeNode( "sector" );
11.6.4. Combining multiple JPA entity graphs into one
Multiple entity graphs can be combined into a single "super graph" that acts as a union. Graph from the previous example can also be built by combining separate aspect graphs into one, such as:
final EntityGraph<Project> a = GraphParser.parse(
Project.class, "employees( username )", entityManager
);
final EntityGraph<Project> b = GraphParser.parse(
Project.class, "employees( password, accessLevel )", entityManager
);
final EntityGraph<Project> c = GraphParser.parse(
Project.class, "employees( department( employees( username ) ) )", entityManager
);
final EntityGraph<Project> all = EntityGraphs.merge( entityManager, Project.class, a, b, c );
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.
@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:
@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.
@BatchSize
fetching exampleList<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 with multiple Department
entities.
Without However, although |
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:
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:
FetchMode.SELECT
mapping exampleList<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
:
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 predicate
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.
FetchMode.SUBSELECT
mapping exampleList<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:
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 To fetch multiple relationships with a JPQL query, the Therefore, Also, the |
Hibernate is going to avoid the secondary query by issuing an OUTER JOIN for the employees
collection.
FetchMode.JOIN
mapping exampleDepartment 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.
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
}
For bags (e.g. regular List(s) of entities that do not preserve any certain ordering),
the |
Now, considering we have the following entities:
LazyCollectionOption.EXTRA
Domain Model exampleDepartment 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:
LazyCollectionOption.EXTRA
fetching exampleDepartment 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 since accessing all elements using |
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 |
Session
basisentityManager
.unwrap( Session.class )
.setJdbcBatchSize( 10 );
12.2. Session batching
The following example shows an anti-pattern for batch inserts.
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:
-
Hibernate caches all the newly inserted
Customer
instances in the session-level cache, 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 fail with anOutOfMemoryException
. The Java 1.8 JVM allocated either 1/4 of available RAM or 1Gb, which can easily accommodate 100 000 objects on the heap. -
long-running transactions can deplete a connection pool so other transactions don’t get a chance to proceed.
-
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.
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++ ) {
if ( i > 0 && i % batchSize == 0 ) {
//flush a batch of inserts and release memory
entityManager.flush();
entityManager.clear();
}
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();
}
}
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.
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. However, it is good practice to close the |
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 lifecycle 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.
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.
UPDATE FROM EntityName e WHERE e.name = ?
DELETE FROM EntityName e WHERE e.name = ?
Although the |
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 |
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();
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.
int updatedEntities = session.createQuery(
"update versioned Person " +
"set name = :newName " +
"where name = :oldName" )
.setParameter( "oldName", oldName )
.setParameter( "newName", newName )
.executeUpdate();
If you use the This feature is only available in HQL since it’s not standardized by JPA. |
DELETE
statementint deletedEntities = entityManager.createQuery(
"delete Person p " +
"where p.name = :name" )
.setParameter( "name", name )
.executeUpdate();
DELETE
statementint 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 affected by the operation.
This may or may not correlate to the number of rows affected in the database.
A 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
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
.
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.
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:
The Person
entity is the base class of this entity inheritance model, and is mapped as follows:
@Entity(name = "Person")
@Inheritance(strategy = InheritanceType.JOINED)
public static class Person implements Serializable {
@Id
private Integer id;
@Id
private String companyName;
private String name;
private boolean employed;
//Getters and setters are omitted for brevity
}
Both the Doctor
and Engineer
entity classes extend the Person
base class:
@Entity(name = "Doctor")
public static class Doctor extends Person {
}
@Entity(name = "Engineer")
public static class Engineer extends Person {
private boolean fellow;
//Getters and setters are omitted for brevity
}
Inheritance tree bulk processing
Now, when you try to execute a bulk entity delete query:
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:
InlineIdsInClauseBulkIdStrategy
delete entity query exampleselect
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 it, 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:
InlineIdsSubSelectValueListBulkIdStrategy
delete entity query exampleselect
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 |
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:
InlineIdsOrClauseBulkIdStrategy
delete entity query exampleselect
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' )
The |
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:
CteValuesListBulkIdStrategy
delete entity query exampleselect
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 CTE (Common Table Expressions) that can be referenced from non-query statements as well. For instance, PostgreSQL supports this feature since version 9.1 and SQL Server offers support for it since version 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 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 up 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 making 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 Hibernate caches are not aware of changes made to the persistent store by other applications. To address this limitation, you can configure a TTL (Time To Live) retention policy at the second-level cache region level so that the underlying cache entries expire regularly. |
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 provider specific 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. By default, if the currently configured
RegionFactory
is not theNoCachingRegionFactory
, then the second-level cache is going to be enabled. Otherwise, the second-level cache is disabled. hibernate.cache.use_query_cache
-
Enable or disable second level caching of query results. The 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.TimestampsCacheFactory
. 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 defining 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 the second-level cache as a 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 setting, it is recommended to define the cache concurrency strategy on a per entity basis. Use the |
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 isnon-lazy
so lazy properties are not cacheable.
13.3. Entity inheritance and second-level cache mapping
Traditionally, when using entity inheritance, Hibernate required an entity hierarchy to be either cached entirely or not cached at all.
Therefore, if you wanted to cache a subclass belonging to a given entity hierarchy,
the JPA @Cacheable
and the Hibernate-specific @Cache
annotations would have to be declared at the root-entity level only.
Although we still believe that all entities belonging to a given entity hierarchy should share the same caching semantics,
the JPA 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 specifyingCacheable
on a subclass.
As of Hibernate ORM 5.3, you can now override a base class However, the Hibernate cache concurrency strategy (e.g. read-only, nonstrict-read-write, read-write, transactional) is still defined at the root entity level and cannot be overridden. |
Nevertheless, the reasons why we advise you to have 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 slows the bootstrap process.
-
providing different caching semantics for subclasses would violate the Liskov substitution principle.
13.4. Entity cache
@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;
//Getters and setters are omitted for brevity
}
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:
Person person = entityManager.find( Person.class, 1L );
Person person = session.get( Person.class, 1L );
The Hibernate second-level cache can also load entities by their natural id:
@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;
//Getters and setters are omitted for brevity
}
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.
@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:
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 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:
<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.
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();
List<Person> persons = session.createQuery(
"select p " +
"from Person p " +
"where p.name = :name")
.setParameter( "name", "John Doe")
.setCacheable(true)
.list();
For entity queries, the query cache does not cache the state of the actual entities. Instead, it stores the entity identifiers, and when the query result is fetched from the cache, the entity state is going to be loaded from the second-level cache entity regions. 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. For projection queries, the query cache stores the dehydrated entity state (e.g. |
13.6.1. Query cache regions
This setting creates two new cache regions:
default-query-results-region
-
Holding the cached query results.
default-update-timestamps-region
-
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 In fact, we recommend that the |
If you require fine-grained control over query cache expiration policies, you can specify a named cache region for a particular query.
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();
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.
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();
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 This behavior is particularly useful in cases when the underlying data may have been updated via a separate process
and is a far more efficient alternative to the bulk eviction of the region via
|
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:
Hibernate | JPA | Description |
---|---|---|
|
|
Default. Reads/writes data from/into the cache |
|
|
Doesn’t read from cache, but writes to the cache upon loading from the database |
|
|
Doesn’t read from cache, but writes to the cache as it reads from the database |
|
|
Read from the cache, but doesn’t write to cache |
|
|
Doesn’t read/write data from/into the cache |
Setting the cache mode can be done either when loading entities directly or when executing a query.
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);
session.setCacheMode( CacheMode.REFRESH );
Person person = session.get( Person.class, 1L );
The custom cache modes can be set for queries as well:
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();
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:
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)
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.
Statistics statistics = session.getSessionFactory().getStatistics();
CacheRegionStatistics secondLevelCacheStatistics =
statistics.getDomainDataRegionStatistics( "query.cache.person" );
long hitCount = secondLevelCacheStatistics.getHitCount();
long missCount = secondLevelCacheStatistics.getMissCount();
double hitRatio = (double) hitCount / ( hitCount + missCount );
13.9. JCache
To use the built-in integration for JCache, you need the 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:
JCacheRegionFactory
configuration<property
name="hibernate.cache.region.factory_class"
value="jcache"/>
The JCacheRegionFactory
configures a javax.cache.CacheManager
.
13.9.2. JCache CacheManager
JCache mandates that CacheManager
s 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, Cache
s 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 Cache
s you can use the following two properties:
<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
.
Using a non-default JCache CacheManager
If you don’t want to use the default CacheManager
, you need to set the hibernate.javax.cache.cache_manager
configuration property
to one of the following values:
- Object reference
-
If the value is an
Object
instance implementing theCacheManager
interface, the providedCacheManager
instance will be used. Class
-
If the value is a Java
Class
object that implements theCacheManager
interface, Hibernate will create a new instance for thatClass
and use it instead of the default one.When passing a Java
Class
that implements theCacheManager
interface, you must make sure that theCacheManager
implementation class provides a default no-arg constructor because that’s going to be used to instantiate aCacheManager
implementationObject
. String
-
If the value is a Java
String
, Hibernate expects it to be the fully-qualifiedClass
name of theCacheManager
implementation which will be used to instantiate the non-defaultCacheManager
.When passing the fully-qualified class name, you must make sure that the associated
Class
type provides a default no-arg constructor because that’s going to be used to instantiate aCacheManager
implementationObject
.
13.9.3. JCache missing cache strategy
By default, the JCache region factory will log a warning when asked to create a cache that is not explicitly configured and pre-started in the underlying cache manager. Thus if you configure an entity type or a collection as cached, but do not configure the corresponding cache explicitly, one warning will be logged for each cache that was not configured explicitly.
You may change this behavior by setting the hibernate.javax.cache.missing_cache_strategy
property
to one of the following values:
Value | Description |
---|---|
|
Fail with an exception on missing caches. |
|
Default value. Create a new cache when a cache is not found (see |
|
Create a new cache when a cache is not found, without logging any warning about the missing cache. |
Note that caches created this way may not be suitable for production usage (unlimited size and no eviction in particular) unless the cache provider explicitly provides a specific configuration for default caches. Ehcache, in particular, allows to set such default configuration using cache templates. See the Ehcache documentation for more details. |
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 built-in integration for Ehcache requires that the |
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:
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:
SingletonEhCacheRegionFactory
configuration<property
name="hibernate.cache.region.factory_class"
value="ehcache-singleton"/>
The SingletonEhCacheRegionFactory
configures a singleton net.sf.ehcache.CacheManager
(see CacheManager#create()),
shared among multiple SessionFactory
instances in the same JVM.
The Ehcache documentation recommends using multiple non-singleton |
13.10.2. Ehcache missing cache strategy
By default, the Ehcache region factory will log a warning when asked to create a cache that is not explicitly configured and pre-started in the underlying cache manager. Thus if you configure an entity type or a collection as cached, but do not configure the corresponding cache explicitly, one warning will be logged for each cache that was not configured explicitly.
You may change this behavior by setting the hibernate.cache.ehcache.missing_cache_strategy
property
to one of the following values:
Value | Description |
---|---|
|
Fail with an exception on missing caches. |
|
Default value. Create a new cache when a cache is not found (see |
|
Create a new cache when a cache is not found, without logging any warning about the missing cache. |
Note that caches created this way may be very badly configured (large size in particular) unless an appropriate |
13.11. Infinispan
Infinispan is a distributed in-memory key/value data store, available as a cache or data grid, which can be used as a Hibernate second-level cache provider as well.
It supports advanced functionality such as transactions, events, querying, distributed processing, off-heap and geographical failover.
For more details, check out the Infinispan User Guide.
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 |
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 can customize the listener interfaces (i.e., the LoadEvent
is processed by the registered implementation of the LoadEventListener
interface), in which case their implementations would
be responsible for processing the 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:
LoadListener
exampleEntityManagerFactory 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 two options:
-
you provide a custom
Interceptor
, which is taken into consideration by the default Hibernate event listeners. For example, theInterceptor#onSave()
method is invoked by HibernateAbstractSaveEventListener
. Or, theInterceptor#onLoad()
is called by theDefaultPreLoadEventListener
. -
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 theInterceptor#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.
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.
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 notifications of a particular entity lifecycle 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
@Entity(name = "Person")
@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 thejavax.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.
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;
//Getters and setters are omitted for brevity
}
@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
.
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
.
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]
14.6.1. Exclude default entity listeners
If you already registered a default entity listener, but you don’t want to apply it to a particular entity,
you can use the
@ExcludeDefaultListeners
and
@ExcludeSuperclassListeners
JPA annotations.
@ExcludeDefaultListeners
instructs the current class to ignore the default entity listeners for the current entity
while @ExcludeSuperclassListeners
is used to ignore the default entity listeners propagated to the BaseEntity
super-class.
@Entity(name = "Publisher")
@ExcludeDefaultListeners
@ExcludeSuperclassListeners
public static class Publisher extends BaseEntity {
@Id
private Long id;
private String name;
//Getters and setters omitted for brevity
}
When persisting a Publisher
entity,
the createdOn
is not going to be set by the onPersist
method of the DefaultEntityListener
because the Publisher
entity was marked with the @ExcludeDefaultListeners
and @ExcludeSuperclassListeners
annotations.
Publisher publisher = new Publisher();
publisher.setId( 1L );
publisher.setName( "Amazon" );
entityManager.persist( publisher );
insert
into
Publisher
(createdOn, updatedOn, name, id)
values
(?, ?, ?, ?)
-- binding parameter [1] as [TIMESTAMP] - [null]
-- binding parameter [2] as [TIMESTAMP] - [null]
-- binding parameter [3] as [VARCHAR] - [Amazon]
-- binding parameter [4] as [BIGINT] - [1]
15. HQL and JPQL
The Hibernate Query Language (HQL) and Java Persistence Query Language (JPQL) are both object model focused query languages similar in nature to SQL. JPQL is a heavily-inspired-by subset of HQL. A JPQL query is always a valid HQL query, the reverse is not true, however.
Both HQL and JPQL are non-type-safe ways to perform query operations. Criteria queries offer a type-safe approach to querying. See Criteria for more information.
15.1. Example domain model
To better understand the further HQL and JPQL examples, it’s time to familiarize the domain model entities that are used in all the examples features in this chapter.
@NamedQueries({
@NamedQuery(
name = "get_person_by_name",
query = "select p from Person p where name = :name"
)
,
@NamedQuery(
name = "get_read_only_person_by_name",
query = "select p from Person p where name = :name",
hints = {
@QueryHint(
name = "org.hibernate.readOnly",
value = "true"
)
}
)
})
@NamedStoredProcedureQueries(
@NamedStoredProcedureQuery(
name = "sp_person_phones",
procedureName = "sp_person_phones",
parameters = {
@StoredProcedureParameter(
name = "personId",
type = Long.class,
mode = ParameterMode.IN
),
@StoredProcedureParameter(
name = "personPhones",
type = Class.class,
mode = ParameterMode.REF_CURSOR
)
}
)
)
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
private String name;
private String nickName;
private String address;
@Temporal(TemporalType.TIMESTAMP )
private Date createdOn;
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
@OrderColumn(name = "order_id")
private List<Phone> phones = new ArrayList<>();
@ElementCollection
@MapKeyEnumerated(EnumType.STRING)
private Map<AddressType, String> addresses = new HashMap<>();
@Version
private int version;
//Getters and setters are omitted for brevity
}
public enum AddressType {
HOME,
OFFICE
}
@Entity
public class Partner {
@Id
@GeneratedValue
private Long id;
private String name;
@Version
private int version;
//Getters and setters are omitted for brevity
}
@Entity
public class Phone {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private Person person;
@Column(name = "phone_number")
private String number;
@Enumerated(EnumType.STRING)
@Column(name = "phone_type")
private PhoneType type;
@OneToMany(mappedBy = "phone", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Call> calls = new ArrayList<>( );
@OneToMany(mappedBy = "phone")
@MapKey(name = "timestamp")
@MapKeyTemporal(TemporalType.TIMESTAMP )
private Map<Date, Call> callHistory = new HashMap<>();
@ElementCollection
private List<Date> repairTimestamps = new ArrayList<>( );
//Getters and setters are omitted for brevity
}
public enum PhoneType {
LAND_LINE,
MOBILE;
}
@Entity
@Table(name = "phone_call")
public class Call {
@Id
@GeneratedValue
private Long id;
@ManyToOne
private Phone phone;
@Column(name = "call_timestamp")
private Date timestamp;
private int duration;
//Getters and setters are omitted for brevity
}
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Payment {
@Id
@GeneratedValue
private Long id;
private BigDecimal amount;
private boolean completed;
@ManyToOne
private Person person;
//Getters and setters are omitted for brevity
}
@Entity
public class CreditCardPayment extends Payment {
}
@Entity
public class WireTransferPayment extends Payment {
}
15.2. Query API
When using Hibernate, you can execute entity queries either via JPA or the Hibernate-specific API.
Since 5.2, the Hibernate Session
interface extends the JPA EntityManager
interface.
For this reason, the query API was also merged, and now the Hibernate org.hibernate.query.Query
interface extends the JPA javax.persistence.Query
.
Next, we are going to see how the query API differs between the standard JPA interfaces and the Hibernate-specific API.
15.2.1. JPA Query API
In JPA, the query is represented by javax.persistence.Query
or javax.persistence.TypedQuery
as obtained from the EntityManager
.
The create an inline Query
or TypedQuery
, you need to use the EntityManager#createQuery
method.
For named queries, the EntityManager#createNamedQuery
method is needed.
Query
or a TypedQuery
referenceQuery query = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.name like :name"
);
TypedQuery<Person> typedQuery = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.name like :name", Person.class
);
Query
or a TypedQuery
reference for a named query@NamedQuery(
name = "get_person_by_name",
query = "select p from Person p where name = :name"
)
Query query = entityManager.createNamedQuery( "get_person_by_name" );
TypedQuery<Person> typedQuery = entityManager.createNamedQuery(
"get_person_by_name", Person.class
);
Hibernate offers a specific
@NamedQuery
annotation
which provides ways to configure various query features, like flush mode, cacheability, time out interval.
Query
or a TypedQuery
reference for a named query@NamedQueries({
@NamedQuery(
name = "get_phone_by_number",
query = "select p " +
"from Phone p " +
"where p.number = :number",
timeout = 1,
readOnly = true
)
})
Phone phone = entityManager
.createNamedQuery( "get_phone_by_number", Phone.class )
.setParameter( "number", "123-456-7890" )
.getSingleResult();
The Query
interface can then be used to control the execution of the query.
For example, we may want to specify an execution timeout or control caching.
Query
usageQuery query = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.name like :name" )
// timeout - in milliseconds
.setHint( "javax.persistence.query.timeout", 2000 )
// flush only at commit time
.setFlushMode( FlushModeType.COMMIT );
For complete details, see the Query
Javadocs.
Many of the settings controlling the execution of the query are defined as hints.
JPA defines some standard hints (like timeout in the example), but most are provider specific.
Relying on provider specific hints limits your applications portability to some degree.
javax.persistence.query.timeout
-
Defines the query timeout, in milliseconds.
javax.persistence.fetchgraph
-
Defines a fetchgraph EntityGraph. Attributes explicitly specified as
AttributeNodes
are treated asFetchType.EAGER
(via join fetch or subsequent select). For details, see the EntityGraph discussions in Fetching. javax.persistence.loadgraph
-
Defines a loadgraph EntityGraph. Attributes explicitly specified as AttributeNodes are treated as
FetchType.EAGER
(via join fetch or subsequent select). Attributes that are not specified are treated asFetchType.LAZY
orFetchType.EAGER
depending on the attribute’s definition in metadata. For details, see the EntityGraph discussions in Fetching. org.hibernate.cacheMode
-
Defines the
CacheMode
to use. Seeorg.hibernate.query.Query#setCacheMode
. org.hibernate.cacheable
-
Defines whether the query is cacheable. true/false. See
org.hibernate.query.Query#setCacheable
. org.hibernate.cacheRegion
-
For queries that are cacheable, defines a specific cache region to use. See
org.hibernate.query.Query#setCacheRegion
. org.hibernate.comment
-
Defines the comment to apply to the generated SQL. See
org.hibernate.query.Query#setComment
. org.hibernate.fetchSize
-
Defines the JDBC fetch-size to use. See
org.hibernate.query.Query#setFetchSize
. org.hibernate.flushMode
-
Defines the Hibernate-specific
FlushMode
to use. Seeorg.hibernate.query.Query#setFlushMode.
If possible, prefer usingjavax.persistence.Query#setFlushMode
instead. org.hibernate.readOnly
-
Defines that entities and collections loaded by this query should be marked as read-only. See
org.hibernate.query.Query#setReadOnly
.
The final thing that needs to happen before the query can be executed is to bind the values for any defined parameters.
JPA defines a simplified set of parameter binding methods.
Essentially, it supports setting the parameter value (by name/position) and a specialized form for Calendar
/Date
types additionally accepting a TemporalType
.
Query query = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.name like :name" )
.setParameter( "name", "J%" );
// For generic temporal field types (e.g. `java.util.Date`, `java.util.Calendar`)
// we also need to provide the associated `TemporalType`
Query query = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.createdOn > :timestamp" )
.setParameter( "timestamp", timestamp, TemporalType.DATE );
JPQL-style positional parameters are declared using a question mark followed by an ordinal - ?1
, ?2
.
The ordinals start with 1.
Just like with named parameters, positional parameters can also appear multiple times in a query.
Query query = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.name like ?1" )
.setParameter( 1, "J%" );
It’s good practice not to mix parameter binding forms in a given query. |
In terms of execution, JPA Query
offers 3 different methods for retrieving a result set.
-
Query#getResultList()
- executes the select query and returns back the list of results. -
Query#getResultStream()
- executes the select query and returns back aStream
over the results. -
Query#getSingleResult()
- executes the select query and returns a single result. If there were more than one result an exception is thrown.
getResultList()
resultList<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.name like :name" )
.setParameter( "name", "J%" )
.getResultList();
getResultStream()
resulttry(Stream<Person> personStream = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.name like :name", Person.class )
.setParameter( "name", "J%" )
.getResultStream()) {
List<Person> persons = personStream
.skip( 5 )
.limit( 5 )
.collect( Collectors.toList() );
}
getSingleResult()
Person person = (Person) entityManager.createQuery(
"select p " +
"from Person p " +
"where p.name like :name" )
.setParameter( "name", "J%" )
.getSingleResult();
15.2.2. Hibernate Query API
In Hibernate, the HQL query is represented as org.hibernate.query.Query
which is obtained from a Session
.
If the HQL is a named query, Session#getNamedQuery
would be used; otherwise Session#createQuery
is needed.
Query
org.hibernate.query.Query query = session.createQuery(
"select p " +
"from Person p " +
"where p.name like :name"
);
Query
reference for a named queryorg.hibernate.query.Query query = session.getNamedQuery( "get_person_by_name" );
Not only was the JPQL syntax heavily inspired by HQL, but many of the JPA APIs were heavily inspired by Hibernate too.
The two |
The Query interface can then be used to control the execution of the query. For example, we may want to specify an execution timeout or control caching.
org.hibernate.query.Query query = session.createQuery(
"select p " +
"from Person p " +
"where p.name like :name" )
// timeout - in seconds
.setTimeout( 2 )
// write to L2 caches, but do not read from them
.setCacheMode( CacheMode.REFRESH )
// assuming query cache was enabled for the SessionFactory
.setCacheable( true )
// add a comment to the generated SQL if enabled via the hibernate.use_sql_comments configuration property
.setComment( "+ INDEX(p idx_person_name)" );
For complete details, see the Query Javadocs.
Query hints here are database query hints.
They are added directly to the generated SQL according to The JPA notion of query hints, on the other hand, refer to hints that target the provider (Hibernate). So even though they are called the same, be aware they have a very different purpose. Also, be aware that Hibernate query hints generally make the application non-portable across databases unless the code adding them first checks the Dialect. |
Flushing is covered in detail in Flushing. Locking is covered in detail in Locking. The concept of read-only state is covered in Persistence Contexts.
Hibernate also allows an application to hook into the process of building the query results via the org.hibernate.transform.ResultTransformer
contract.
See its Javadocs as well as the Hibernate-provided implementations for additional details.
The last thing that needs to happen before we can execute the query is to bind the values for any parameters defined in the query. Query defines many overloaded methods for this purpose. The most generic form takes the value as well as the Hibernate Type.
org.hibernate.query.Query query = session.createQuery(
"select p " +
"from Person p " +
"where p.name like :name" )
.setParameter( "name", "J%", StringType.INSTANCE );
Hibernate generally understands the expected type of the parameter given its context in the query.
In the previous example since we are using the parameter in a LIKE
comparison against a String-typed attribute Hibernate would automatically infer the type; so the above could be simplified.
org.hibernate.query.Query query = session.createQuery(
"select p " +
"from Person p " +
"where p.name like :name" )
.setParameter( "name", "J%" );
There are also short hand forms for binding common types such as strings, booleans, integers, etc.
org.hibernate.query.Query query = session.createQuery(
"select p " +
"from Person p " +
"where p.name like :name " +
" and p.createdOn > :timestamp" )
.setParameter( "name", "J%" )
.setParameter( "timestamp", timestamp, TemporalType.TIMESTAMP);
Traditionally, Hibernate used to support a JDBC positional parameter syntax form via a There was no way to relate two such positional parameters as being "the same" aside from binding the same value to each and, for this reason, this form is no longer supported.
|
In terms of execution, Hibernate offers 4 different methods. The 2 most commonly used are
-
Query#list
- executes the select query and returns back the list of results. -
Query#uniqueResult
- executes the select query and returns the single result. If there were more than one result an exception is thrown.
list()
resultList<Person> persons = session.createQuery(
"select p " +
"from Person p " +
"where p.name like :name" )
.setParameter( "name", "J%" )
.list();
It is also possible to extract a single result from a Query
.
uniqueResult()
Person person = (Person) session.createQuery(
"select p " +
"from Person p " +
"where p.name like :name" )
.setParameter( "name", "J%" )
.uniqueResult();
If the unique result is used often and the attributes upon which it is based are unique, you may want to consider mapping a natural-id and using the natural-id loading API. See the Natural Ids for more information on this topic. |
15.2.3. Query scrolling
Hibernate offers additional, specialized methods for scrolling the query and handling results using a server-side cursor.
Query#scroll
works in tandem with the JDBC notion of a scrollable ResultSet
.
The Query#scroll
method is overloaded:
-
The main form accepts a single argument of type
org.hibernate.ScrollMode
which indicates the type of scrolling to be used. See the Javadocs for the details on each. -
The second form takes no argument and will use the
ScrollMode
indicated byDialect#defaultScrollMode
.
Query#scroll
returns a org.hibernate.ScrollableResults
which wraps the underlying JDBC (scrollable) ResultSet
and provides access to the results.
Unlike a typical forward-only ResultSet
, the ScrollableResults
allows you to navigate the ResultSet
in any direction.
ResultSet
containing entitiestry ( ScrollableResults scrollableResults = session.createQuery(
"select p " +
"from Person p " +
"where p.name like :name" )
.setParameter( "name", "J%" )
.scroll()
) {
while(scrollableResults.next()) {
Person person = (Person) scrollableResults.get()[0];
process(person);
}
}
Since this form holds the JDBC If left unclosed by the application, Hibernate will automatically close the underlying resources (e.g. However, it is good practice to close the |
If you plan to use |
Hibernate also supports Query#iterate
, which is intended for loading entities when it is known that the loaded entries are already stored in the second-level cache.
The idea behind iterate is that just the matching identifiers will be obtained in the SQL query.
From these the identifiers are resolved by second-level cache lookup.
If these second-level cache lookups fail, additional queries will need to be issued against the database.
This operation can perform significantly better for loading large numbers of entities that for certain already exist in the second-level cache. In cases where many of the entities do not exist in the second-level cache, this operation will almost definitely perform worse. |
The Iterator
returned from Query#iterate
is actually a specially typed Iterator: org.hibernate.engine.HibernateIterator
.
It is specialized to expose a close()
method (again, inherited from java.io.Closeable
).
When you are done with this Iterator
you should close it, either by casting to HibernateIterator
or Closeable
, or by calling Hibernate#close(java.util.Iterator)
.
Since 5.2, Hibernate offers support for returning a Stream
which can be later used to transform the underlying ResultSet
.
Internally, the stream()
behaves like a Query#scroll
and the underlying result is backed by a ScrollableResults
.
Fetching a projection using the Query#stream
method can be done as follows:
stream()
using a projection result typetry ( Stream<Object[]> persons = session.createQuery(
"select p.name, p.nickName " +
"from Person p " +
"where p.name like :name" )
.setParameter( "name", "J%" )
.stream() ) {
persons
.map( row -> new PersonNames(
(String) row[0],
(String) row[1] ) )
.forEach( this::process );
}
When fetching a single result, like a Person
entity, instead of a Stream<Object[]>
, Hibernate is going to figure out the actual type, so the result is a Stream<Person>
.
stream()
using an entity result typetry( Stream<Person> persons = session.createQuery(
"select p " +
"from Person p " +
"where p.name like :name" )
.setParameter( "name", "J%" )
.stream() ) {
Map<Phone, List<Call>> callRegistry = persons
.flatMap( person -> person.getPhones().stream() )
.flatMap( phone -> phone.getCalls().stream() )
.collect( Collectors.groupingBy( Call::getPhone ) );
process(callRegistry);
}
Just like with |
15.2.4. Query streaming
Since version 2.2, the JPA Query
interface offers support for returning a Stream
via the getResultStream
method.
Just like the scroll
method, you can use a try-with-resources block to close the Stream
prior to closing the currently running Persistence Context.
Since Hibernate 5.4, the Stream
is also closed when calling a terminal operation,
as illustrated by the following example.
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.name like :name", Person.class )
.setParameter( "name", "J%" )
.getResultStream()
.skip( 5 )
.limit( 5 )
.collect( Collectors.toList() );
The Stream
is closed automatically after calling the collect
method,
since there is no reason to keep the underlying JDBC ResultSet
open
if the Stream
cannot be reused.
15.3. Case Sensitivity
With the exception of names of Java classes and properties, queries are case-insensitive.
So SeLeCT
is the same as sELEct
is the same as SELECT
, but org.hibernate.eg.FOO
and org.hibernate.eg.Foo
are different, as are foo.barSet
and foo.BARSET
.
This documentation uses lowercase keywords as a convention in query examples. |
15.4. Statement types
Both HQL and JPQL allow SELECT
, UPDATE
and DELETE
statements to be performed.
HQL additionally allows INSERT
statements, in a form similar to a SQL INSERT FROM SELECT
.
Care should be taken as to when an
— Section 4.10 of the JPA 2.0 Specification
|
15.5. Select statements
The BNF for SELECT
statements in HQL is:
select_statement :: =
[select_clause]
from_clause
[where_clause]
[groupby_clause]
[having_clause]
[orderby_clause]
The simplest possible HQL SELECT
statement is of the form:
List<Person> persons = session.createQuery(
"from Person" )
.list();
The select statement in JPQL is exactly the same as for HQL except that JPQL requires a
Even though HQL does not require the presence of a It is usually better to explicitly specify intent.
Hibernate does not actually enforce that a |
15.6. Update statements
The BNF for UPDATE
statements is the same in HQL and JPQL:
update_statement ::=
update_clause [where_clause]
update_clause ::=
UPDATE entity_name [[AS] identification_variable]
SET update_item {, update_item}*
update_item ::=
[identification_variable.]{state_field | single_valued_object_field} = new_value
new_value ::=
scalar_expression | simple_entity_expression | NULL
UPDATE
statements, by default, do not affect the version
or the timestamp
attribute values for the affected entities.
However, you can force Hibernate to set the version
or timestamp
attribute values through the use of a versioned update
.
This is achieved by adding the VERSIONED
keyword after the UPDATE
keyword.
Versioned updates is a Hibernate-specific feature and will not work in a portable manner. Custom version types, |
An UPDATE
statement is executed using the executeUpdate()
of either org.hibernate.query.Query
or javax.persistence.Query
.
The method is named for those familiar with the JDBC executeUpdate()
on java.sql.PreparedStatement
.
The int
value returned by the executeUpdate()
method indicates the number of entities affected by the operation.
This may or may not correlate to the number of rows affected in the database.
An HQL bulk operation might result in multiple actual SQL statements being executed (for joined-subclass, for example).
The returned number indicates the number of actual entities affected by the statement.
Using a JOINED inheritance hierarchy, a delete against one of the subclasses may actually result in deletes against not just the table to which that subclass is mapped, but also the "root" table and tables "in between".
int updatedEntities = entityManager.createQuery(
"update Person p " +
"set p.name = :newName " +
"where p.name = :oldName" )
.setParameter( "oldName", oldName )
.setParameter( "newName", newName )
.executeUpdate();
int updatedEntities = session.createQuery(
"update Person " +
"set name = :newName " +
"where name = :oldName" )
.setParameter( "oldName", oldName )
.setParameter( "newName", newName )
.executeUpdate();
int updatedEntities = session.createQuery(
"update versioned Person " +
"set name = :newName " +
"where name = :oldName" )
.setParameter( "oldName", oldName )
.setParameter( "newName", newName )
.executeUpdate();
Neither |
15.7. Delete statements
The BNF for DELETE
statements is the same in HQL and JPQL:
delete_statement ::=
delete_clause [where_clause]
delete_clause ::=
DELETE FROM entity_name [[AS] identification_variable]
A DELETE
statement is also executed using the executeUpdate()
method of either org.hibernate.query.Query
or javax.persistence.Query
.
15.8. Insert statements
HQL adds the ability to define INSERT
statements as well.
There is no JPQL equivalent to HQL-style INSERT statements. |
The BNF for an HQL INSERT
statement is:
insert_statement ::=
insert_clause select_statement
insert_clause ::=
INSERT INTO entity_name (attribute_list)
attribute_list ::=
state_field[, state_field ]*
The attribute_list
is analogous to the column specification
in the SQL INSERT
statement.
For entities involved in mapped inheritance, only attributes directly defined on the named entity can be used in the attribute_list
.
Superclass properties are not allowed and subclass properties do not make sense.
In other words, INSERT
statements are inherently non-polymorphic.
select_statement
can be any valid HQL select query, with the caveat that the return types must match the types expected by the insert.
Currently, this is checked during query compilation rather than allowing the check to delegate to the database.
This may cause problems between Hibernate Types which are equivalent as opposed to equal.
For example, this might lead to issues with mismatches between an attribute mapped as a org.hibernate.type.DateType
and an attribute defined as a org.hibernate.type.TimestampType
,
even though the database might not make a distinction or might be able to handle the conversion.
For the id attribute, the insert statement gives you two options.
You can either explicitly specify the id property in the attribute_list
, in which case its value is taken from the corresponding select expression, or omit it from the attribute_list
in which case a generated value is used.
This latter option is only available when using id generators that operate "in the database"; attempting to use this option with any "in memory" type generators will cause an exception during parsing.
For optimistic locking attributes, the insert statement again gives you two options.
You can either specify the attribute in the attribute_list
in which case its value is taken from the corresponding select expressions or omit it from the attribute_list
in which case the seed value
defined by the corresponding org.hibernate.type.VersionType
is used.
int insertedEntities = session.createQuery(
"insert into Partner (id, name) " +
"select p.id, p.name " +
"from Person p ")
.executeUpdate();
15.9. The FROM
clause
The FROM
clause is responsible for defining the scope of object model types available to the rest of the query.
It is also responsible for defining all the "identification variables" available to the rest of the query.
15.10. Identification variables
Identification variables are often referred to as aliases.
References to object model classes in the FROM
clause can be associated with an identification variable that can then be used to refer to that type throughout the rest of the query.
In most cases declaring an identification variable is optional, though it is usually good practice to declare them.
An identification variable must follow the rules for Java identifier validity.
According to JPQL, identification variables must be treated as case-insensitive. Good practice says you should use the same case throughout a query to refer to a given identification variable. In other words, JPQL says they can be case-insensitive and so Hibernate must be able to treat them as such, but this does not make it good practice.
15.11. Root entity references
A root entity reference, or what JPA calls a range variable declaration
, is specifically a reference to a mapped entity type from the application.
It cannot name component/embeddable types.
And associations, including collections, are handled in a different manner, as later discussed.
The BNF for a root entity reference is:
root_entity_reference ::=
entity_name [AS] identification_variable
List<Person> persons = entityManager.createQuery(
"select p " +
"from org.hibernate.userguide.model.Person p", Person.class )
.getResultList();
We see that the query is defining a root entity reference to the org.hibernate.userguide.model.Person
object model type.
Additionally, it declares an alias of p
to that org.hibernate.userguide.model.Person
reference, which is the identification variable.
Usually, the root entity reference represents just the entity name
rather than the entity class FQN (fully-qualified name).
By default, the entity name is the unqualified entity class name, here Person
.
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p", Person.class )
.getResultList();
Multiple root entity references can also be specified, even when naming the same entity.
List<Object[]> persons = entityManager.createQuery(
"select distinct pr, ph " +
"from Person pr, Phone ph " +
"where ph.person = pr and ph is not null", Object[].class)
.getResultList();
List<Person> persons = entityManager.createQuery(
"select distinct pr1 " +
"from Person pr1, Person pr2 " +
"where pr1.id <> pr2.id " +
" and pr1.address = pr2.address " +
" and pr1.createdOn < pr2.createdOn", Person.class )
.getResultList();
15.12. Explicit joins
The FROM
clause can also contain explicit relationship joins using the join
keyword.
These joins can be either inner
or left outer
style joins.
List<Person> persons = entityManager.createQuery(
"select distinct pr " +
"from Person pr " +
"join pr.phones ph " +
"where ph.type = :phoneType", Person.class )
.setParameter( "phoneType", PhoneType.MOBILE )
.getResultList();
// same query but specifying join type as 'inner' explicitly
List<Person> persons = entityManager.createQuery(
"select distinct pr " +
"from Person pr " +
"inner join pr.phones ph " +
"where ph.type = :phoneType", Person.class )
.setParameter( "phoneType", PhoneType.MOBILE )
.getResultList();
List<Person> persons = entityManager.createQuery(
"select distinct pr " +
"from Person pr " +
"left join pr.phones ph " +
"where ph is null " +
" or ph.type = :phoneType", Person.class )
.setParameter( "phoneType", PhoneType.LAND_LINE )
.getResultList();
// functionally the same query but using the 'left outer' phrase
List<Person> persons = entityManager.createQuery(
"select distinct pr " +
"from Person pr " +
"left outer join pr.phones ph " +
"where ph is null " +
" or ph.type = :phoneType", Person.class )
.setParameter( "phoneType", PhoneType.LAND_LINE )
.getResultList();
HQL also defines a WITH
clause to qualify the join conditions.
The HQL-style WITH keyword is specific to Hibernate. JPQL defines the |
WITH
clause join exampleList<Object[]> personsAndPhones = session.createQuery(
"select pr.name, ph.number " +
"from Person pr " +
"left join pr.phones ph with ph.type = :phoneType " )
.setParameter( "phoneType", PhoneType.LAND_LINE )
.list();
ON
clause join exampleList<Object[]> personsAndPhones = entityManager.createQuery(
"select pr.name, ph.number " +
"from Person pr " +
"left join pr.phones ph on ph.type = :phoneType " )
.setParameter( "phoneType", PhoneType.LAND_LINE )
.getResultList();
The important distinction is that in the generated SQL the conditions of the |
The distinction in this specific example is probably not that significant.
The with clause
is sometimes necessary for more complicated queries.
Explicit joins may reference association or component/embedded attributes. In the case of component/embedded attributes, the join is simply logical and does not correlate to a physical (SQL) join. For further information about collection-valued association references, see Collection member references.
An important use case for explicit joins is to define FETCH JOIN
s which override the laziness of the joined association.
As an example, given an entity named Person
with a collection-valued association named phones
, the JOIN FETCH
will also load the child collection in the same SQL query:
// functionally the same query but using the 'left outer' phrase
List<Person> persons = entityManager.createQuery(
"select distinct pr " +
"from Person pr " +
"left join fetch pr.phones ", Person.class )
.getResultList();
As you can see from the example, a fetch join is specified by injecting the keyword fetch
after the keyword join
.
In the example, we used a left outer join because we also wanted to return customers who have no orders.
Inner joins can also be fetched, but inner joins filter out the root entity. In the example, using an inner join instead would have resulted in customers without any orders being filtered out of the result.
Fetch joins are not valid in sub-queries. Care should be taken when fetch joining a collection-valued association which is in any way further restricted (the fetched collection will be restricted too). For this reason, it is usually considered best practice not to assign an identification variable to fetched joins except for the purpose of specifying nested fetch joins. Fetch joins should not be used in paged queries (e.g. |
15.13. Implicit joins (path expressions)
Another means of adding to the scope of object model types available to the query is through the use of implicit joins or path expressions.
List<Phone> phones = entityManager.createQuery(
"select ph " +
"from Phone ph " +
"where ph.person.address = :address ", Phone.class )
.setParameter( "address", address )
.getResultList();
// same as
List<Phone> phones = entityManager.createQuery(
"select ph " +
"from Phone ph " +
"join ph.person pr " +
"where pr.address = :address ", Phone.class )
.setParameter( "address", address)
.getResultList();
An implicit join always starts from an identification variable
, followed by the navigation operator ( .
),
followed by an attribute for the object model type referenced by the initial identification variable
.
In the example, the initial identification variable
is ph
which refers to the Phone
entity.
The ph.person
reference then refers to the person
attribute of the Phone
entity.
person
is an association type so we further navigate to its age attribute.
If the attribute represents an entity association (non-collection) or a component/embedded, that reference can be further navigated. Basic values and collection-valued associations cannot be further navigated. |
As shown in the example, implicit joins can appear outside the FROM clause
.
However, they affect the FROM clause
.
Implicit joins are always treated as inner joins. Multiple references to the same implicit join always refer to the same logical and physical (SQL) join. |
List<Phone> phones = entityManager.createQuery(
"select ph " +
"from Phone ph " +
"where ph.person.address = :address " +
" and ph.person.createdOn > :timestamp", Phone.class )
.setParameter( "address", address )
.setParameter( "timestamp", timestamp )
.getResultList();
//same as
List<Phone> phones = entityManager.createQuery(
"select ph " +
"from Phone ph " +
"inner join ph.person pr " +
"where pr.address = :address " +
" and pr.createdOn > :timestamp", Phone.class )
.setParameter( "address", address )
.setParameter( "timestamp", timestamp )
.getResultList();
Just as with explicit joins, implicit joins may reference association or component/embedded attributes. For further information about collection-valued association references, see Collection member references.
In the case of component/embedded attributes, the join is simply logical and does not correlate to a physical (SQL) join. Unlike explicit joins, however, implicit joins may also reference basic state fields as long as the path expression ends there.
15.14. Distinct
For JPQL and HQL, DISTINCT
has two meanings:
-
It can be passed to the database so that duplicates are removed from a result set
-
It can be used to filter out the same parent entity references when join fetching a child collection
15.14.1. Using DISTINCT with SQL projections
For SQL projections, DISTINCT
needs to be passed to the database because the duplicated entries need to be filtered out before being returned to the database client.
List<String> lastNames = entityManager.createQuery(
"select distinct p.lastName " +
"from Person p", String.class)
.getResultList();
When running the query above, Hibernate generates the following SQL query:
SELECT DISTINCT
p.last_name as col_0_0_
FROM person p
For this particular use case, passing the DISTINCT
keyword from JPQL/HQL to the database is the right thing to do.
15.14.2. Using DISTINCT with entity queries
DISTINCT
can also be used to filter out entity object references when fetching a child association along with the parent entities.
List<Person> authors = entityManager.createQuery(
"select distinct p " +
"from Person p " +
"left join fetch p.books", Person.class)
.getResultList();
In this case, DISTINCT
is used because there can be multiple Books
entities associated with a given Person
.
If in the database there are 3 Person
s in the database and each person has 2 Book
s, without DISTINCT
this query will return 6 Person
s since
the SQL-level result-set size is given by the number of joined Book
records.
However, the DISTINCT
keyword is passed to the database as well:
SELECT DISTINCT
p.id as id1_1_0_,
b.id as id1_0_1_,
p.first_name as first_na2_1_0_,
p.last_name as last_nam3_1_0_,
b.author_id as author_i3_0_1_,
b.title as title2_0_1_,
b.author_id as author_i3_0_0__,
b.id as id1_0_0__
FROM person p
LEFT OUTER JOIN book b ON p.id=b.author_id
In this case, the DISTINCT
SQL keyword is undesirable since it does a redundant result set sorting, as explained in this blog post.
To fix this issue, Hibernate 5.2.2 added support for the HINT_PASS_DISTINCT_THROUGH
entity query hint:
List<Person> authors = entityManager.createQuery(
"select distinct p " +
"from Person p " +
"left join fetch p.books", Person.class)
.setHint( QueryHints.HINT_PASS_DISTINCT_THROUGH, false )
.getResultList();
With this entity query hint, Hibernate will not pass the DISTINCT
keyword to the SQL query:
SELECT
p.id as id1_1_0_,
b.id as id1_0_1_,
p.first_name as first_na2_1_0_,
p.last_name as last_nam3_1_0_,
b.author_id as author_i3_0_1_,
b.title as title2_0_1_,
b.author_id as author_i3_0_0__,
b.id as id1_0_0__
FROM person p
LEFT OUTER JOIN book b ON p.id=b.author_id
When using the HINT_PASS_DISTINCT_THROUGH
entity query hint, Hibernate can still remove the duplicated parent-side entities from the query result.
15.15. Collection member references
References to collection-valued associations actually refer to the values of that collection.
List<Phone> phones = entityManager.createQuery(
"select ph " +
"from Person pr " +
"join pr.phones ph " +
"join ph.calls c " +
"where pr.address = :address " +
" and c.duration > :duration", Phone.class )
.setParameter( "address", address )
.setParameter( "duration", duration )
.getResultList();
// alternate syntax
List<Phone> phones = session.createQuery(
"select ph " +
"from Person pr, " +
"in (pr.phones) ph, " +
"in (ph.calls) c " +
"where pr.address = :address " +
" and c.duration > :duration" )
.setParameter( "address", address )
.setParameter( "duration", duration )
.list();
In the example, the identification variable ph
actually refers to the object model type Phone
, which is the type of the elements of the Person#phones
association.
The example also shows the alternate syntax for specifying collection association joins using the IN
syntax.
Both forms are equivalent.
Which form an application chooses to use is simply a matter of taste.
15.16. Special case - qualified path expressions
We said earlier that collection-valued associations actually refer to the values of that collection. Based on the type of collection, there are also a set of explicit qualification expressions available.
@OneToMany(mappedBy = "phone")
@MapKey(name = "timestamp")
@MapKeyTemporal(TemporalType.TIMESTAMP )
private Map<Date, Call> callHistory = new HashMap<>();
// select all the calls (the map value) for a given Phone
List<Call> calls = entityManager.createQuery(
"select ch " +
"from Phone ph " +
"join ph.callHistory ch " +
"where ph.id = :id ", Call.class )
.setParameter( "id", id )
.getResultList();
// same as above
List<Call> calls = entityManager.createQuery(
"select value(ch) " +
"from Phone ph " +
"join ph.callHistory ch " +
"where ph.id = :id ", Call.class )
.setParameter( "id", id )
.getResultList();
// select all the Call timestamps (the map key) for a given Phone
List<Date> timestamps = entityManager.createQuery(
"select key(ch) " +
"from Phone ph " +
"join ph.callHistory ch " +
"where ph.id = :id ", Date.class )
.setParameter( "id", id )
.getResultList();
// select all the Call and their timestamps (the 'Map.Entry') for a given Phone
List<Map.Entry<Date, Call>> callHistory = entityManager.createQuery(
"select entry(ch) " +
"from Phone ph " +
"join ph.callHistory ch " +
"where ph.id = :id " )
.setParameter( "id", id )
.getResultList();
// Sum all call durations for a given Phone of a specific Person
Long duration = entityManager.createQuery(
"select sum(ch.duration) " +
"from Person pr " +
"join pr.phones ph " +
"join ph.callHistory ch " +
"where ph.id = :id " +
" and index(ph) = :phoneIndex", Long.class )
.setParameter( "id", id )
.setParameter( "phoneIndex", phoneIndex )
.getSingleResult();
- VALUE
-
Refers to the collection value. Same as not specifying a qualifier. Useful to explicitly show intent. Valid for any type of collection-valued reference.
- INDEX
-
According to HQL rules, this is valid for both
Maps
andLists
which specify ajavax.persistence.OrderColumn
annotation to refer to theMap
key or theList
position (aka theOrderColumn
value). JPQL however, reserves this for use in theList
case and addsKEY
for theMap
case. Applications interested in JPA provider portability should be aware of this distinction. - KEY
-
Valid only for
Maps
. Refers to the map’s key. If the key is itself an entity, it can be further navigated. - ENTRY
-
Only valid for
Maps
. Refers to the map’s logicaljava.util.Map.Entry
tuple (the combination of its key and value).ENTRY
is only valid as a terminal path and it’s applicable to theSELECT
clause only.
See Collection-related expressions for additional details on collection-related expressions.
15.17. Polymorphism
HQL and JPQL queries are inherently polymorphic.
List<Payment> payments = entityManager.createQuery(
"select p " +
"from Payment p ", Payment.class )
.getResultList();
This query names the Payment
entity explicitly.
However, all subclasses of Payment
are also available to the query.
So, if the CreditCardPayment
and WireTransferPayment
entities extend the Payment
class, all three types would be available to the entity query,
and the query would return instances of all three.
This behavior can be altered in two ways:
-
by limiting the query to select only from the subclass entity.
-
by using either the
org.hibernate.annotations.Polymorphism
annotation (global, and Hibernate-specific). See the@Polymorphism
section for more info about this use case.
The HQL query It returns every object of every entity type defined by your application mappings. |
15.19. Identification variable
See The FROM
clause.
15.20. Path expressions
Again, see The FROM
clause.
15.21. Literals
String literals are enclosed in single quotes. To escape a single quote within a string literal, use double single quotes.
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.name like 'Joe'", Person.class)
.getResultList();
// Escaping quotes
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.name like 'Joe''s'", Person.class)
.getResultList();
Numeric literals are allowed in a few different forms.
// simple integer literal
Person person = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.id = 1", Person.class)
.getSingleResult();
// simple integer literal, typed as a long
Person person = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.id = 1L", Person.class)
.getSingleResult();
// decimal notation
List<Call> calls = entityManager.createQuery(
"select c " +
"from Call c " +
"where c.duration > 100.5", Call.class )
.getResultList();
// decimal notation, typed as a float
List<Call> calls = entityManager.createQuery(
"select c " +
"from Call c " +
"where c.duration > 100.5F", Call.class )
.getResultList();
// scientific notation
List<Call> calls = entityManager.createQuery(
"select c " +
"from Call c " +
"where c.duration > 1e+2", Call.class )
.getResultList();
// scientific notation, typed as a float
List<Call> calls = entityManager.createQuery(
"select c " +
"from Call c " +
"where c.duration > 1e+2F", Call.class )
.getResultList();
In the scientific notation form, the Specific typing can be achieved through the use of the same suffix approach specified by Java.
So, The boolean literals are Enums can even be referenced as literals. The fully-qualified enum class name must be used. HQL can also handle constants in the same manner, though JPQL does not define that as being supported. Entity names can also be used as literal. See Entity type. Date/time literals can be specified using the JDBC escape syntax:
These Date/time literals only work if the underlying JDBC driver supports them. |
15.22. Arithmetic
Arithmetic operations also represent valid expressions.
// select clause date/time arithmetic operations
Long duration = entityManager.createQuery(
"select sum(ch.duration) * :multiplier " +
"from Person pr " +
"join pr.phones ph " +
"join ph.callHistory ch " +
"where ph.id = 1L ", Long.class )
.setParameter( "multiplier", 1000L )
.getSingleResult();
// select clause date/time arithmetic operations
Integer years = entityManager.createQuery(
"select year( current_date() ) - year( p.createdOn ) " +
"from Person p " +
"where p.id = 1L", Integer.class )
.getSingleResult();
// where clause arithmetic operations
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where year( current_date() ) - year( p.createdOn ) > 1", Person.class )
.getResultList();
The following rules apply to the result of arithmetic operations:
-
If either of the operands is
Double
/double
, the result is aDouble
-
else, if either of the operands is
Float
/float
, the result is aFloat
-
else, if either operand is
BigDecimal
, the result isBigDecimal
-
else, if either operand is
BigInteger
, the result isBigInteger
(except for division, in which case the result type is not further defined) -
else, if either operand is
Long
/long
, the result isLong
(except for division, in which case the result type is not further defined) -
else, (the assumption being that both operands are of integral type) the result is
Integer
(except for division, in which case the result type is not further defined)
Date arithmetic is also supported, albeit in a more limited fashion.
This is due to differences in database support and partly to the lack of support for INTERVAL
definition in the query language itself.
15.23. Concatenation (operation)
HQL defines a concatenation operator in addition to supporting the concatenation (CONCAT
) function.
This is not defined by JPQL, so portable applications should avoid its use.
The concatenation operator is taken from the SQL concatenation operator (e.g ||
).
String name = entityManager.createQuery(
"select 'Customer ' || p.name " +
"from Person p " +
"where p.id = 1", String.class )
.getSingleResult();
See Scalar functions for details on the concat()
function.
15.24. Aggregate functions
Aggregate functions are also valid expressions in HQL and JPQL. The semantics is the same as their SQL counterpart. The supported aggregate functions are:
COUNT
(including distinct/all qualifiers)-
The result type is always
Long
. AVG
-
The result type is always
Double
. MIN
-
The result type is the same as the argument type.
MAX
-
The result type is the same as the argument type.
SUM
-
The result type of the
SUM()
function depends on the type of the values being summed. For integral values (other thanBigInteger
), the result type isLong
.
For floating point values (other than BigDecimal
) the result type is Double
.
For BigInteger
values, the result type is BigInteger
. For BigDecimal
values, the result type is BigDecimal
.
Object[] callStatistics = entityManager.createQuery(
"select " +
" count(c), " +
" sum(c.duration), " +
" min(c.duration), " +
" max(c.duration), " +
" avg(c.duration) " +
"from Call c ", Object[].class )
.getSingleResult();
Long phoneCount = entityManager.createQuery(
"select count( distinct c.phone ) " +
"from Call c ", Long.class )
.getSingleResult();
List<Object[]> callCount = entityManager.createQuery(
"select p.number, count(c) " +
"from Call c " +
"join c.phone p " +
"group by p.number", Object[].class )
.getResultList();
Aggregations often appear with grouping. For information on grouping see Group by.
15.25. Scalar functions
Both HQL and JPQL define some standard functions that are available regardless of the underlying database in use. HQL can also understand additional functions defined by the Dialect as well as the application.
15.26. JPQL standardized functions
Here is the list of functions defined as supported by JPQL. Applications interested in remaining portable between JPA providers should stick to these functions.
- CONCAT
-
String concatenation function. Variable argument length of 2 or more string values to be concatenated together.
List<String> callHistory = entityManager.createQuery(
"select concat( p.number, ' : ' , cast(c.duration as string) ) " +
"from Call c " +
"join c.phone p", String.class )
.getResultList();
- SUBSTRING
-
Extracts a portion of a string value. The second argument denotes the starting position, where 1 is the first character of the string. The third (optional) argument denotes the length.
List<String> prefixes = entityManager.createQuery(
"select substring( p.number, 1, 2 ) " +
"from Call c " +
"join c.phone p", String.class )
.getResultList();
- UPPER
-
Upper cases the specified string.
List<String> names = entityManager.createQuery(
"select upper( p.name ) " +
"from Person p ", String.class )
.getResultList();
- LOWER
-
Lower cases the specified string.
List<String> names = entityManager.createQuery(
"select lower( p.name ) " +
"from Person p ", String.class )
.getResultList();
- TRIM
-
Follows the semantics of the SQL trim function.
List<String> names = entityManager.createQuery(
"select trim( p.name ) " +
"from Person p ", String.class )
.getResultList();
- LENGTH
-
Returns the length of a string.
List<Integer> lengths = entityManager.createQuery(
"select length( p.name ) " +
"from Person p ", Integer.class )
.getResultList();
- LOCATE
-
Locates a string within another string. The third argument (optional) is used to denote a position from which to start looking.
List<Integer> sizes = entityManager.createQuery(
"select locate( 'John', p.name ) " +
"from Person p ", Integer.class )
.getResultList();
- ABS
-
Calculates the mathematical absolute value of a numeric value.
List<Integer> abs = entityManager.createQuery(
"select abs( c.duration ) " +
"from Call c ", Integer.class )
.getResultList();
- MOD
-
Calculates the remainder of dividing the first argument by the second.
List<Integer> mods = entityManager.createQuery(
"select mod( c.duration, 10 ) " +
"from Call c ", Integer.class )
.getResultList();
- SQRT
-
Calculates the mathematical square root of a numeric value.
List<Double> sqrts = entityManager.createQuery(
"select sqrt( c.duration ) " +
"from Call c ", Double.class )
.getResultList();
- CURRENT_DATE
-
Returns the database current date.
List<Call> calls = entityManager.createQuery(
"select c " +
"from Call c " +
"where c.timestamp = current_date", Call.class )
.getResultList();
- CURRENT_TIME
-
Returns the database current time.
List<Call> calls = entityManager.createQuery(
"select c " +
"from Call c " +
"where c.timestamp = current_time", Call.class )
.getResultList();
- CURRENT_TIMESTAMP
-
Returns the database current timestamp.
List<Call> calls = entityManager.createQuery(
"select c " +
"from Call c " +
"where c.timestamp = current_timestamp", Call.class )
.getResultList();
15.27. HQL functions
Beyond the JPQL standardized functions, HQL makes some additional functions available regardless of the underlying database in use.
- BIT_LENGTH
-
Returns the length of binary data.
List<Number> bits = entityManager.createQuery(
"select bit_length( c.duration ) " +
"from Call c ", Number.class )
.getResultList();
- CAST
-
Performs a SQL cast. The cast target should name the Hibernate mapping type to use. See the data types chapter on for more information.
List<String> durations = entityManager.createQuery(
"select cast( c.duration as string ) " +
"from Call c ", String.class )
.getResultList();
- EXTRACT
-
Performs a SQL extraction on datetime values. An extraction extracts parts of the datetime (the year, for example).
List<Integer> years = entityManager.createQuery(
"select extract( YEAR from c.timestamp ) " +
"from Call c ", Integer.class )
.getResultList();
See the abbreviated forms below.
- YEAR
-
Abbreviated extract form for extracting the year.
List<Integer> years = entityManager.createQuery(
"select year( c.timestamp ) " +
"from Call c ", Integer.class )
.getResultList();
- MONTH
-
Abbreviated extract form for extracting the month.
- DAY
-
Abbreviated extract form for extracting the day.
- HOUR
-
Abbreviated extract form for extracting the hour.
- MINUTE
-
Abbreviated extract form for extracting the minute.
- SECOND
-
Abbreviated extract form for extracting the second.
- STR
-
Abbreviated form for casting a value as character data.
List<String> timestamps = entityManager.createQuery(
"select str( c.timestamp ) " +
"from Call c ", String.class )
.getResultList();
List<String> timestamps = entityManager.createQuery(
"select str( cast(duration as float) / 60, 4, 2 ) " +
"from Call c ", String.class )
.getResultList();
15.28. User-defined functions
Hibernate Dialects can register additional functions known to be available for that particular database product. These functions are also available in HQL (and JPQL, though only when using Hibernate as the JPA provider, obviously). However, they would only be available when using that database Dialect. Applications that aim for database portability should avoid using functions in this category.
Application developers can also supply their own set of functions.
This would usually represent either user-defined SQL functions or aliases for snippets of SQL.
Such function declarations are made by using the addSqlFunction()
method of
the org.hibernate.boot.MetadataBuilder
or the legacy org.hibernate.cfg.Configuration
.
Now, let’s assume we have the following apply_vat
PostgreSQL user-defined function:
statement.executeUpdate(
"CREATE OR REPLACE FUNCTION apply_vat(integer) RETURNS integer " +
" AS 'select cast(($1 * 1.2) as integer);' " +
" LANGUAGE SQL " +
" IMMUTABLE " +
" RETURNS NULL ON NULL INPUT;"
);
Let’s consider we have persisted the following entity in our database:
Book book = new Book();
book.setIsbn( "978-9730228236" );
book.setTitle( "High-Performance Java Persistence" );
book.setAuthor( "Vlad Mihalcea" );
book.setPriceCents( 4500 );
entityManager.persist( book );
15.28.1. User-defined functions referenced in the WHERE clause
By default, Hibernate can pass through any user-defined function that’s being used in the WHERE clause of a JPQL/HQL entity query.
List<Book> books = entityManager.createQuery(
"select b " +
"from Book b " +
"where apply_vat(b.priceCents) = :price ", Book.class )
.setParameter( "price", 5400 )
.getResultList();
assertTrue( books
.stream()
.filter( book -> "High-Performance Java Persistence".equals( book.getTitle() ) )
.findAny()
.isPresent()
);
While this works just fine with Hibernate, it might be a problem with other JPA providers.
For this purpose, JPA offers the function
JPQL keyword which works as follows.
function
keywordList<Book> books = entityManager.createQuery(
"select b " +
"from Book b " +
"where function('apply_vat', b.priceCents) = :price ", Book.class )
.setParameter( "price", 5400 )
.getResultList();
assertTrue( books
.stream()
.filter( book -> "High-Performance Java Persistence".equals( book.getTitle() ) )
.findAny()
.isPresent()
);
15.28.2. User-defined functions referenced in the SELECT clause
When the user-defined function is referenced in the SELECT clause of a JPQL/HQL entity query, Hibernate can no longer pass it through unless the function is registered.
MetadataBuilderContributor
settings.put( "hibernate.metadata_builder_contributor",
(MetadataBuilderContributor) metadataBuilder ->
metadataBuilder.applySqlFunction(
"apply_vat",
new StandardSQLFunction(
"apply_vat",
StandardBasicTypes.INTEGER
)
)
);
Now that that apply_vat
is registered, we can reference it in the JPQL SELECT clause.
List<Tuple> books = entityManager.createQuery(
"select b.title as title, apply_vat(b.priceCents) as price " +
"from Book b " +
"where b.author = :author ", Tuple.class )
.setParameter( "author", "Vlad Mihalcea" )
.getResultList();
assertEquals( 1, books.size() );
Tuple book = books.get( 0 );
assertEquals( "High-Performance Java Persistence", book.get( "title" ) );
assertEquals( 5400, ((Number) book.get( "price" )).intValue() );
15.29. Collection-related expressions
There are a few specialized expressions for working with collection-valued associations. Generally, these are just abbreviated forms or other expressions for the sake of conciseness.
- SIZE
-
Calculate the size of a collection. Equates to a subquery!
- MAXELEMENT
-
Available for use on collections of basic type. Refers to the maximum value as determined by applying the
max
SQL aggregation. - MAXINDEX
-
Available for use on indexed collections. Refers to the maximum index (key/position) as determined by applying the
max
SQL aggregation. - MINELEMENT
-
Available for use on collections of basic type. Refers to the minimum value as determined by applying the
min
SQL aggregation. - MININDEX
-
Available for use on indexed collections. Refers to the minimum index (key/position) as determined by applying the
min
SQL aggregation. - ELEMENTS
-
Used to refer to the elements of a collection as a whole. Only allowed in the where clause. Often used in conjunction with
ALL
,ANY
orSOME
restrictions. - INDICES
-
Similar to
elements
except that theindices
expression refers to the collections indices (keys/positions) as a whole.
List<Phone> phones = entityManager.createQuery(
"select p " +
"from Phone p " +
"where maxelement( p.calls ) = :call", Phone.class )
.setParameter( "call", call )
.getResultList();
List<Phone> phones = entityManager.createQuery(
"select p " +
"from Phone p " +
"where minelement( p.calls ) = :call", Phone.class )
.setParameter( "call", call )
.getResultList();
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where maxindex( p.phones ) = 0", Person.class )
.getResultList();
// the above query can be re-written with member of
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where :phone member of p.phones", Person.class )
.setParameter( "phone", phone )
.getResultList();
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where :phone = some elements ( p.phones )", Person.class )
.setParameter( "phone", phone )
.getResultList();
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where exists elements ( p.phones )", Person.class )
.getResultList();
List<Phone> phones = entityManager.createQuery(
"select p " +
"from Phone p " +
"where current_date() > key( p.callHistory )", Phone.class )
.getResultList();
List<Phone> phones = entityManager.createQuery(
"select p " +
"from Phone p " +
"where current_date() > all elements( p.repairTimestamps )", Phone.class )
.getResultList();
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where 1 in indices( p.phones )", Person.class )
.getResultList();
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where size( p.phones ) = 2", Person.class )
.getResultList();
Elements of indexed collections (arrays, lists, and maps) can be referred to by index operator.
// indexed lists
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.phones[ 0 ].type = 'LAND_LINE'", Person.class )
.getResultList();
// maps
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.addresses[ 'HOME' ] = :address", Person.class )
.setParameter( "address", address)
.getResultList();
//max index in list
List<Person> persons = entityManager.createQuery(
"select pr " +
"from Person pr " +
"where pr.phones[ maxindex(pr.phones) ].type = 'LAND_LINE'", Person.class )
.getResultList();
See also Special case - qualified path expressions as there is a good deal of overlap.
15.30. Entity type
We can also refer to the type of an entity as an expression.
This is mainly useful when dealing with entity inheritance hierarchies.
The type can be expressed using a TYPE
function used to refer to the type of an identification variable representing an entity.
The name of the entity also serves as a way to refer to an entity type.
Additionally, the entity type can be parameterized, in which case the entity’s Java Class reference would be bound as the parameter value.
List<Payment> payments = entityManager.createQuery(
"select p " +
"from Payment p " +
"where type(p) = CreditCardPayment", Payment.class )
.getResultList();
List<Payment> payments = entityManager.createQuery(
"select p " +
"from Payment p " +
"where type(p) = :type", Payment.class )
.setParameter( "type", WireTransferPayment.class)
.getResultList();
HQL also has a legacy form of referring to an entity type using the The legacy form would have used |
15.31. CASE expressions
Both the simple and searched forms are supported, as well as the two SQL defined abbreviated forms (NULLIF
and COALESCE
)
15.32. Simple CASE expressions
The simple form has the following syntax:
CASE {operand} WHEN {test_value} THEN {match_result} ELSE {miss_result} END
List<String> nickNames = entityManager.createQuery(
"select " +
" case p.nickName " +
" when 'NA' " +
" then '<no nick name>' " +
" else p.nickName " +
" end " +
"from Person p", String.class )
.getResultList();
// same as above
List<String> nickNames = entityManager.createQuery(
"select coalesce(p.nickName, '<no nick name>') " +
"from Person p", String.class )
.getResultList();
15.33. Searched CASE expressions
The searched form has the following syntax:
CASE [ WHEN {test_conditional} THEN {match_result} ]* ELSE {miss_result} END
List<String> nickNames = entityManager.createQuery(
"select " +
" case " +
" when p.nickName is null " +
" then " +
" case " +
" when p.name is null " +
" then '<no nick name>' " +
" else p.name " +
" end" +
" else p.nickName " +
" end " +
"from Person p", String.class )
.getResultList();
// coalesce can handle this more succinctly
List<String> nickNames = entityManager.createQuery(
"select coalesce( p.nickName, p.name, '<no nick name>' ) " +
"from Person p", String.class )
.getResultList();
15.34. CASE expressions with arithmetic operations
If you want to use arithmetic operations in the CASE expressions, you need to wrap the arithmetic operation in parentheses as illustrated by the following example:
List<Long> values = entityManager.createQuery(
"select " +
" case when p.nickName is null " +
" then (p.id * 1000) " +
" else p.id " +
" end " +
"from Person p " +
"order by p.id", Long.class)
.getResultList();
assertEquals(3, values.size());
assertEquals( 1L, (long) values.get( 0 ) );
assertEquals( 2000, (long) values.get( 1 ) );
assertEquals( 3000, (long) values.get( 2 ) );
Without wrapping the arithmetic expression in |
15.35. NULLIF expressions
NULLIF is an abbreviated CASE expression that returns NULL if its operands are considered equal.
List<String> nickNames = entityManager.createQuery(
"select nullif( p.nickName, p.name ) " +
"from Person p", String.class )
.getResultList();
// equivalent CASE expression
List<String> nickNames = entityManager.createQuery(
"select " +
" case" +
" when p.nickName = p.name" +
" then null" +
" else p.nickName" +
" end " +
"from Person p", String.class )
.getResultList();
15.36. COALESCE expressions
COALESCE
is an abbreviated CASE expression that returns the first non-null operand.
We have seen a number of COALESCE
examples above.
15.37. The SELECT
clause
The SELECT
clause identifies which objects and values to return as the query results.
The expressions discussed in Expressions are all valid select expressions, except where otherwise noted.
See the section Hibernate Query API for information on handling the results depending on the types of values specified in the SELECT
clause.
There is a particular expression type that is only valid in the select clause. Hibernate calls this "dynamic instantiation". JPQL supports some of that feature and calls it a "constructor expression".
So rather than dealing with the Object[]
(again, see Hibernate Query API) here, we are wrapping the values in a type-safe Java object that will be returned as the results of the query.
public class CallStatistics {
private final long count;
private final long total;
private final int min;
private final int max;
private final double avg;
public CallStatistics(long count, long total, int min, int max, double avg) {
this.count = count;
this.total = total;
this.min = min;
this.max = max;
this.avg = avg;
}
//Getters and setters omitted for brevity
}
CallStatistics callStatistics = entityManager.createQuery(
"select new org.hibernate.userguide.hql.CallStatistics(" +
" count(c), " +
" sum(c.duration), " +
" min(c.duration), " +
" max(c.duration), " +
" avg(c.duration)" +
") " +
"from Call c ", CallStatistics.class )
.getSingleResult();
The projection class must be fully qualified in the entity query, and it must define a matching constructor. |
The class here need not be mapped. It can be a DTO class. If it does represent an entity, the resulting instances are returned in the NEW state (not managed!). |
HQL supports additional "dynamic instantiation" features.
First, the query can specify to return a List
rather than an Object[]
for scalar results:
List<List> phoneCallDurations = entityManager.createQuery(
"select new list(" +
" p.number, " +
" c.duration " +
") " +
"from Call c " +
"join c.phone p ", List.class )
.getResultList();
The results from this query will be a List<List>
as opposed to a List<Object[]>
HQL also supports wrapping the scalar results in a Map
.
List<Map> phoneCallTotalDurations = entityManager.createQuery(
"select new map(" +
" p.number as phoneNumber , " +
" sum(c.duration) as totalDuration, " +
" avg(c.duration) as averageDuration " +
") " +
"from Call c " +
"join c.phone p " +
"group by p.number ", Map.class )
.getResultList();
The results from this query will be a List<Map<String, Object>>
as opposed to a List<Object[]>
.
The keys of the map are defined by the aliases given to the select expressions.
If the user doesn’t assign aliases, the key will be the index of each particular result set column (e.g. 0, 1, 2, etc).
15.38. Predicates
Predicates form the basis of the where clause, the having clause and searched case expressions.
They are expressions which resolve to a truth value, generally TRUE
or FALSE
, although boolean comparisons involving NULL
resolve typically to UNKNOWN
.
15.39. Relational comparisons
Comparisons involve one of the comparison operators: =
, >
, >=
, <
, <=
, <>
.
HQL also defines !=
as a comparison operator synonymous with <>
.
The operands should be of the same type.
// numeric comparison
List<Call> calls = entityManager.createQuery(
"select c " +
"from Call c " +
"where c.duration < 30 ", Call.class )
.getResultList();
// string comparison
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.name like 'John%' ", Person.class )
.getResultList();
// datetime comparison
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.createdOn > '1950-01-01' ", Person.class )
.getResultList();
// enum comparison
List<Phone> phones = entityManager.createQuery(
"select p " +
"from Phone p " +
"where p.type = 'MOBILE' ", Phone.class )
.getResultList();
// boolean comparison
List<Payment> payments = entityManager.createQuery(
"select p " +
"from Payment p " +
"where p.completed = true ", Payment.class )
.getResultList();
// boolean comparison
List<Payment> payments = entityManager.createQuery(
"select p " +
"from Payment p " +
"where type(p) = WireTransferPayment ", Payment.class )
.getResultList();
// entity value comparison
List<Object[]> phonePayments = entityManager.createQuery(
"select p " +
"from Payment p, Phone ph " +
"where p.person = ph.person ", Object[].class )
.getResultList();
Comparisons can also involve subquery qualifiers: ALL
, ANY
, SOME
. SOME
and ANY
are synonymous.
The ALL
qualifier resolves to true if the comparison is true for all of the values in the result of the subquery.
It resolves to false if the subquery result is empty.
// select all persons with all calls shorter than 50 seconds
List<Person> persons = entityManager.createQuery(
"select distinct p.person " +
"from Phone p " +
"join p.calls c " +
"where 50 > all ( " +
" select duration" +
" from Call" +
" where phone = p " +
") ", Person.class )
.getResultList();
The ANY
/SOME
qualifier resolves to true if the comparison is true for some of (at least one of) the values in the result of the subquery.
It resolves to false if the subquery result is empty.
15.40. Nullness predicate
It check a value for nullness. It can be applied to basic attribute references, entity references, and parameters. HQL additionally allows it to be applied to component/embeddable types.
// select all persons with a nickname
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.nickName is not null", Person.class )
.getResultList();
// select all persons without a nickname
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.nickName is null", Person.class )
.getResultList();
15.41. Like predicate
Performs a like comparison on string values. The syntax is:
like_expression ::=
string_expression
[NOT] LIKE pattern_value
[ESCAPE escape_character]
The semantics follow that of the SQL like expression.
The pattern_value
is the pattern to attempt to match in the string_expression
.
Just like SQL, pattern_value
can use _
and %
as wildcards.
The meanings are the same. The _
symbol matches any single character and %
matches any number of characters.
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.name like 'Jo%'", Person.class )
.getResultList();
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.name not like 'Jo%'", Person.class )
.getResultList();
The optional escape 'escape character'
is used to specify an escape character used to escape the special meaning of _
and %
in the pattern_value
.
This is useful when needing to search on patterns including either _
or %
.
The syntax is formed as follows: 'like_predicate' escape 'escape_symbol'
So, if |
is the escape symbol and we want to match all stored procedures prefixed with Dr_
, the like criteria becomes: 'Dr|_%' escape '|'
:
// find any person with a name starting with "Dr_"
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.name like 'Dr|_%' escape '|'", Person.class )
.getResultList();
15.42. Between predicate
Analogous to the SQL BETWEEN
expression,
it checks if the value is within boundaries.
All the operands should have comparable types.
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"join p.phones ph " +
"where p.id = 1L and index(ph) between 0 and 3", Person.class )
.getResultList();
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.createdOn between '1999-01-01' and '2001-01-02'", Person.class )
.getResultList();
List<Call> calls = entityManager.createQuery(
"select c " +
"from Call c " +
"where c.duration between 5 and 20", Call.class )
.getResultList();
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.name between 'H' and 'M'", Person.class )
.getResultList();
15.43. In predicate
IN
predicates performs a check that a particular value is in a list of values. Its syntax is:
in_expression ::=
single_valued_expression [NOT] IN single_valued_list
single_valued_list ::=
constructor_expression | (subquery) | collection_valued_input_parameter
constructor_expression ::= (expression[, expression]*)
The types of the single_valued_expression
and the individual values in the single_valued_list
must be consistent.
JPQL limits the valid types here to string, numeric, date, time, timestamp, and enum types, and, in JPQL, single_valued_expression
can only refer to:
-
"state fields", which is its term for simple attributes. Specifically, this excludes association and component/embedded attributes.
-
entity type expressions. See Entity type.
In HQL, single_valued_expression
can refer to a far more broad set of expression types.
Single-valued association are allowed, and so are component/embedded attributes, although that feature depends on the level of support for tuple or "row value constructor syntax" in the underlying database.
Additionally, HQL does not limit the value type in any way, though application developers should be aware that different types may incur limited support based on the underlying database vendor.
This is largely the reason for the JPQL limitations.
The list of values can come from a number of different sources.
In the constructor_expression
and collection_valued_input_parameter
, the list of values must not be empty; it must contain at least one value.
List<Payment> payments = entityManager.createQuery(
"select p " +
"from Payment p " +
"where type(p) in ( CreditCardPayment, WireTransferPayment )", Payment.class )
.getResultList();
List<Phone> phones = entityManager.createQuery(
"select p " +
"from Phone p " +
"where type in ( 'MOBILE', 'LAND_LINE' )", Phone.class )
.getResultList();
List<Phone> phones = entityManager.createQuery(
"select p " +
"from Phone p " +
"where type in :types", Phone.class )
.setParameter( "types", Arrays.asList( PhoneType.MOBILE, PhoneType.LAND_LINE ) )
.getResultList();
List<Phone> phones = entityManager.createQuery(
"select distinct p " +
"from Phone p " +
"where p.person.id in (" +
" select py.person.id " +
" from Payment py" +
" where py.completed = true and py.amount > 50 " +
")", Phone.class )
.getResultList();
// Not JPQL compliant!
List<Phone> phones = entityManager.createQuery(
"select distinct p " +
"from Phone p " +
"where p.person in (" +
" select py.person " +
" from Payment py" +
" where py.completed = true and py.amount > 50 " +
")", Phone.class )
.getResultList();
// Not JPQL compliant!
List<Payment> payments = entityManager.createQuery(
"select distinct p " +
"from Payment p " +
"where ( p.amount, p.completed ) in (" +
" (50, true )," +
" (100, true )," +
" (5, false )" +
")", Payment.class )
.getResultList();
15.44. Exists predicate
Exists expressions test the existence of results from a subquery. The affirmative form returns true if the subquery result contains values. The negated form returns true if the subquery result is empty.
15.45. Empty collection predicate
The IS [NOT] EMPTY
expression applies to collection-valued path expressions.
It checks whether the particular collection has any associated values.
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.phones is empty", Person.class )
.getResultList();
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.phones is not empty", Person.class )
.getResultList();
15.46. Member-of collection predicate
The [NOT] MEMBER [OF]
expression applies to collection-valued path expressions.
It checks whether a value is a member of the specified collection.
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where 'Home address' member of p.addresses", Person.class )
.getResultList();
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where 'Home address' not member of p.addresses", Person.class )
.getResultList();
15.47. NOT predicate operator
The NOT
operator is used to negate the predicate that follows it.
If that following predicate is true, the NOT resolves to false.
If the predicate is true, NOT resolves to false. If the predicate is unknown (e.g. |
15.48. AND predicate operator
The AND
operator is used to combine 2 predicate expressions.
The result of the AND expression is true if and only if both predicates resolve to true.
If either predicate resolves to unknown, the AND expression resolves to unknown as well. Otherwise, the result is false.
15.49. OR predicate operator
The OR
operator is used to combine 2 predicate expressions.
The result of the OR expression is true if one predicate resolves to true.
If both predicates resolve to unknown, the OR expression resolves to unknown.
Otherwise, the result is false.
15.50. The WHERE
clause
The WHERE
clause of a query is made up of predicates which assert whether values in each potential row match the current filtering criteria.
Thus, the where clause restricts the results returned from a select query and limits the scope of update and delete queries.
15.51. Group by
The GROUP BY
clause allows building aggregated results for various value groups. As an example, consider the following queries:
Long totalDuration = entityManager.createQuery(
"select sum( c.duration ) " +
"from Call c ", Long.class )
.getSingleResult();
List<Object[]> personTotalCallDurations = entityManager.createQuery(
"select p.name, sum( c.duration ) " +
"from Call c " +
"join c.phone ph " +
"join ph.person p " +
"group by p.name", Object[].class )
.getResultList();
//It's even possible to group by entities!
List<Object[]> personTotalCallDurations = entityManager.createQuery(
"select p, sum( c.duration ) " +
"from Call c " +
"join c.phone ph " +
"join ph.person p " +
"group by p", Object[].class )
.getResultList();
The first query retrieves the complete total of all orders. The second retrieves the total for each customer, grouped by each customer.
In a grouped query, the where clause applies to the non-aggregated values (essentially it determines whether rows will make it into the aggregation).
The HAVING
clause also restricts results, but it operates on the aggregated values.
In the Group by example, we retrieved Call
duration totals for all persons.
If that ended up being too much data to deal with, we might want to restrict the results to focus only on customers with a summed total of more than 1000:
List<Object[]> personTotalCallDurations = entityManager.createQuery(
"select p.name, sum( c.duration ) " +
"from Call c " +
"join c.phone ph " +
"join ph.person p " +
"group by p.name " +
"having sum( c.duration ) > 1000", Object[].class )
.getResultList();
The HAVING
clause follows the same rules as the WHERE
clause and is also made up of predicates.
HAVING
is applied after the groupings and aggregations have been done, while the WHERE
clause is applied before.
15.52. Order by
The results of the query can also be ordered.
The ORDER BY
clause is used to specify the selected values to be used to order the result.
The types of expressions considered valid as part of the ORDER BY
clause include:
-
state fields
-
component/embeddable attributes
-
scalar expressions such as arithmetic operations, functions, etc.
-
identification variable declared in the select clause for any of the previous expression types
Additionally, JPQL says that all values referenced in the ORDER BY
clause must be named in the SELECT
clause.
HQL does not mandate that restriction, but applications desiring database portability should be aware that not all databases support referencing values in the ORDER BY
clause that are not referenced in the select clause.
Individual expressions in the order-by can be qualified with either ASC
(ascending) or DESC
(descending) to indicate the desired ordering direction.
Null values can be placed in front or at the end of the sorted set using NULLS FIRST
or NULLS LAST
clause respectively.
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"order by p.name", Person.class )
.getResultList();
List<Object[]> personTotalCallDurations = entityManager.createQuery(
"select p.name, sum( c.duration ) as total " +
"from Call c " +
"join c.phone ph " +
"join ph.person p " +
"group by p.name " +
"order by total", Object[].class )
.getResultList();
15.53. Read-only entities
As explained in entity immutability section, fetching entities in read-only mode is much more efficient than fetching read-write entities. Even if the entities are mutable, you can still fetch them in read-only mode, and benefit from reducing the memory footprint and speeding up the flushing process.
Read-only entities are skipped by the dirty checking mechanism as illustrated by the following example:
List<Call> calls = entityManager.createQuery(
"select c " +
"from Call c " +
"join c.phone p " +
"where p.number = :phoneNumber ", Call.class )
.setParameter( "phoneNumber", "123-456-7890" )
.setHint( "org.hibernate.readOnly", true )
.getResultList();
calls.forEach( c -> c.setDuration( 0 ) );
SELECT c.id AS id1_5_ ,
c.duration AS duration2_5_ ,
c.phone_id AS phone_id4_5_ ,
c.call_timestamp AS call_tim3_5_
FROM phone_call c
INNER JOIN phone p ON c.phone_id = p.id
WHERE p.phone_number = '123-456-7890'
As you can see, there is no SQL UPDATE
being executed.
You can also pass the read-only hint to named queries using the JPA @QueryHint
annotation.
@NamedQuery(
name = "get_read_only_person_by_name",
query = "select p from Person p where name = :name",
hints = {
@QueryHint(
name = "org.hibernate.readOnly",
value = "true"
)
}
)
The Hibernate native API offers a Query#setReadOnly
method, as an alternative to using a JPA query hint:
List<Call> calls = entityManager.createQuery(
"select c " +
"from Call c " +
"join c.phone p " +
"where p.number = :phoneNumber ", Call.class )
.setParameter( "phoneNumber", "123-456-7890" )
.unwrap( org.hibernate.query.Query.class )
.setReadOnly( true )
.getResultList();
15.54. Entity query plan cache
Any entity query, be it JPQL or Criteria API, has to be parsed into an AST (Abstract Syntax Tree) so that Hibernate can generate the proper SQL statement. The entity query compilation takes time, and for this reason, Hibernate offers a query plan cache.
When executing an entity query, Hibernate first checks the plan cache, and only if there’s no plan available, a new one will be computed right away.
The query plan cache can be configured via the following configuration properties:
hibernate.query.plan_cache_max_size
-
This setting gives the maximum number of entries of the plan cache. The default value is 2048.
hibernate.query.plan_parameter_metadata_max_size
-
The setting gives the maximum number of
ParameterMetadataImpl
instances maintained by the query plan cache. TheParameterMetadataImpl
object encapsulates metadata about parameters encountered within a query. The default value is 128.
Now, if you have many JPQL or Criteria API queries, it’s a good idea to increase the query plan cache size so that the vast majority of executing entity queries can skip the compilation phase, therefore reducing execution time.
To get a better understanding of the query plan cache effectiveness, Hibernate offers several statistics you can use. For more details, check out the Query plan cache statistics section.
16. Criteria
Criteria queries offer a type-safe alternative to HQL, JPQL and native SQL queries.
Hibernate offers an older, legacy No feature development will target those APIs. Eventually, Hibernate-specific Criteria features will be ported as extensions to the JPA This chapter will focus on the JPA APIs for declaring type-safe criteria queries. |
Criteria queries are a programmatic, type-safe way to express a query.
They are type-safe in terms of using interfaces and classes to represent various structural parts of a query such as the query itself, the select clause, or an order-by, etc.
They can also be type-safe in terms of referencing attributes as we will see in a bit.
Users of the older Hibernate org.hibernate.Criteria
query API will recognize the general approach, though we believe the JPA API to be superior as it represents a clean look at the lessons learned from that API.
Criteria queries are essentially an object graph, where each part of the graph represents an increasing (as we navigate down this graph) more atomic part of the query.
The first step in performing a criteria query is building this graph.
The javax.persistence.criteria.CriteriaBuilder
interface is the first thing with which you need to become acquainted with before using criteria queries.
Its role is that of a factory for all the individual pieces of the criteria.
You obtain a javax.persistence.criteria.CriteriaBuilder
instance by calling the getCriteriaBuilder()
method of either javax.persistence.EntityManagerFactory
or javax.persistence.EntityManager
.
The next step is to obtain a javax.persistence.criteria.CriteriaQuery
.
This is accomplished using one of the three methods on javax.persistence.criteria.CriteriaBuilder
for this purpose:
-
<T> CriteriaQuery<T> createQuery( Class<T> resultClass )
-
CriteriaQuery<Tuple> createTupleQuery()
-
CriteriaQuery<Object> createQuery()
Each serves a different purpose depending on the expected type of the query results.
The chapter 6 (i.e., Criteria API) of the JPA Specification already contains a decent amount of reference material pertaining to the various parts of a criteria query. So rather than duplicate all that content here, let’s instead look at some of the more widely anticipated usages of the API. |
16.1. Typed criteria queries
The type of the criteria query (aka the <T>
) indicates the expected types in the query result.
This might be an entity, an Integer
, or any other object.
16.2. Selecting an entity
This is probably the most common form of query. The application wants to select entity instances.
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Person> criteria = builder.createQuery( Person.class );
Root<Person> root = criteria.from( Person.class );
criteria.select( root );
criteria.where( builder.equal( root.get( Person_.name ), "John Doe" ) );
List<Person> persons = entityManager.createQuery( criteria ).getResultList();
The example uses createQuery()
passing in the Person
class reference as the results of the query will be Person
objects.
The call to the The |
16.3. Selecting an expression
The simplest form of selecting an expression is selecting a particular attribute from an entity. But this expression might also represent an aggregation, a mathematical operation, etc.
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<String> criteria = builder.createQuery( String.class );
Root<Person> root = criteria.from( Person.class );
criteria.select( root.get( Person_.nickName ) );
criteria.where( builder.equal( root.get( Person_.name ), "John Doe" ) );
List<String> nickNames = entityManager.createQuery( criteria ).getResultList();
In this example, the query is typed as java.lang.String
because that is the anticipated type of the results (the type of the Person#nickName
attribute is java.lang.String
).
Because a query might contain multiple references to the Person
entity, attribute references always need to be qualified.
This is accomplished by the Root#get
method call.
16.4. Selecting multiple values
There are actually a few different ways to select multiple values using criteria queries. We will explore two options here, but an alternative recommended approach is to use tuples as described in Tuple criteria queries, or consider a wrapper query, see Selecting a wrapper for details.
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> criteria = builder.createQuery( Object[].class );
Root<Person> root = criteria.from( Person.class );
Path<Long> idPath = root.get( Person_.id );
Path<String> nickNamePath = root.get( Person_.nickName);
criteria.select( builder.array( idPath, nickNamePath ) );
criteria.where( builder.equal( root.get( Person_.name ), "John Doe" ) );
List<Object[]> idAndNickNames = entityManager.createQuery( criteria ).getResultList();
Technically this is classified as a typed query, but you can see from handling the results that this is sort of misleading. Anyway, the expected result type here is an array.
The example then uses the array method of javax.persistence.criteria.CriteriaBuilder
which explicitly combines individual selections into a javax.persistence.criteria.CompoundSelection
.
multiselect
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> criteria = builder.createQuery( Object[].class );
Root<Person> root = criteria.from( Person.class );
Path<Long> idPath = root.get( Person_.id );
Path<String> nickNamePath = root.get( Person_.nickName);
criteria.multiselect( idPath, nickNamePath );
criteria.where( builder.equal( root.get( Person_.name ), "John Doe" ) );
List<Object[]> idAndNickNames = entityManager.createQuery( criteria ).getResultList();
Just as we saw in Selecting an array we have a typed criteria query returning an Object
array.
Both queries are functionally equivalent.
This second example uses the multiselect()
method which behaves slightly differently based on the type given when the criteria query was first built,
but, in this case, it says to select and return an Object[].
16.5. Selecting a wrapper
Another alternative to Selecting multiple values is to instead select an object that will "wrap" the multiple values. Going back to the example query there, rather than returning an array of [Person#id, Person#nickName], instead declare a class that holds these values and use that as a return object.
public class PersonWrapper {
private final Long id;
private final String nickName;
public PersonWrapper(Long id, String nickName) {
this.id = id;
this.nickName = nickName;
}
public Long getId() {
return id;
}
public String getNickName() {
return nickName;
}
}
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<PersonWrapper> criteria = builder.createQuery( PersonWrapper.class );
Root<Person> root = criteria.from( Person.class );
Path<Long> idPath = root.get( Person_.id );
Path<String> nickNamePath = root.get( Person_.nickName);
criteria.select( builder.construct( PersonWrapper.class, idPath, nickNamePath ) );
criteria.where( builder.equal( root.get( Person_.name ), "John Doe" ) );
List<PersonWrapper> wrappers = entityManager.createQuery( criteria ).getResultList();
First, we see the simple definition of the wrapper object we will be using to wrap our result values.
Specifically, notice the constructor and its argument types.
Since we will be returning PersonWrapper
objects, we use PersonWrapper
as the type of our criteria query.
This example illustrates the use of the javax.persistence.criteria.CriteriaBuilder
method construct which is used to build a wrapper expression.
For every row in the result, we are saying we would like a PersonWrapper
instantiated with the remaining arguments by the matching constructor.
This wrapper expression is then passed as the select.
16.6. Tuple criteria queries
A better approach to Selecting multiple values is to use either a wrapper (which we just saw in Selecting a wrapper) or using the javax.persistence.Tuple
contract.
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> criteria = builder.createQuery( Tuple.class );
Root<Person> root = criteria.from( Person.class );
Path<Long> idPath = root.get( Person_.id );
Path<String> nickNamePath = root.get( Person_.nickName);
criteria.multiselect( idPath, nickNamePath );
criteria.where( builder.equal( root.get( Person_.name ), "John Doe" ) );
List<Tuple> tuples = entityManager.createQuery( criteria ).getResultList();
for ( Tuple tuple : tuples ) {
Long id = tuple.get( idPath );
String nickName = tuple.get( nickNamePath );
}
//or using indices
for ( Tuple tuple : tuples ) {
Long id = (Long) tuple.get( 0 );
String nickName = (String) tuple.get( 1 );
}
This example illustrates accessing the query results through the javax.persistence.Tuple
interface.
The example uses the explicit createTupleQuery()
of javax.persistence.criteria.CriteriaBuilder
.
An alternate approach is to use createQuery( Tuple.class )
.
Again we see the use of the multiselect()
method, just like in Selecting an array using multiselect
.
The difference here is that the type of the javax.persistence.criteria.CriteriaQuery
was defined as javax.persistence.Tuple
so the compound selections, in this case, are interpreted to be the tuple elements.
The javax.persistence.Tuple contract provides three forms of access to the underlying elements:
- typed
-
The Selecting a tuple example illustrates this form of access in the
tuple.get( idPath )
andtuple.get( nickNamePath )
calls. This allows typed access to the underlying tuple values based on thejavax.persistence.TupleElement
expressions used to build the criteria. - positional
-
Allows access to the underlying tuple values based on the position. The simple Object get(int position) form is very similar to the access illustrated in Selecting an array and Selecting an array using
multiselect
. The <X> X get(int position, Class<X> type form allows typed positional access, but based on the explicitly supplied type which the tuple value must be type-assignable to. - aliased
-
Allows access to the underlying tuple values based on (optionally) assigned alias. The example query did not apply an alias. An alias would be applied via the alias method on
javax.persistence.criteria.Selection
. Just likepositional
access, there is both a typed (Object get(String alias)) and an untyped (<X> X get(String alias, Class<X> type)) form.
16.7. FROM clause
A CriteriaQuery
object defines a query over one or more entity, embeddable, or basic abstract schema types.
The root objects of the query are entities, from which the other types are reached by navigation.
— JPA Specification, section 6.5.2 Query Roots, pg 262
All the individual parts of the FROM clause (roots, joins, paths) implement the |
16.8. Roots
Roots define the basis from which all joins, paths and attributes are available in the query.
A root is always an entity type. Roots are defined and added to the criteria by the overloaded from methods on javax.persistence.criteria.CriteriaQuery
:
<X> Root<X> from( Class<X> );
<X> Root<X> from( EntityType<X> );
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Person> criteria = builder.createQuery( Person.class );
Root<Person> root = criteria.from( Person.class );
Criteria queries may define multiple roots, the effect of which is to create a Cartesian Product between the newly added root and the others.
Here is an example defining a Cartesian Product between Person
and Partner
entities:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> criteria = builder.createQuery( Tuple.class );
Root<Person> personRoot = criteria.from( Person.class );
Root<Partner> partnerRoot = criteria.from( Partner.class );
criteria.multiselect( personRoot, partnerRoot );
Predicate personRestriction = builder.and(
builder.equal( personRoot.get( Person_.address ), address ),
builder.isNotEmpty( personRoot.get( Person_.phones ) )
);
Predicate partnerRestriction = builder.and(
builder.like( partnerRoot.get( Partner_.name ), prefix ),
builder.equal( partnerRoot.get( Partner_.version ), 0 )
);
criteria.where( builder.and( personRestriction, partnerRestriction ) );
List<Tuple> tuples = entityManager.createQuery( criteria ).getResultList();
16.9. Joins
Joins allow navigation from other javax.persistence.criteria.From
to either association or embedded attributes.
Joins are created by the numerous overloaded join methods of the javax.persistence.criteria.From
interface.
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Phone> criteria = builder.createQuery( Phone.class );
Root<Phone> root = criteria.from( Phone.class );
// Phone.person is a @ManyToOne
Join<Phone, Person> personJoin = root.join( Phone_.person );
// Person.addresses is an @ElementCollection
Join<Person, String> addressesJoin = personJoin.join( Person_.addresses );
criteria.where( builder.isNotEmpty( root.get( Phone_.calls ) ) );
List<Phone> phones = entityManager.createQuery( criteria ).getResultList();
16.10. Fetches
Just like in HQL and JPQL, criteria queries can specify that associated data be fetched along with the owner.
Fetches are created by the numerous overloaded fetch methods of the javax.persistence.criteria.From
interface.
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Phone> criteria = builder.createQuery( Phone.class );
Root<Phone> root = criteria.from( Phone.class );
// Phone.person is a @ManyToOne
Fetch<Phone, Person> personFetch = root.fetch( Phone_.person );
// Person.addresses is an @ElementCollection
Fetch<Person, String> addressesJoin = personFetch.fetch( Person_.addresses );
criteria.where( builder.isNotEmpty( root.get( Phone_.calls ) ) );
List<Phone> phones = entityManager.createQuery( criteria ).getResultList();
Technically speaking, embedded attributes are always fetched with their owner.
However, in order to define the fetching of Phone#addresses we needed a |
16.12. Using parameters
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Person> criteria = builder.createQuery( Person.class );
Root<Person> root = criteria.from( Person.class );
ParameterExpression<String> nickNameParameter = builder.parameter( String.class );
criteria.where( builder.equal( root.get( Person_.nickName ), nickNameParameter ) );
TypedQuery<Person> query = entityManager.createQuery( criteria );
query.setParameter( nickNameParameter, "JD" );
List<Person> persons = query.getResultList();
Use the parameter method of javax.persistence.criteria.CriteriaBuilder
to obtain a parameter reference.
Then use the parameter reference to bind the parameter value to the javax.persistence.Query
.
16.13. Using group by
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> criteria = builder.createQuery( Tuple.class );
Root<Person> root = criteria.from( Person.class );
criteria.groupBy(root.get("address"));
criteria.multiselect(root.get("address"), builder.count(root));
List<Tuple> tuples = entityManager.createQuery( criteria ).getResultList();
for ( Tuple tuple : tuples ) {
String name = (String) tuple.get( 0 );
Long count = (Long) tuple.get( 1 );
}
17. Native SQL Queries
You may also express queries in the native SQL dialect of your database.
This is useful if you want to utilize database-specific features such as window functions, Common Table Expressions (CTE) or the CONNECT BY
option in Oracle.
It also provides a clean migration path from a direct SQL/JDBC based application to Hibernate/JPA.
Hibernate also allows you to specify handwritten SQL (including stored procedures) for all create, update, delete, and retrieve operations.
17.1. Creating a native query using JPA
Execution of native SQL queries is controlled via the NativeQuery
interface, which is obtained by calling Session.createNativeQuery()
.
The following sections describe how to use this API for querying.
17.2. Scalar queries
The most basic SQL query is to get a list of scalars (column) values.
List<Object[]> persons = entityManager.createNativeQuery(
"SELECT * FROM Person" )
.getResultList();
List<Object[]> persons = entityManager.createNativeQuery(
"SELECT id, name FROM Person" )
.getResultList();
for(Object[] person : persons) {
Number id = (Number) person[0];
String name = (String) person[1];
}
List<Object[]> persons = session.createNativeQuery(
"SELECT * FROM Person" )
.list();
List<Object[]> persons = session.createNativeQuery(
"SELECT id, name FROM Person" )
.list();
for(Object[] person : persons) {
Number id = (Number) person[0];
String name = (String) person[1];
}
These will return a List
of Object
arrays ( Object[]
) with scalar values for each column in the PERSON
table.
Hibernate will use java.sql.ResultSetMetadata
to deduce the actual order and types of the returned scalar values.
To avoid the overhead of using ResultSetMetadata
, or simply to be more explicit in what is returned, one can use addScalar()
:
List<Object[]> persons = session.createNativeQuery(
"SELECT * FROM Person" )
.addScalar( "id", LongType.INSTANCE )
.addScalar( "name", StringType.INSTANCE )
.list();
for(Object[] person : persons) {
Long id = (Long) person[0];
String name = (String) person[1];
}
Although it still returns an Object
arrays, this query will not use the ResultSetMetadata
anymore since it explicitly gets the id
and name
columns as respectively a BigInteger
and a String
from the underlying ResultSet
.
This also means that only these two columns will be returned, even though the query is still using *
and the ResultSet
contains more than the three listed columns.
It is possible to leave out the type information for all or some of the scalars.
List<Object[]> persons = session.createNativeQuery(
"SELECT * FROM Person" )
.addScalar( "id", LongType.INSTANCE )
.addScalar( "name" )
.list();
for(Object[] person : persons) {
Long id = (Long) person[0];
String name = (String) person[1];
}
This is essentially the same query as before, but now ResultSetMetaData
is used to determine the type of name
, whereas the type of id
is explicitly specified.
How the java.sql.Types
returned from ResultSetMetaData
is mapped to Hibernate types is controlled by the Dialect
.
If a specific type is not mapped, or does not result in the expected type, it is possible to customize it via calls to registerHibernateType
in the Dialect.
17.3. Entity queries
The above queries were all about returning scalar values, basically returning the raw values from the ResultSet
.
List<Person> persons = entityManager.createNativeQuery(
"SELECT * FROM Person", Person.class )
.getResultList();
List<Person> persons = session.createNativeQuery(
"SELECT * FROM Person" )
.addEntity( Person.class )
.list();
Assuming that Person
is mapped as a class with the columns id
, name
, nickName
, address
, createdOn
, and version
,
the following query will also return a List
where each element is a Person
entity.
List<Person> persons = entityManager.createNativeQuery(
"SELECT id, name, nickName, address, createdOn, version " +
"FROM Person", Person.class )
.getResultList();
List<Person> persons = session.createNativeQuery(
"SELECT id, name, nickName, address, createdOn, version " +
"FROM Person" )
.addEntity( Person.class )
.list();
17.4. Handling associations and collections
If the entity is mapped with a many-to-one
or a child-side one-to-one
to another entity,
it is required to also return this when performing the native query,
otherwise, a database-specific column not found error will occur.
List<Phone> phones = entityManager.createNativeQuery(
"SELECT id, phone_number, phone_type, person_id " +
"FROM Phone", Phone.class )
.getResultList();
List<Phone> phones = session.createNativeQuery(
"SELECT id, phone_number, phone_type, person_id " +
"FROM Phone" )
.addEntity( Phone.class )
.list();
This will allow the Phone#person
to function properly since the many-to-one
or one-to-one
association is going to use a proxy that will be initialized when being navigated for the first time.
It is possible to eagerly join the Phone
and the Person
entities to avoid the possible extra roundtrip for initializing the many-to-one
association.
List<Object[]> tuples = session.createNativeQuery(
"SELECT * " +
"FROM Phone ph " +
"JOIN Person pr ON ph.person_id = pr.id" )
.addEntity("phone", Phone.class )
.addJoin( "pr", "phone.person")
.list();
for(Object[] tuple : tuples) {
Phone phone = (Phone) tuple[0];
Person person = (Person) tuple[1];
assertNotNull( person.getName() );
}
SELECT
*
FROM
Phone ph
JOIN
Person pr
ON ph.person_id = pr.id
As seen in the associated SQL query, Hibernate manages to construct the entity hierarchy without requiring any extra database roundtrip. |
By default, when using the addJoin()
method, the result set will contain both entities that are joined.
To construct the entity hierarchy, you need to use a ROOT_ENTITY
or DISTINCT_ROOT_ENTITY
ResultTransformer
.
ResultTransformer
List<Person> persons = session.createNativeQuery(
"SELECT * " +
"FROM Phone ph " +
"JOIN Person pr ON ph.person_id = pr.id" )
.addEntity("phone", Phone.class )
.addJoin( "pr", "phone.person")
.setResultTransformer( Criteria.ROOT_ENTITY )
.list();
for(Person person : persons) {
person.getPhones();
}
Because of the |
Notice that you added an alias name pr to be able to specify the target property path of the join.
It is possible to do the same eager joining for collections (e.g. the Phone#calls
one-to-many
association).
List<Phone> phones = entityManager.createNativeQuery(
"SELECT * " +
"FROM Phone ph " +
"JOIN phone_call c ON c.phone_id = ph.id", Phone.class )
.getResultList();
for(Phone phone : phones) {
List<Call> calls = phone.getCalls();
}
SELECT *
FROM phone ph
JOIN call c ON c.phone_id = ph.id
List<Object[]> tuples = session.createNativeQuery(
"SELECT * " +
"FROM Phone ph " +
"JOIN phone_call c ON c.phone_id = ph.id" )
.addEntity("phone", Phone.class )
.addJoin( "c", "phone.calls")
.list();
for(Object[] tuple : tuples) {
Phone phone = (Phone) tuple[0];
Call call = (Call) tuple[1];
}
SELECT *
FROM phone ph
JOIN call c ON c.phone_id = ph.id
At this stage, you are reaching the limits of what is possible with native queries, without starting to enhance the sql queries to make them usable in Hibernate. Problems can arise when returning multiple entities of the same type or when the default alias/column names are not enough.
17.5. Returning multiple entities
Until now, the result set column names are assumed to be the same as the column names specified in the mapping document. This can be problematic for SQL queries that join multiple tables since the same column names can appear in more than one table.
Column alias injection is needed in the following query which otherwise throws NonUniqueDiscoveredSqlAliasException
.
List<Object> entities = entityManager.createNativeQuery(
"SELECT * " +
"FROM Person pr, Partner pt " +
"WHERE pr.name = pt.name" )
.getResultList();
List<Object> entities = session.createNativeQuery(
"SELECT * " +
"FROM Person pr, Partner pt " +
"WHERE pr.name = pt.name" )
.list();
The query was intended to return all Person
and Partner
instances with the same name.
The query fails because there is a conflict of names since the two entities are mapped to the same column names (e.g. id
, name
, version
).
Also, on some databases, the returned column aliases will most likely be on the form pr.id
, pr.name
, etc.
which are not equal to the columns specified in the mappings (id
and name
).
The following form is not vulnerable to column name duplication:
List<Object> entities = session.createNativeQuery(
"SELECT {pr.*}, {pt.*} " +
"FROM Person pr, Partner pt " +
"WHERE pr.name = pt.name" )
.addEntity( "pr", Person.class)
.addEntity( "pt", Partner.class)
.list();
There’s no such equivalent in JPA because the |
The {pr.}
and {pt.}
notation used above is shorthand for "all properties".
Alternatively, you can list the columns explicitly, but even in this case, Hibernate injects the SQL column aliases for each property.
The placeholder for a column alias is just the property name qualified by the table alias.
17.6. Alias and property references
In most cases, the above alias injection is needed. For queries relating to more complex mappings, like composite properties, inheritance discriminators, collections etc., you can use specific aliases that allow Hibernate to inject the proper aliases.
The following table shows the different ways you can use the alias injection. Please note that the alias names in the result are simply examples, each alias will have a unique and probably different name when used.
Description | Syntax | Example |
---|---|---|
A simple property |
|
|
A composite property |
|
|
Discriminator of an entity |
|
|
All properties of an entity |
|
|
A collection key |
|
|
The id of a collection |
|
|
The element of a collection |
|
|
property of the element in the collection |
|
|
All properties of the element in the collection |
|
|
All properties of the collection |
|
|
17.7. Returning DTOs (Data Transfer Objects)
It is possible to apply a ResultTransformer
to native SQL queries, allowing it to return non-managed entities.
public class PersonSummaryDTO {
private Number id;
private String name;
//Getters and setters are omitted for brevity
public Number getId() {
return id;
}
public void setId(Number id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
List<PersonSummaryDTO> dtos = session.createNativeQuery(
"SELECT p.id as \"id\", p.name as \"name\" " +
"FROM Person p")
.setResultTransformer( Transformers.aliasToBean( PersonSummaryDTO.class ) )
.list();
There’s no such equivalent in JPA because the |
The above query will return a list of PersonSummaryDTO
which has been instantiated and injected the values of id
and name
into its corresponding properties or fields.
17.8. Handling inheritance
Native SQL queries which query for entities that are mapped as part of an inheritance must include all properties for the base class and all its subclasses.
List<CreditCardPayment> payments = session.createNativeQuery(
"SELECT * " +
"FROM Payment p " +
"JOIN CreditCardPayment cp on cp.id = p.id" )
.addEntity( CreditCardPayment.class )
.list();
There’s no such equivalent in JPA because the |
17.9. Parameters
Native SQL queries support positional as well as named parameters:
List<Person> persons = entityManager.createNativeQuery(
"SELECT * " +
"FROM Person " +
"WHERE name like :name", Person.class )
.setParameter("name", "J%")
.getResultList();
List<Person> persons = session.createNativeQuery(
"SELECT * " +
"FROM Person " +
"WHERE name like :name" )
.addEntity( Person.class )
.setParameter("name", "J%")
.list();
17.10. Named SQL queries
Named SQL queries can also be defined during mapping and called in exactly the same way as a named HQL query.
In this case, you do not need to call addEntity()
anymore.
JPA defines the javax.persistence.NamedNativeQuery
annotation for this purpose,
and the Hibernate org.hibernate.annotations.NamedNativeQuery
annotation extends it and adds the following attributes:
flushMode()
-
The flush mode for the query. By default, it uses the current Persistence Context flush mode.
cacheable()
-
Whether the query (results) is cacheable or not. By default, queries are not cached.
cacheRegion()
-
If the query results are cacheable, name the query cache region to use.
fetchSize()
-
The number of rows fetched by the JDBC Driver per database trip. The default value is given by the JDBC driver.
timeout()
-
The query timeout (in seconds). By default, there’s no timeout.
callable()
-
Does the SQL query represent a call to a procedure/function? The default is false.
comment()
-
A comment added to the SQL query for tuning the execution plan.
cacheMode()
-
The cache mode used for this query. This refers to entities/collections returned by the query. The default value is
CacheModeType.NORMAL
. readOnly()
-
Whether the results should be read-only. By default, queries are not read-only so entities are stored in the Persistence Context.
17.10.1. Named SQL queries selecting scalar values
To fetch a single column of given table, the named query looks as follows:
NamedNativeQuery
@NamedNativeQuery(
name = "find_person_name",
query =
"SELECT name " +
"FROM Person "
),
List<String> names = entityManager.createNamedQuery(
"find_person_name" )
.getResultList();
List<String> names = session.getNamedQuery(
"find_person_name" )
.list();
Selecting multiple scalar values is done like this:
NamedNativeQuery
@NamedNativeQuery(
name = "find_person_name_and_nickName",
query =
"SELECT " +
" name, " +
" nickName " +
"FROM Person "
),
Without specifying an explicit result type, Hibernate will assume an Object
array:
List<Object[]> tuples = entityManager.createNamedQuery(
"find_person_name_and_nickName" )
.getResultList();
for(Object[] tuple : tuples) {
String name = (String) tuple[0];
String nickName = (String) tuple[1];
}
List<Object[]> tuples = session.getNamedQuery(
"find_person_name_and_nickName" )
.list();
for(Object[] tuple : tuples) {
String name = (String) tuple[0];
String nickName = (String) tuple[1];
}
It’s possible to use a DTO to store the resulting scalar values:
public class PersonNames {
private final String name;
private final String nickName;
public PersonNames(String name, String nickName) {
this.name = name;
this.nickName = nickName;
}
public String getName() {
return name;
}
public String getNickName() {
return nickName;
}
}
NamedNativeQuery
with ConstructorResult
@NamedNativeQuery(
name = "find_person_name_and_nickName_dto",
query =
"SELECT " +
" name, " +
" nickName " +
"FROM Person ",
resultSetMapping = "name_and_nickName_dto"
),
@SqlResultSetMapping(
name = "name_and_nickName_dto",
classes = @ConstructorResult(
targetClass = PersonNames.class,
columns = {
@ColumnResult(name = "name"),
@ColumnResult(name = "nickName")
}
)
)
List<PersonNames> personNames = entityManager.createNamedQuery(
"find_person_name_and_nickName_dto" )
.getResultList();
List<PersonNames> personNames = session.getNamedQuery(
"find_person_name_and_nickName_dto" )
.list();
You can also use the @NamedNativeQuery
Hibernate annotation
to customize the named query using various configurations such as fetch mode, cacheability, time out interval.
ConstructorResult
and Hibernate NamedNativeQuery
@NamedNativeQueries({
@NamedNativeQuery(
name = "get_person_phone_count",
query = "SELECT pr.name AS name, count(*) AS phoneCount " +
"FROM Phone p " +
"JOIN Person pr ON pr.id = p.person_id " +
"GROUP BY pr.name",
resultSetMapping = "person_phone_count",
timeout = 1,
readOnly = true
),
})
@SqlResultSetMapping(
name = "person_phone_count",
classes = @ConstructorResult(
targetClass = PersonPhoneCount.class,
columns = {
@ColumnResult(name = "name"),
@ColumnResult(name = "phoneCount")
}
)
)
NamedNativeQuery
named native query selecting multiple scalar values into a DTOList<PersonPhoneCount> personNames = session.getNamedNativeQuery(
"get_person_phone_count")
.getResultList();
17.10.2. Named SQL queries selecting entities
Considering the following named query:
NamedNativeQuery
@NamedNativeQuery(
name = "find_person_by_name",
query =
"SELECT " +
" p.id AS \"id\", " +
" p.name AS \"name\", " +
" p.nickName AS \"nickName\", " +
" p.address AS \"address\", " +
" p.createdOn AS \"createdOn\", " +
" p.version AS \"version\" " +
"FROM Person p " +
"WHERE p.name LIKE :name",
resultClass = Person.class
),
The result set mapping declares the entities retrieved by this native query. Each field of the entity is bound to an SQL alias (or column name). All fields of the entity including the ones of subclasses and the foreign key columns of related entities have to be present in the SQL query. Field definitions are optional provided that they map to the same column name as the one declared on the class property.
Executing this named native query can be done as follows:
List<Person> persons = entityManager.createNamedQuery(
"find_person_by_name" )
.setParameter("name", "J%")
.getResultList();
List<Person> persons = session.getNamedQuery(
"find_person_by_name" )
.setParameter("name", "J%")
.list();
To join multiple entities, you need to use a SqlResultSetMapping
for each entity the SQL query is going to fetch.
NamedNativeQuery
@NamedNativeQuery(
name = "find_person_with_phones_by_name",
query =
"SELECT " +
" pr.id AS \"pr.id\", " +
" pr.name AS \"pr.name\", " +
" pr.nickName AS \"pr.nickName\", " +
" pr.address AS \"pr.address\", " +
" pr.createdOn AS \"pr.createdOn\", " +
" pr.version AS \"pr.version\", " +
" ph.id AS \"ph.id\", " +
" ph.person_id AS \"ph.person_id\", " +
" ph.phone_number AS \"ph.number\", " +
" ph.phone_type AS \"ph.type\" " +
"FROM Person pr " +
"JOIN Phone ph ON pr.id = ph.person_id " +
"WHERE pr.name LIKE :name",
resultSetMapping = "person_with_phones"
)
@SqlResultSetMapping(
name = "person_with_phones",
entities = {
@EntityResult(
entityClass = Person.class,
fields = {
@FieldResult( name = "id", column = "pr.id" ),
@FieldResult( name = "name", column = "pr.name" ),
@FieldResult( name = "nickName", column = "pr.nickName" ),
@FieldResult( name = "address", column = "pr.address" ),
@FieldResult( name = "createdOn", column = "pr.createdOn" ),
@FieldResult( name = "version", column = "pr.version" ),
}
),
@EntityResult(
entityClass = Phone.class,
fields = {
@FieldResult( name = "id", column = "ph.id" ),
@FieldResult( name = "person", column = "ph.person_id" ),
@FieldResult( name = "number", column = "ph.number" ),
@FieldResult( name = "type", column = "ph.type" ),
}
)
}
),
List<Object[]> tuples = entityManager.createNamedQuery(
"find_person_with_phones_by_name" )
.setParameter("name", "J%")
.getResultList();
for(Object[] tuple : tuples) {
Person person = (Person) tuple[0];
Phone phone = (Phone) tuple[1];
}
List<Object[]> tuples = session.getNamedQuery(
"find_person_with_phones_by_name" )
.setParameter("name", "J%")
.list();
for(Object[] tuple : tuples) {
Person person = (Person) tuple[0];
Phone phone = (Phone) tuple[1];
}
Finally, if the association to a related entity involves a composite primary key, a @FieldResult
element should be used for each foreign key column.
The @FieldResult
name is composed of the property name for the relationship, followed by a dot ("."), followed by the name or the field or property of the primary key.
For this example, the following entities are going to be used:
@Embeddable
public class Dimensions {
private int length;
private int width;
//Getters and setters are omitted for brevity
}
@Embeddable
public class Identity implements Serializable {
private String firstname;
private String lastname;
//Getters and setters are omitted for brevity
public boolean equals(Object o) {
if ( this == o ) return true;
if ( o == null || getClass() != o.getClass() ) return false;
final Identity identity = (Identity) o;
if ( !firstname.equals( identity.firstname ) ) return false;
if ( !lastname.equals( identity.lastname ) ) return false;
return true;
}
public int hashCode() {
int result;
result = firstname.hashCode();
result = 29 * result + lastname.hashCode();
return result;
}
}
@Entity
public class Captain {
@EmbeddedId
private Identity id;
//Getters and setters are omitted for brevity
}
@Entity
@NamedNativeQueries({
@NamedNativeQuery(name = "find_all_spaceships",
query =
"SELECT " +
" name as \"name\", " +
" model, " +
" speed, " +
" lname as lastn, " +
" fname as firstn, " +
" length, " +
" width, " +
" length * width as surface, " +
" length * width * 10 as volume " +
"FROM SpaceShip",
resultSetMapping = "spaceship"
)
})
@SqlResultSetMapping(
name = "spaceship",
entities = @EntityResult(
entityClass = SpaceShip.class,
fields = {
@FieldResult(name = "name", column = "name"),
@FieldResult(name = "model", column = "model"),
@FieldResult(name = "speed", column = "speed"),
@FieldResult(name = "captain.lastname", column = "lastn"),
@FieldResult(name = "captain.firstname", column = "firstn"),
@FieldResult(name = "dimensions.length", column = "length"),
@FieldResult(name = "dimensions.width", column = "width"),
}
),
columns = {
@ColumnResult(name = "surface"),
@ColumnResult(name = "volume")
}
)
public class SpaceShip {
@Id
private String name;
private String model;
private double speed;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumns({
@JoinColumn(name = "fname", referencedColumnName = "firstname"),
@JoinColumn(name = "lname", referencedColumnName = "lastname")
})
private Captain captain;
private Dimensions dimensions;
//Getters and setters are omitted for brevity
}
List<Object[]> tuples = entityManager.createNamedQuery(
"find_all_spaceships" )
.getResultList();
for(Object[] tuple : tuples) {
SpaceShip spaceShip = (SpaceShip) tuple[0];
Number surface = (Number) tuple[1];
Number volume = (Number) tuple[2];
}
List<Object[]> tuples = session.getNamedQuery(
"find_all_spaceships" )
.list();
for(Object[] tuple : tuples) {
SpaceShip spaceShip = (SpaceShip) tuple[0];
Number surface = (Number) tuple[1];
Number volume = (Number) tuple[2];
}
17.11. Resolving global catalog and schema in native SQL queries
When using multiple database catalogs and schemas, Hibernate offers the possibility of setting a global catalog or schema so that you don’t have to declare it explicitly for every entity.
<property name="hibernate.default_catalog" value="crm"/>
<property name="hibernate.default_schema" value="analytics"/>
This way, we can imply the global crm catalog and analytics schema in every JPQL, HQL or Criteria API query.
However, for native queries, the SQL query is passed as is, therefore you need to explicitly set the global catalog and schema whenever you are referencing a database table. Fortunately, Hibernate allows you to resolve the current global catalog and schema using the following placeholders:
- {h-catalog}
-
resolves the current
hibernate.default_catalog
configuration property value. - {h-schema}
-
resolves the current
hibernate.default_schema
configuration property value. - {h-domain}
-
resolves the current
hibernate.default_catalog
andhibernate.default_schema
configuration property values (e.g. catalog.schema).
With these placeholders, you can imply the catalog, schema, or both catalog and schema for every native query.
So, when running the following native query:
@NamedNativeQuery(
name = "last_30_days_hires",
query =
"select * " +
"from {h-domain}person " +
"where age(hired_on) < '30 days'",
resultClass = Person.class
)
Hibernate is going to resolve the {h-domain}
placeholder according to the values of the default catalog and schema:
SELECT *
FROM crm.analytics.person
WHERE age(hired_on) < '30 days'
17.12. Using stored procedures for querying
Hibernate provides support for queries via stored procedures and functions.
A stored procedure arguments are declared using the IN
parameter type, and the result can be either marked with an OUT
parameter type, a REF_CURSOR
or it could just return the result like a function.
OUT
parameter typestatement.executeUpdate(
"CREATE PROCEDURE sp_count_phones (" +
" IN personId INT, " +
" OUT phoneCount INT " +
") " +
"BEGIN " +
" SELECT COUNT(*) INTO phoneCount " +
" FROM Phone p " +
" WHERE p.person_id = personId; " +
"END"
);
To use this stored procedure, you can execute the following JPA 2.1 query:
OUT
parameter type using JPAStoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_count_phones");
query.registerStoredProcedureParameter( "personId", Long.class, ParameterMode.IN);
query.registerStoredProcedureParameter( "phoneCount", Long.class, ParameterMode.OUT);
query.setParameter("personId", 1L);
query.execute();
Long phoneCount = (Long) query.getOutputParameterValue("phoneCount");
OUT
parameter type using HibernateSession session = entityManager.unwrap( Session.class );
ProcedureCall call = session.createStoredProcedureCall( "sp_count_phones" );
call.registerParameter( "personId", Long.class, ParameterMode.IN ).bindValue( 1L );
call.registerParameter( "phoneCount", Long.class, ParameterMode.OUT );
Long phoneCount = (Long) call.getOutputs().getOutputParameterValue( "phoneCount" );
assertEquals( Long.valueOf( 2 ), phoneCount );
If the stored procedure outputs the result directly without an OUT
parameter type:
OUT
parameter typestatement.executeUpdate(
"CREATE PROCEDURE sp_phones(IN personId INT) " +
"BEGIN " +
" SELECT * " +
" FROM Phone " +
" WHERE person_id = personId; " +
"END"
);
You can retrieve the results of the aforementioned MySQL stored procedure as follows:
OUT
parameter type using JPAStoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_phones");
query.registerStoredProcedureParameter( 1, Long.class, ParameterMode.IN);
query.setParameter(1, 1L);
List<Object[]> personComments = query.getResultList();
OUT
parameter type using HibernateSession session = entityManager.unwrap( Session.class );
ProcedureCall call = session.createStoredProcedureCall( "sp_phones" );
call.registerParameter( 1, Long.class, ParameterMode.IN ).bindValue( 1L );
Output output = call.getOutputs().getCurrent();
List<Object[]> personComments = ( (ResultSetOutput) output ).getResultList();
For a REF_CURSOR
result sets, we’ll consider the following Oracle stored procedure:
REF_CURSOR
stored procedurestatement.executeUpdate(
"CREATE OR REPLACE PROCEDURE sp_person_phones ( " +
" personId IN NUMBER, " +
" personPhones OUT SYS_REFCURSOR ) " +
"AS " +
"BEGIN " +
" OPEN personPhones FOR " +
" SELECT *" +
" FROM phone " +
" WHERE person_id = personId; " +
"END;"
);
|
This function can be called using the standard Java Persistence API:
REF_CURSOR
stored procedure using JPAStoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_person_phones" );
query.registerStoredProcedureParameter( 1, Long.class, ParameterMode.IN );
query.registerStoredProcedureParameter( 2, Class.class, ParameterMode.REF_CURSOR );
query.setParameter( 1, 1L );
query.execute();
List<Object[]> postComments = query.getResultList();
REF_CURSOR
stored procedure using HibernateSession session = entityManager.unwrap(Session.class);
ProcedureCall call = session.createStoredProcedureCall( "sp_person_phones");
call.registerParameter(1, Long.class, ParameterMode.IN).bindValue(1L);
call.registerParameter(2, Class.class, ParameterMode.REF_CURSOR);
Output output = call.getOutputs().getCurrent();
List<Object[]> postComments = ( (ResultSetOutput) output ).getResultList();
assertEquals(2, postComments.size());
If the database defines an SQL function:
statement.executeUpdate(
"CREATE FUNCTION fn_count_phones(personId integer) " +
"RETURNS integer " +
"DETERMINISTIC " +
"READS SQL DATA " +
"BEGIN " +
" DECLARE phoneCount integer; " +
" SELECT COUNT(*) INTO phoneCount " +
" FROM Phone p " +
" WHERE p.person_id = personId; " +
" RETURN phoneCount; " +
"END"
);
Because the current StoredProcedureQuery
implementation doesn’t yet support SQL functions,
we need to use the JDBC syntax.
This limitation is acknowledged and will be addressed by the HHH-10530 issue. |
final AtomicReference<Integer> phoneCount = new AtomicReference<>();
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try (CallableStatement function = connection.prepareCall(
"{ ? = call fn_count_phones(?) }" )) {
function.registerOutParameter( 1, Types.INTEGER );
function.setInt( 2, 1 );
function.execute();
phoneCount.set( function.getInt( 1 ) );
}
} );
Stored procedure queries cannot be paged with Since these servers can return multiple result sets and update counts, Hibernate will iterate the results and take the first result that is a result set as its return value, so everything else will be discarded. For SQL Server, if you can enable |
17.13. Using named queries to call stored procedures
Just like with SQL statements, you can also use named queries to call stored procedures.
For this purpose, JPA defines the @NamedStoredProcedureQuery
annotation.
REF_CURSOR
named query stored procedure@NamedStoredProcedureQueries(
@NamedStoredProcedureQuery(
name = "sp_person_phones",
procedureName = "sp_person_phones",
parameters = {
@StoredProcedureParameter(
name = "personId",
type = Long.class,
mode = ParameterMode.IN
),
@StoredProcedureParameter(
name = "personPhones",
type = Class.class,
mode = ParameterMode.REF_CURSOR
)
}
)
)
Calling this stored procedure is straightforward, as illustrated by the following example.
REF_CURSOR
stored procedure using a JPA named queryList<Object[]> postComments = entityManager
.createNamedStoredProcedureQuery( "sp_person_phones" )
.setParameter( "personId", 1L )
.getResultList();
17.14. Custom SQL for CRUD (Create, Read, Update and Delete)
Hibernate can use custom SQL for CRUD operations. The SQL can be overridden at the statement level or individual column level. This section describes statement overrides. For columns, see Column transformers: read and write expressions.
The following example shows how to define custom SQL operations using annotations.
@SQLInsert
, @SQLUpdate
, and @SQLDelete
override the INSERT, UPDATE, DELETE statements of a given entity.
For the SELECT clause, a @Loader
must be defined along with a @NamedNativeQuery
used for loading the underlying table record.
For collections, Hibernate allows defining a custom @SQLDeleteAll
which is used for removing all child records associated with a given parent entity.
To filter collections, the @Where
annotation allows customizing the underlying SQL WHERE clause.
@Entity(name = "Person")
@SQLInsert(
sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, true) ",
check = ResultCheckStyle.COUNT
)
@SQLUpdate(
sql = "UPDATE person SET name = ? where id = ? "
)
@SQLDelete(
sql = "UPDATE person SET valid = false WHERE id = ? "
)
@Loader(namedQuery = "find_valid_person")
@NamedNativeQueries({
@NamedNativeQuery(
name = "find_valid_person",
query = "SELECT id, name " +
"FROM person " +
"WHERE id = ? and valid = true",
resultClass = Person.class
)
})
public static class Person {
@Id
@GeneratedValue
private Long id;
private String name;
@ElementCollection
@SQLInsert(
sql = "INSERT INTO person_phones (person_id, phones, valid) VALUES (?, ?, true) ")
@SQLDeleteAll(
sql = "UPDATE person_phones SET valid = false WHERE person_id = ?")
@Where( clause = "valid = true" )
private List<String> phones = new ArrayList<>();
//Getters and setters are omitted for brevity
}
In the example above, the entity is mapped so that entries are soft-deleted (the records are not removed from the database, but instead, a flag marks the row validity).
The Person
entity benefits from custom INSERT, UPDATE, and DELETE statements which update the valid
column accordingly.
The custom @Loader
is used to retrieve only Person
rows that are valid.
The same is done for the phones
collection. The @SQLDeleteAll
and the SQLInsert
queries are used whenever the collection is modified.
You can also call a store procedure using the custom CRUD statements.
The only requirement is to set the |
To check that the execution happens correctly, Hibernate allows you to define one of those three strategies:
-
none: no check is performed; the store procedure is expected to fail upon constraint violations.
-
count: use of row-count returned by the
executeUpdate()
method call to check that the update was successful. -
param: like count but using a
CallableStatement
output parameter.
To define the result check style, use the check
parameter.
The parameter order is important and is defined by the order Hibernate handles properties. You can see the expected order by enabling debug logging, so Hibernate can print out the static SQL that is used to create, update, delete entities. To see the expected sequence, remember to not include your custom SQL through annotations or mapping files as that will override the Hibernate generated static SQL. |
Overriding SQL statements for secondary tables is also possible using @org.hibernate.annotations.Table
and the sqlInsert
, sqlUpdate
, sqlDelete
attributes.
@Entity(name = "Person")
@Table(name = "person")
@SQLInsert(
sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, true) "
)
@SQLDelete(
sql = "UPDATE person SET valid = false WHERE id = ? "
)
@SecondaryTable(name = "person_details",
pkJoinColumns = @PrimaryKeyJoinColumn(name = "person_id"))
@org.hibernate.annotations.Table(
appliesTo = "person_details",
sqlInsert = @SQLInsert(
sql = "INSERT INTO person_details (image, person_id, valid) VALUES (?, ?, true) ",
check = ResultCheckStyle.COUNT
),
sqlDelete = @SQLDelete(
sql = "UPDATE person_details SET valid = false WHERE person_id = ? "
)
)
@Loader(namedQuery = "find_valid_person")
@NamedNativeQueries({
@NamedNativeQuery(
name = "find_valid_person",
query = "SELECT " +
" p.id, " +
" p.name, " +
" pd.image " +
"FROM person p " +
"LEFT OUTER JOIN person_details pd ON p.id = pd.person_id " +
"WHERE p.id = ? AND p.valid = true AND pd.valid = true",
resultClass = Person.class
)
})
public static class Person {
@Id
@GeneratedValue
private Long id;
private String name;
@Column(name = "image", table = "person_details")
private byte[] image;
//Getters and setters are omitted for brevity
}
The SQL is directly executed in your database, so you can use any dialect you like. This will, however, reduce the portability of your mapping if you use database-specific SQL. |
You can also use stored procedures for customizing the CRUD statements.
Assuming the following stored procedure:
statement.executeUpdate(
"CREATE OR REPLACE PROCEDURE sp_delete_person ( " +
" personId IN NUMBER ) " +
"AS " +
"BEGIN " +
" UPDATE person SET valid = 0 WHERE id = personId; " +
"END;"
);}
The entity can use this stored procedure to soft-delete the entity in question:
@SQLDelete(
sql = "{ call sp_delete_person( ? ) } ",
callable = true
)
You need to set the |
18. Spatial
18.1. Overview
Hibernate Spatial was originally developed as a generic extension to Hibernate for handling geographic data. Since 5.0, Hibernate Spatial is now part of the Hibernate ORM project, and it allows you to deal with geographic data in a standardized way.
Hibernate Spatial provides a standardized, cross-database interface to geographic data storage and query functions. It supports most of the functions described by the OGC Simple Feature Specification. Supported databases are Oracle 10g/11g, PostgreSQL/PostGIS, MySQL, Microsoft SQL Server and H2/GeoDB.
Spatial data types are not part of the Java standard library, and they are absent from the JDBC specification. Over the years JTS has emerged the de facto standard to fill this gap. JTS is an implementation of the Simple Feature Specification (SFS). Many databases on the other hand implement the SQL/MM - Part 3: Spatial Data specification - a related, but broader specification. The biggest difference is that SFS is limited to 2D geometries in the projected plane (although JTS supports 3D coordinates), whereas SQL/MM supports 2-, 3- or 4-dimensional coordinate spaces.
Hibernate Spatial supports two different geometry models: JTS and
geolatte-geom. As already mentioned, JTS is the de facto
standard. Geolatte-geom (also written by the lead developer of Hibernate Spatial) is a more recent library that
supports many features specified in SQL/MM but not available in JTS (such as support for 4D geometries, and support for extended WKT/WKB formats).
Geolatte-geom also implements encoders/decoders for the database native types. Geolatte-geom has good interoperability with
JTS. Converting a Geolatte geometry
to a JTS `geometry, for instance, doesn’t require copying of the coordinates.
It also delegates spatial processing to JTS.
Whether you use JTS or Geolatte-geom, Hibernate spatial maps the database spatial types to your geometry model of choice. It will, however, always use Geolatte-geom to decode the database native types.
Hibernate Spatial also makes a number of spatial functions available in HQL and in the Criteria Query API. These functions are specified in both SQL/MM as SFS, and are commonly implemented in databases with spatial support (see Hibernate Spatial dialect function support)
18.2. Configuration
Hibernate Spatial requires some configuration prior to start using it.
18.2.1. Dependency
You need to include the hibernate-spatial
dependency in your build environment.
For Maven, you need to add the following dependency:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-spatial</artifactId>
<version>${hibernate.version}</version>
</dependency>
18.2.2. Dialects
Hibernate Spatial extends the Hibernate ORM dialects so that the spatial functions of the database are made available within HQL and JPQL.
So, for instance, instead of using the PostgreSQL82Dialect
, we use the Hibernate Spatial extension of that dialect which is the PostgisDialect
.
<property
name="hibernate.dialect"
value="org.hibernate.spatial.dialect.postgis.PostgisDialect"
/>
Not all databases support all the functions defined by Hibernate Spatial. The table below provides an overview of the functions provided by each database. If the function is defined in the Simple Feature Specification, the description references the relevant section.
Function |
Description |
PostgresSQL |
Oracle 10g/11g |
MySQL |
SQLServer |
GeoDB (H2) |
DB2 |
CockroachDB |
Basic functions on Geometry |
||||||||
|
SFS §2.1.1.1 |
|||||||
|
SFS §2.1.1.1 |
|||||||
|
SFS §2.1.1.1 |
|||||||
|
SFS §2.1.1.1 |
|||||||
|
SFS §2.1.1.1 |
|||||||
|
SFS §2.1.1.1 |
|||||||
|
SFS §2.1.1.1 |
|||||||
|
SFS §2.1.1.1 |
|||||||
|
SFS §2.1.1.1 |
|||||||
Functions for testing Spatial Relations between geometric objects |
||||||||
|
SFS §2.1.1.2 |
|||||||
|
SFS §2.1.1.2 |
|||||||
|
SFS §2.1.1.2 |
|||||||
|
SFS §2.1.1.2 |
|||||||
|
SFS §2.1.1.2 |
|||||||
|
SFS §2.1.1.2 |
|||||||
|
SFS §2.1.1.2 |
|||||||
|
SFS §2.1.1.2 |
|||||||
|
SFS §2.1.1.2 |
|||||||
Functions that support Spatial Analysis |
||||||||
|
SFS §2.1.1.3 |
|||||||
|
SFS §2.1.1.3 |
|||||||
|
SFS §2.1.1.3 |
(1) |
||||||
|
SFS §2.1.1.3 |
(1) |
||||||
|
SFS §2.1.1.3 (renamed from union) |
(1) |
||||||
|
SFS §2.1.1.3 |
(1) |
||||||
|
SFS §2.1.1.3 |
(1) |
||||||
Common non-SFS functions |
||||||||
|
Returns true if the geometries are within the specified distance of one another |
|||||||
|
Returns a new geometry with its coordinates transformed to the SRID referenced by the integer parameter |
|||||||
Spatial aggregate Functions |
||||||||
|
Returns a bounding box that bounds the set of returned geometries |
(1) Argument Geometries need to have the same dimensionality.
- Postgis
-
For Postgis from versions 1.3 and later, the best dialect to use is
org.hibernate.spatial.dialect.postgis.PostgisDialect
.This translates the HQL spatial functions to the Postgis SQL/MM-compliant functions. For older, pre v1.3 versions of Postgis, which are not SQL/MM compliant, the dialect
org.hibernate.spatial.dialect.postgis.PostgisNoSQLMM
is provided.
- MySQL
-
There are several dialects for MySQL:
MySQLSpatialDialect
-
a spatially-extended version of Hibernate
MySQLDialect
MySQL5SpatialDialect
-
a spatially-extended version of Hibernate
MySQL5Dialect
MySQLSpatial56Dialect
-
a spatially-extended version of Hibernate
MySQL55Dialect
.
MySQL versions before 5.6.1 had only limited support for spatial operators. Most operators only took account of the minimum bounding rectangles (MBR) of the geometries, and not the geometries themselves. This changed in version 5.6.1, when MySQL introduced These dialects may, therefore, produce results that differ from that of the other spatial dialects. For more information, see this page in the MySQL reference guide (esp. the section Functions That Test Spatial Relations Between Geometry Objects) |
- Oracle10g/11g
-
There is currently only one Oracle spatial dialect:
OracleSpatial10gDialect
which extends the Hibernate dialectOracle10gDialect
. This dialect has been tested on both Oracle 10g and Oracle 11g with theSDO_GEOMETRY
spatial database type.This dialect can be configured using the Hibernate property:
hibernate.spatial.connection_finder
-
the fully-qualified class name for the implementation of the
ConnectionFinder
to use (see below).
The
ConnectionFinder interfaceThe When the passed object is not already an In may cases, this strategy will suffice.
If not, you can provide your own implementation of this interface on the classpath, and configure it in the |
- SQL Server
-
The dialect
SqlServer2008Dialect
supports theGEOMETRY
type in SQL Server 2008 and later.
The |
- CockroachDB
-
The dialect
CockroachDB202SpatialDialect
support theGEOMETRY
type in CockroachDB v20.2 and later.
The |
- GeoDB (H2)
-
The
GeoDBDialect
supports the GeoDB a spatial extension of the H2 in-memory database.
The dialect has been tested with GeoDB version 0.7 |
- DB2
-
The
DB2SpatialDialect
supports the spatial extensions of the DB2 LUW database. The dialect has been tested with DB2 LUW 11.1. The dialect does not support DB2 for z/OS or DB2 column-oriented databases.
In order to use the DB2 Hibernate Spatial capabilities, it is necessary to first execute the following SQL statements which will allow DB2 to accept Extended WellKnown Text (EWKT) data and return EWKT data. One way to do this is to copy these statements into a file such as ewkt.sql and execute it in a DB2 command window with a command like 'db2 -tvf ewkt.sql'.
|
18.3. Types
Hibernate Spatial comes with the following types:
- jts_geometry
-
Handled by
org.hibernate.spatial.JTSGeometryType
it maps a database geometry column type to aorg.locationtech.jts.geom.Geometry
entity property type. - geolatte_geometry
-
Handled by
org.hibernate.spatial.GeolatteGeometryType
, it maps a database geometry column type to anorg.geolatte.geom.Geometry
entity property type.
It suffices to declare a property as either a JTS or a Geolatte-geom Geometry
and Hibernate Spatial will map it using the
relevant type.
Here is an example using JTS:
import org.locationtech.jts.geom.Point;
@Entity(name = "Event")
public static class Event {
@Id
private Long id;
private String name;
private Point location;
//Getters and setters are omitted for brevity
}
We can now treat spatial geometries like any other type.
Event event = new Event();
event.setId( 1L);
event.setName( "Hibernate ORM presentation");
Point point = geometryFactory.createPoint( new Coordinate( 10, 5 ) );
event.setLocation( point );
entityManager.persist( event );
Spatial Dialects defines many query functions that are available both in HQL and JPQL queries. Below we show how we
could use the within
function to find all objects within a given spatial extent or window.
Polygon window = geometryFactory.createPolygon( coordinates );
Event event = entityManager.createQuery(
"select e " +
"from Event e " +
"where within(e.location, :window) = true", Event.class)
.setParameter("window", window)
.getSingleResult();
19. Multitenancy
19.1. What is multitenancy?
The term multitenancy, in general, is applied to software development to indicate an architecture in which a single running instance of an application simultaneously serves multiple clients (tenants). This is highly common in SaaS solutions. Isolating information (data, customizations, etc.) pertaining to the various tenants is a particular challenge in these systems. This includes the data owned by each tenant stored in the database. It is this last piece, sometimes called multitenant data, that we will focus on.
19.2. Multitenant data approaches
There are three main approaches to isolating information in these multitenant systems which go hand-in-hand with different database schema definitions and JDBC setups.
Each multitenancy strategy has pros and cons as well as specific techniques and considerations. Such topics are beyond the scope of this documentation. |
19.2.1. Separate database
Each tenant’s data is kept in a physically separate database instance. JDBC Connections would point specifically to each database so any pooling would be per-tenant. A general application approach, here, would be to define a JDBC Connection pool per-tenant and to select the pool to use based on the tenant identifier associated with the currently logged in user.
19.2.2. Separate schema
Each tenant’s data is kept in a distinct database schema on a single database instance. There are two different ways to define JDBC Connections here:
-
Connections could point specifically to each schema as we saw with the
Separate database
approach. This is an option provided that the driver supports naming the default schema in the connection URL or if the pooling mechanism supports naming a schema to use for its Connections. Using this approach, we would have a distinct JDBC Connection pool per-tenant where the pool to use would be selected based on the "tenant identifier" associated with the currently logged in user. -
Connections could point to the database itself (using some default schema) but the Connections would be altered using the SQL
SET SCHEMA
(or similar) command. Using this approach, we would have a single JDBC Connection pool for use to service all tenants, but before using the Connection, it would be altered to reference the schema named by the "tenant identifier" associated with the currently logged in user.
19.3. Partitioned (discriminator) data
All data is kept in a single database schema. The data for each tenant is partitioned by the use of partition value or discriminator. The complexity of this discriminator might range from a simple column value to a complex SQL formula. Again, this approach would use a single Connection pool to service all tenants. However, in this approach, the application needs to alter each and every SQL statement sent to the database to reference the "tenant identifier" discriminator.
19.4. Multitenancy in Hibernate
Using Hibernate with multitenant data comes down to both an API and then integration piece(s). As usual, Hibernate strives to keep the API simple and isolated from any underlying integration complexities. The API is really just defined by passing the tenant identifier as part of opening any session.
SessionFactory
private void doInSession(String tenant, Consumer<Session> function) {
Session session = null;
Transaction txn = null;
try {
session = sessionFactory
.withOptions()
.tenantIdentifier( tenant )
.openSession();
txn = session.getTransaction();
txn.begin();
function.accept(session);
txn.commit();
} catch (Throwable e) {
if ( txn != null ) txn.rollback();
throw e;
} finally {
if (session != null) {
session.close();
}
}
}
Additionally, when specifying the configuration, an org.hibernate.MultiTenancyStrategy
should be named using the hibernate.multiTenancy
setting.
Hibernate will perform validations based on the type of strategy you specify.
The strategy here correlates with the isolation approach discussed above.
- NONE
-
(the default) No multitenancy is expected. In fact, it is considered an error if a tenant identifier is specified when opening a session using this strategy.
- SCHEMA
-
Correlates to the separate schema approach. It is an error to attempt to open a session without a tenant identifier using this strategy. Additionally, a
MultiTenantConnectionProvider
must be specified. - DATABASE
-
Correlates to the separate database approach. It is an error to attempt to open a session without a tenant identifier using this strategy. Additionally, a
MultiTenantConnectionProvider
must be specified. - DISCRIMINATOR
-
Correlates to the partitioned (discriminator) approach. It is an error to attempt to open a session without a tenant identifier using this strategy. This strategy is not yet implemented and you can follow its progress via the HHH-6054 Jira issue.
19.4.1. MultiTenantConnectionProvider
When using either the DATABASE or SCHEMA approach, Hibernate needs to be able to obtain Connections in a tenant-specific manner.
That is the role of the MultiTenantConnectionProvider
contract.
Application developers will need to provide an implementation of this contract.
Most of its methods are extremely self-explanatory.
The only ones which might not be are getAnyConnection
and releaseAnyConnection
.
It is important to note also that these methods do not accept the tenant identifier.
Hibernate uses these methods during startup to perform various configuration, mainly via the java.sql.DatabaseMetaData
object.
The MultiTenantConnectionProvider
to use can be specified in a number of ways:
-
Use the
hibernate.multi_tenant_connection_provider
setting. It could name aMultiTenantConnectionProvider
instance, aMultiTenantConnectionProvider
implementation class reference or aMultiTenantConnectionProvider
implementation class name. -
Passed directly to the
org.hibernate.boot.registry.StandardServiceRegistryBuilder
. -
If none of the above options match, but the settings do specify a
hibernate.connection.datasource
value, Hibernate will assume it should use the specificDataSourceBasedMultiTenantConnectionProviderImpl
implementation which works on a number of pretty reasonable assumptions when running inside of an app server and using onejavax.sql.DataSource
per tenant. See its Javadocs for more details.
The following example portrays a MultiTenantConnectionProvider
implementation that handles multiple ConnectionProvider
s.
MultiTenantConnectionProvider
implementationpublic class ConfigurableMultiTenantConnectionProvider
extends AbstractMultiTenantConnectionProvider {
private final Map<String, ConnectionProvider> connectionProviderMap =
new HashMap<>( );
public ConfigurableMultiTenantConnectionProvider(
Map<String, ConnectionProvider> connectionProviderMap) {
this.connectionProviderMap.putAll( connectionProviderMap );
}
@Override
protected ConnectionProvider getAnyConnectionProvider() {
return connectionProviderMap.values().iterator().next();
}
@Override
protected ConnectionProvider selectConnectionProvider(String tenantIdentifier) {
return connectionProviderMap.get( tenantIdentifier );
}
}
The ConfigurableMultiTenantConnectionProvider
can be set up as follows:
MultiTenantConnectionProvider
usage exampleprivate void init() {
registerConnectionProvider( FRONT_END_TENANT );
registerConnectionProvider( BACK_END_TENANT );
Map<String, Object> settings = new HashMap<>( );
settings.put( AvailableSettings.MULTI_TENANT, multiTenancyStrategy() );
settings.put( AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER,
new ConfigurableMultiTenantConnectionProvider( connectionProviderMap ) );
sessionFactory = sessionFactory(settings);
}
protected void registerConnectionProvider(String tenantIdentifier) {
Properties properties = properties();
properties.put( Environment.URL,
tenantUrl(properties.getProperty( Environment.URL ), tenantIdentifier) );
DriverManagerConnectionProviderImpl connectionProvider =
new DriverManagerConnectionProviderImpl();
connectionProvider.configure( properties );
connectionProviderMap.put( tenantIdentifier, connectionProvider );
}
When using multitenancy, it’s possible to save an entity with the same identifier across different tenants:
doInSession( FRONT_END_TENANT, session -> {
Person person = new Person( );
person.setId( 1L );
person.setName( "John Doe" );
session.persist( person );
} );
doInSession( BACK_END_TENANT, session -> {
Person person = new Person( );
person.setId( 1L );
person.setName( "John Doe" );
session.persist( person );
} );
19.4.2. CurrentTenantIdentifierResolver
org.hibernate.context.spi.CurrentTenantIdentifierResolver
is a contract for Hibernate to be able to resolve what the application considers the current tenant identifier.
The implementation to use can be either passed directly to Configuration
via its setCurrentTenantIdentifierResolver
method,
or be specified via the hibernate.tenant_identifier_resolver
setting.
There are two situations where CurrentTenantIdentifierResolver
is used:
-
The first situation is when the application is using the
org.hibernate.context.spi.CurrentSessionContext
feature in conjunction with multitenancy. In the case of the current-session feature, Hibernate will need to open a session if it cannot find an existing one in scope. However, when a session is opened in a multitenant environment, the tenant identifier has to be specified. This is where theCurrentTenantIdentifierResolver
comes into play; Hibernate will consult the implementation you provide to determine the tenant identifier to use when opening the session. In this case, it is required that aCurrentTenantIdentifierResolver
is supplied. -
The other situation is when you do not want to explicitly specify the tenant identifier all the time. If a
CurrentTenantIdentifierResolver
has been specified, Hibernate will use it to determine the default tenant identifier to use when opening the session.
Additionally, if the CurrentTenantIdentifierResolver
implementation returns true
for its validateExistingCurrentSessions
method, Hibernate will make sure any existing sessions that are found in scope have a matching tenant identifier.
This capability is only pertinent when the CurrentTenantIdentifierResolver
is used in current-session settings.
19.4.3. Caching
Multitenancy support in Hibernate works seamlessly with the Hibernate second level cache. The key used to cache data encodes the tenant identifier.
Currently, schema export will not really work with multitenancy. The JPA expert group is in the process of defining multitenancy support for an upcoming version of the specification. |
19.4.4. Multitenancy Hibernate Session configuration
When using multitenancy, you might want to configure each tenant-specific Session
differently.
For instance, each tenant could specify a different time zone configuration.
registerConnectionProvider( FRONT_END_TENANT, TimeZone.getTimeZone( "UTC" ) );
registerConnectionProvider( BACK_END_TENANT, TimeZone.getTimeZone( "CST" ) );
The registerConnectionProvider
method is used to define the tenant-specific context.
registerConnectionProvider
method used for defining the tenant-specific contextprotected void registerConnectionProvider(String tenantIdentifier, TimeZone timeZone) {
Properties properties = properties();
properties.put(
Environment.URL,
tenantUrl( properties.getProperty( Environment.URL ), tenantIdentifier )
);
DriverManagerConnectionProviderImpl connectionProvider =
new DriverManagerConnectionProviderImpl();
connectionProvider.configure( properties );
connectionProviderMap.put( tenantIdentifier, connectionProvider );
timeZoneTenantMap.put( tenantIdentifier, timeZone );
}
For our example, the tenant-specific context is held in the connectionProviderMap
and timeZoneTenantMap
.
private Map<String, ConnectionProvider> connectionProviderMap = new HashMap<>();
private Map<String, TimeZone> timeZoneTenantMap = new HashMap<>();
Now, when building the Hibernate Session
, aside from passing the tenant identifier,
we could also configure the Session
to use the tenant-specific time zone.
Session
can be configured using the tenant-specific contextprivate void doInSession(String tenant, Consumer<Session> function, boolean useTenantTimeZone) {
Session session = null;
Transaction txn = null;
try {
SessionBuilder sessionBuilder = sessionFactory
.withOptions()
.tenantIdentifier( tenant );
if ( useTenantTimeZone ) {
sessionBuilder.jdbcTimeZone( timeZoneTenantMap.get( tenant ) );
}
session = sessionBuilder.openSession();
txn = session.getTransaction();
txn.begin();
function.accept( session );
txn.commit();
}
catch (Throwable e) {
if ( txn != null ) {
txn.rollback();
}
throw e;
}
finally {
if ( session != null ) {
session.close();
}
}
}
So, if we set the useTenantTimeZone
parameter to true
, Hibernate will persist the Timestamp
properties using the
tenant-specific time zone. As you can see in the following example, the Timestamp
is successfully retrieved
even if the currently running JVM uses a different time zone.
useTenantTimeZone
allows you to persist a Timestamp
in the provided time zonedoInSession( FRONT_END_TENANT, session -> {
Person person = new Person();
person.setId( 1L );
person.setName( "John Doe" );
person.setCreatedOn( LocalDateTime.of( 2018, 11, 23, 12, 0, 0 ) );
session.persist( person );
}, true );
doInSession( BACK_END_TENANT, session -> {
Person person = new Person();
person.setId( 1L );
person.setName( "John Doe" );
person.setCreatedOn( LocalDateTime.of( 2018, 11, 23, 12, 0, 0 ) );
session.persist( person );
}, true );
doInSession( FRONT_END_TENANT, session -> {
Timestamp personCreationTimestamp = (Timestamp) session
.createNativeQuery(
"select p.created_on " +
"from Person p " +
"where p.id = :personId" )
.setParameter( "personId", 1L )
.getSingleResult();
assertEquals(
Timestamp.valueOf( LocalDateTime.of( 2018, 11, 23, 12, 0, 0 ) ),
personCreationTimestamp
);
}, true );
doInSession( BACK_END_TENANT, session -> {
Timestamp personCreationTimestamp = (Timestamp) session
.createNativeQuery(
"select p.created_on " +
"from Person p " +
"where p.id = :personId" )
.setParameter( "personId", 1L )
.getSingleResult();
assertEquals(
Timestamp.valueOf( LocalDateTime.of( 2018, 11, 23, 12, 0, 0 ) ),
personCreationTimestamp
);
}, true );
However, behind the scenes, we can see that Hibernate has saved the created_on
property in the tenant-specific time zone.
The following example shows you that the Timestamp
was saved in the UTC time zone, hence the offset displayed in the
test output.
useTenantTimeZone
property set to false
, the Timestamp
is fetched in the tenant-specific time zonedoInSession( FRONT_END_TENANT, session -> {
Timestamp personCreationTimestamp = (Timestamp) session
.createNativeQuery(
"select p.created_on " +
"from Person p " +
"where p.id = :personId" )
.setParameter( "personId", 1L )
.getSingleResult();
log.infof(
"The created_on timestamp value is: [%s]",
personCreationTimestamp
);
long timeZoneOffsetMillis =
Timestamp.valueOf( LocalDateTime.of( 2018, 11, 23, 12, 0, 0 ) ).getTime() -
personCreationTimestamp.getTime();
assertEquals(
TimeZone.getTimeZone(ZoneId.systemDefault()).getRawOffset(),
timeZoneOffsetMillis
);
log.infof(
"For the current time zone: [%s], the UTC time zone offset is: [%d]",
TimeZone.getDefault().getDisplayName(), timeZoneOffsetMillis
);
}, false );
SELECT
p.created_on
FROM
Person p
WHERE
p.id = ?
-- binding parameter [1] as [BIGINT] - [1]
-- extracted value ([CREATED_ON] : [TIMESTAMP]) - [2018-11-23 10:00:00.0]
-- The created_on timestamp value is: [2018-11-23 10:00:00.0]
-- For the current time zone: [Eastern European Time], the UTC time zone offset is: [7200000]
Notice that for the Eastern European Time
time zone, the time zone offset was 2 hours when the test was executed.
20. OSGi
20.1. OSGi Specification and Environment
Hibernate targets the OSGi 4.3 spec or later.
It was necessary to start with 4.3, over 4.2, due to our dependency on OSGi’s BundleWiring
for entity/mapping scanning.
Hibernate supports three types of configurations within OSGi.
-
Container-Managed JPA Container-Managed JPA
-
Unmanaged JPA Unmanaged JPA
-
Unmanaged Native Unmanaged Native
20.2. hibernate-osgi
Rather than embedding OSGi capabilities into hibernate-core, and sub-modules, hibernate-osgi was created.
It’s purposefully separated, isolating all OSGi dependencies.
It provides an OSGi-specific ClassLoader
(aggregates the container’s ClassLoader
with core and EntityManager
ClassLoader
s),
JPA persistence provider, SessionFactory
/EntityManagerFactory
bootstrapping, entities/mappings scanner, and service management.
20.3. features.xml
Apache Karaf environments tend to make heavy use of its "features" concept, where a feature is a set of order-specific bundles focused on a concise capability.
These features are typically defined in a features.xml
file.
Hibernate produces and releases its own features.xml
that defines a core hibernate-orm
, as well as additional features for optional functionality (caching, Envers, etc.).
This is included in the binary distribution, as well as deployed to the JBoss Nexus repository (using the org.hibernate
groupId and hibernate-osgi
artifactId with the karaf.xml
classifier).
Note that our features are versioned using the same ORM artifact versions they wrap. Also, note that the features are heavily tested against Karaf 3.0.3 as a part of our PaxExam-based integration tests. However, they’ll likely work on other versions as well.
hibernate-osgi, theoretically, supports a variety of OSGi containers, such as Equinox.
In that case, please use features.xm
as a reference for necessary bundles to activate and their correct ordering.
However, note that Karaf starts a number of bundles automatically, several of which would need to be installed manually on alternatives.
20.4. QuickStarts/Demos
All three configurations have a QuickStart/Demo available in the hibernate-demos project:
20.5. Container-Managed JPA
The Enterprise OSGi specification includes container-managed JPA.
The container is responsible for discovering persistence units in bundles and automatically creating the EntityManagerFactory
(one EntityManagerFactory
per PersistenceUnit
).
It uses the JPA provider (hibernate-osgi) that has registered itself with the OSGi PersistenceProvider
service.
20.6. Enterprise OSGi JPA Container
In order to utilize container-managed JPA, an Enterprise OSGi JPA container must be active in the runtime.
In Karaf, this means Aries JPA, which is included out-of-the-box (simply activate the jpa
and transaction
features).
Originally, we intended to include those dependencies within our own features.xml
.
However, after guidance from the Karaf and Aries teams, it was pulled out.
This allows Hibernate OSGi to be portable and not be directly tied to Aries versions, instead of having the user choose which to use.
That being said, the QuickStart/Demo projects include a sample features.xml showing which features need to be activated in Karaf in order to support this environment. As mentioned, use this purely as a reference!
20.7. persistence.xml
Similar to any other JPA setup, your bundle must include a persistence.xml
file. This is typically located in META-INF
.
20.8. DataSource
Typical Enterprise OSGi JPA usage includes a DataSource
installed in the container.
Your bundle’s persistence.xml
calls out the DataSource
through JNDI.
For example, you could install the following H2 DataSource
.
You can deploy the DataSource
manually (Karaf has a deploy
dir), or through a "blueprint bundle" (blueprint:file:/[PATH]/datasource-h2.xml
).
<?xml version="1.0" encoding="UTF-8"?>
<!--
First install the H2 driver using:
> install -s mvn:com.h2database/h2/1.3.163
Then copy this file to the deploy folder
-->
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
<bean id="dataSource" class="org.h2.jdbcx.JdbcDataSource">
<property name="URL" value="jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE"/>
<property name="user" value="sa"/>
<property name="password" value=""/>
</bean>
<service interface="javax.sql.DataSource" ref="dataSource">
<service-properties>
<entry key="osgi.jndi.service.name" value="jdbc/h2ds"/>
</service-properties>
</service>
</blueprint>
That DataSource
is then used by your persistence.xml
persistence-unit. The following works in Karaf, but the names may need tweaked in alternative containers.
<jta-data-source>osgi:service/javax.sql.DataSource/(osgi.jndi.service.name=jdbc/h2ds)</jta-data-source>
20.9. Bundle Package Imports
Your bundle’s manifest will need to import, at a minimum:
-
javax.persistence
-
org.hibernate.proxy
andjavassist.util.proxy
, due to Hibernate’s ability to return proxies for lazy initialization (Javassist enhancement occurs on the entity’sClassLoader
during runtime).
20.10. Obtaining an EntityManger
The easiest, and most supported, method of obtaining an EntityManager
utilizes OSGi’s OSGI-INF/blueprint/blueprint.xml
in your bundle.
The container takes the name of your persistence unit, then automatically injects an EntityManager
instance into your given bean attribute.
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns:jpa="http://aries.apache.org/xmlns/jpa/v1.0.0"
xmlns:tx="http://aries.apache.org/xmlns/transactions/v1.0.0"
default-activation="eager"
xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
<!-- This gets the container-managed EntityManager and injects it into the DataPointServiceImpl bean.
Assumes DataPointServiceImpl has an "entityManager" field with a getter and setter. -->
<bean id="dpService" class="org.hibernate.osgitest.DataPointServiceImpl">
<jpa:context unitname="managed-jpa" property="entityManager"/>
<tx:transaction method="*" value="Required"/>
</bean>
<service ref="dpService" interface="org.hibernate.osgitest.DataPointService"/>
</blueprint>
20.11. Unmanaged JPA
Hibernate also supports the use of JPA, unmanaged by the OSGi container.
The client bundle is responsible for managing the EntityManagerFactory
and `EntityManager`s.
20.12. persistence.xml
Similar to any other JPA setup, your bundle must include a persistence.xml
file. This is typically located in META-INF
.
20.13. Bundle Package Imports
Your bundle’s manifest will need to import, at a minimum:
-
javax.persistence
-
org.hibernate.proxy
andjavassist.util.proxy
, due to Hibernate’s ability to return proxies for lazy initialization (Javassist enhancement occurs on the entity’sClassLoader
during runtime) -
JDBC driver package (example:
org.h2
) -
org.osgi.framework
, necessary to discover theEntityManagerFactory
(described below)
20.14. Obtaining an EntityMangerFactory
hibernate-osgi
registers an OSGi service, using the JPA PersistenceProvider
interface name, that bootstraps and creates an EntityManagerFactory
specific for OSGi environments.
It is VITAL that your |
EntityManagerFactory
public class HibernateUtil {
private EntityManagerFactory emf;
public EntityManager getEntityManager() {
return getEntityManagerFactory().createEntityManager();
}
private EntityManagerFactory getEntityManagerFactory() {
if ( emf == null ) {
Bundle thisBundle = FrameworkUtil.getBundle(
HibernateUtil.class
);
BundleContext context = thisBundle.getBundleContext();
ServiceReference serviceReference = context.getServiceReference(
PersistenceProvider.class.getName()
);
PersistenceProvider persistenceProvider = ( PersistenceProvider ) context
.getService(
serviceReference
);
emf = persistenceProvider.createEntityManagerFactory(
"YourPersistenceUnitName",
null
);
}
return emf;
}
}
20.15. Unmanaged Native
Native Hibernate use is also supported. The client bundle is responsible for managing the SessionFactory
and Session
s.
20.16. Bundle Package Imports
Your bundle’s manifest will need to import, at a minimum:
-
javax.persistence
-
org.hibernate.proxy
andjavassist.util.proxy
, due to Hibernate’s ability to return proxies for lazy initialization (Javassist enhancement occurs on the entity’sClassLoader
during runtime) -
JDBC driver package (example:
org.h2
) -
org.osgi.framework
, necessary to discover theSessionFactory
(described below) -
org.hibernate.*
packages, as necessary (ex: cfg, criterion, service, etc.)
20.17. Obtaining a SessionFactory
hibernate-osgi
registers an OSGi service, using the SessionFactory
interface name, that bootstraps and creates a SessionFactory
specific for OSGi environments.
It is VITAL that your |
SessionFactory
public class HibernateUtil {
private SessionFactory sf;
public Session getSession() {
return getSessionFactory().openSession();
}
private SessionFactory getSessionFactory() {
if ( sf == null ) {
Bundle thisBundle = FrameworkUtil.getBundle(
HibernateUtil.class
);
BundleContext context = thisBundle.getBundleContext();
ServiceReference sr = context.getServiceReference(
SessionFactory.class.getName()
);
sf = ( SessionFactory ) context.getService( sr );
}
return sf;
}
}
20.18. Optional Modules
The unmanaged-native demo project displays the use of optional Hibernate modules.
Each module adds additional dependency bundles that must first be activated, either manually or through an additional feature.
As of ORM 4.2, Envers is fully supported.
Support for C3P0, Proxool, EhCache, and Infinispan were added in 4.3. However, none of their 3rd party libraries currently work in OSGi (lots of ClassLoader
problems, etc.).
We’re tracking the issues in JIRA.
20.19. Extension Points
Multiple contracts exist to allow applications to integrate with and extend Hibernate capabilities.
Most apps utilize JDK services to provide their implementations.
hibernate-osgi
supports the same extensions through OSGi services.
Implement and register them in any of the three configurations.
hibernate-osgi
will discover and integrate them during EntityManagerFactory
/SessionFactory
bootstrapping. Supported extension points are as follows.
The specified interface should be used during service registration.
org.hibernate.integrator.spi.Integrator
-
(as of 4.2)
org.hibernate.boot.registry.selector.StrategyRegistrationProvider
-
(as of 4.3)
org.hibernate.boot.model.TypeContributor
-
(as of 4.3)
- JTA’s
-
javax.transaction.TransactionManager
andjavax.transaction.UserTransaction
(as of 4.2). However, these are typically provided by the OSGi container.
The easiest way to register extension point implementations is through a blueprint.xml
file.
Add OSGI-INF/blueprint/blueprint.xml
to your classpath. Envers' blueprint is a great example:
<!--
~ 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>.
-->
<blueprint default-activation="eager"
xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
<bean id="integrator" class="org.hibernate.envers.boot.internal.EnversIntegrator"/>
<service ref="integrator" interface="org.hibernate.integrator.spi.Integrator"/>
<bean id="typeContributor"
class="org.hibernate.envers.boot.internal.TypeContributorImpl"/>
<service ref="typeContributor" interface="org.hibernate.boot.model.TypeContributor"/>
</blueprint>
Extension points can also be registered programmatically with BundleContext#registerService
, typically within your BundleActivator#start
.
20.20. Caveats
-
Technically, multiple persistence units are supported by Enterprise OSGi JPA and unmanaged Hibernate JPA use. However, we cannot currently support this in OSGi. In Hibernate 4, only one instance of the OSGi-specific
ClassLoader
is used per Hibernate bundle, mainly due to heavy use of static TCCL utilities. We hope to support one OSGiClassLoader
per persistence unit in Hibernate 5. -
Scanning is supported to find non-explicitly listed entities and mappings. However, they MUST be in the same bundle as your persistence unit (fairly typical anyway). Our OSGi
ClassLoader
only considers the "requesting bundle" (hence the requirement on using services to createEntityManagerFactory
/SessionFactory
), rather than attempting to scan all available bundles. This is primarily for versioning considerations, collision protection, etc. -
Some containers (ex: Aries) always return true for
PersistenceUnitInfo#excludeUnlistedClasses
, even if yourpersistence.xml
explicitly hasexclude-unlisted-classes
set tofalse
. They claim it’s to protect JPA providers from having to implement scanning ("we handle it for you"), even though we still want to support it in many cases. The workaround is to sethibernate.archive.autodetection
to, for example,hbm,class
. This tells hibernate to ignore theexcludeUnlistedClasses
value and scan for*.hbm.xml
and entities regardless. -
Scanning does not currently support annotated packages on
package-info.java
. -
Currently, Hibernate OSGi is primarily tested using Apache Karaf and Apache Aries JPA. Additional testing is needed with Equinox, Gemini, and other container providers.
-
Hibernate ORM has many dependencies that do not currently provide OSGi manifests. The QuickStart tutorials make heavy use of 3rd party bundles (SpringSource, ServiceMix) or the
wrap:…
operator.
21. Envers
21.1. Basics
To audit changes that are performed on an entity, you only need two things:
-
the
hibernate-envers
jar on the classpath, -
an
@Audited
annotation on the entity.
Unlike in previous versions, you no longer need to specify listeners in the Hibernate configuration file. Just putting the Envers jar on the classpath is enough because listeners will be registered automatically. |
And that’s all. You can create, modify and delete the entities as always.
The use of JPA’s |
If you look at the generated schema for your entities, or at the data persisted by Hibernate, you will notice that there are no changes.
However, for each audited entity, a new table is introduced - entity_table_AUD
, which stores the historical data, whenever you commit a transaction.
Envers automatically creates audit tables if |
Considering we have a Customer
entity, when annotating it with the Audited
annotation,
Hibernate is going to generate the following tables using the hibernate.hbm2ddl.auto
schema tool:
@Audited
@Entity(name = "Customer")
public static class Customer {
@Id
private Long id;
private String firstName;
private String lastName;
@Temporal( TemporalType.TIMESTAMP )
@Column(name = "created_on")
@CreationTimestamp
private Date createdOn;
//Getters and setters are omitted for brevity
}
create table Customer (
id bigint not null,
created_on timestamp,
firstName varchar(255),
lastName varchar(255),
primary key (id)
)
create table Customer_AUD (
id bigint not null,
REV integer not null,
REVTYPE tinyint,
created_on timestamp,
firstName varchar(255),
lastName varchar(255),
primary key (id, REV)
)
create table REVINFO (
REV integer generated by default as identity,
REVTSTMP bigint,
primary key (REV)
)
alter table Customer_AUD
add constraint FK5ecvi1a0ykunrriib7j28vpdj
foreign key (REV)
references REVINFO
Instead of annotating the whole class and auditing all properties, you can annotate only some persistent properties with @Audited
.
This will cause only these properties to be audited.
Now, considering the previous Customer
entity,
let’s see how Envers auditing works when inserting, updating, and deleting the entity in question.
INSERT
operationCustomer customer = new Customer();
customer.setId( 1L );
customer.setFirstName( "John" );
customer.setLastName( "Doe" );
entityManager.persist( customer );
insert
into
Customer
(created_on, firstName, lastName, id)
values
(?, ?, ?, ?)
-- binding parameter [1] as [TIMESTAMP] - [Mon Jul 24 17:21:32 EEST 2017]
-- binding parameter [2] as [VARCHAR] - [John]
-- binding parameter [3] as [VARCHAR] - [Doe]
-- binding parameter [4] as [BIGINT] - [1]
insert
into
REVINFO
(REV, REVTSTMP)
values
(?, ?)
-- binding parameter [1] as [BIGINT] - [1]
-- binding parameter [2] as [BIGINT] - [1500906092803]
insert
into
Customer_AUD
(REVTYPE, created_on, firstName, lastName, id, REV)
values
(?, ?, ?, ?, ?, ?)
-- binding parameter [1] as [INTEGER] - [0]
-- binding parameter [2] as [TIMESTAMP] - [Mon Jul 24 17:21:32 EEST 2017]
-- binding parameter [3] as [VARCHAR] - [John]
-- binding parameter [4] as [VARCHAR] - [Doe]
-- binding parameter [5] as [BIGINT] - [1]
-- binding parameter [6] as [INTEGER] - [1]
UPDATE
operationCustomer customer = entityManager.find( Customer.class, 1L );
customer.setLastName( "Doe Jr." );
update
Customer
set
created_on=?,
firstName=?,
lastName=?
where
id=?
-- binding parameter [1] as [TIMESTAMP] - [2017-07-24 17:21:32.757]
-- binding parameter [2] as [VARCHAR] - [John]
-- binding parameter [3] as [VARCHAR] - [Doe Jr.]
-- binding parameter [4] as [BIGINT] - [1]
insert
into
REVINFO
(REV, REVTSTMP)
values
(?, ?)
-- binding parameter [1] as [BIGINT] - [2]
-- binding parameter [2] as [BIGINT] - [1500906092853]
insert
into
Customer_AUD
(REVTYPE, created_on, firstName, lastName, id, REV)
values
(?, ?, ?, ?, ?, ?)
-- binding parameter [1] as [INTEGER] - [1]
-- binding parameter [2] as [TIMESTAMP] - [2017-07-24 17:21:32.757]
-- binding parameter [3] as [VARCHAR] - [John]
-- binding parameter [4] as [VARCHAR] - [Doe Jr.]
-- binding parameter [5] as [BIGINT] - [1]
-- binding parameter [6] as [INTEGER] - [2]
DELETE
operationCustomer customer = entityManager.getReference( Customer.class, 1L );
entityManager.remove( customer );
delete
from
Customer
where
id = ?
-- binding parameter [1] as [BIGINT] - [1]
insert
into
REVINFO
(REV, REVTSTMP)
values
(?, ?)
-- binding parameter [1] as [BIGINT] - [3]
-- binding parameter [2] as [BIGINT] - [1500906092876]
insert
into
Customer_AUD
(REVTYPE, created_on, firstName, lastName, id, REV)
values
(?, ?, ?, ?, ?, ?)
-- binding parameter [1] as [INTEGER] - [2]
-- binding parameter [2] as [TIMESTAMP] - [null]
-- binding parameter [3] as [VARCHAR] - [null]
-- binding parameter [4] as [VARCHAR] - [null]
-- binding parameter [5] as [BIGINT] - [1]
-- binding parameter [6] as [INTEGER] - [3]
The REVTYPE
column value is taken from the RevisionType
Enum.
Database column value |
Associated |
Description |
0 |
|
A database table row was inserted. |
1 |
|
A database table row was updated. |
2 |
|
A database table row was deleted. |
The audit (history) of an entity can be accessed using the AuditReader
interface, which can be obtained by having an open EntityManager
or Session
via the AuditReaderFactory
.
Customer
entityList<Number> revisions = doInJPA( this::entityManagerFactory, entityManager -> {
return AuditReaderFactory.get( entityManager ).getRevisions(
Customer.class,
1L
);
} );
select
c.REV as col_0_0_
from
Customer_AUD c
cross join
REVINFO r
where
c.id = ?
and c.REV = r.REV
order by
c.REV asc
-- binding parameter [1] as [BIGINT] - [1]
Using the previously fetched revisions, we can now inspect the state of the Customer
entity at that particular revision:
Customer
entityCustomer customer = (Customer) AuditReaderFactory
.get( entityManager )
.createQuery()
.forEntitiesAtRevision( Customer.class, revisions.get( 0 ) )
.getSingleResult();
assertEquals("Doe", customer.getLastName());
select
c.id as id1_1_,
c.REV as REV2_1_,
c.REVTYPE as REVTYPE3_1_,
c.created_on as created_4_1_,
c.firstName as firstNam5_1_,
c.lastName as lastName6_1_
from
Customer_AUD c
where
c.REV = (
select
max( c_max.REV )
from
Customer_AUD c_max
where
c_max.REV <= ?
and c.id = c_max.id
)
and c.REVTYPE <> ?
-- binding parameter [1] as [INTEGER] - [1]
-- binding parameter [2] as [INTEGER] - [2]
When executing the aforementioned SQL query, there are two parameters:
- revision_number
-
The first parameter marks the revision number we are interested in or the latest one that exists up to this particular revision.
- revision_type
-
The second parameter specifies that we are not interested in
DEL
RevisionType
so that deleted entries are filtered out.
The same goes for the second revision associated with the UPDATE
statement.
Customer
entityCustomer customer = (Customer) AuditReaderFactory
.get( entityManager )
.createQuery()
.forEntitiesAtRevision( Customer.class, revisions.get( 1 ) )
.getSingleResult();
assertEquals("Doe Jr.", customer.getLastName());
For the deleted entity revision, Envers throws a NoResultException
since the entity was no longer valid at that revision.
Customer
entitytry {
Customer customer = (Customer) AuditReaderFactory
.get( entityManager )
.createQuery()
.forEntitiesAtRevision( Customer.class, revisions.get( 2 ) )
.getSingleResult();
fail("The Customer was deleted at this revision: " + revisions.get( 2 ));
}
catch (NoResultException expected) {
}
You can use the
forEntitiesAtRevision(Class<T> cls, String entityName, Number revision, boolean includeDeletions)
method to get the deleted entity revision so that, instead of a NoResultException
,
all attributes, except for the entity identifier, are going to be null
.
Customer
entity without getting a NoResultException
Customer customer = (Customer) AuditReaderFactory
.get( entityManager )
.createQuery()
.forEntitiesAtRevision(
Customer.class,
Customer.class.getName(),
revisions.get( 2 ),
true )
.getSingleResult();
assertEquals( Long.valueOf( 1L ), customer.getId() );
assertNull( customer.getFirstName() );
assertNull( customer.getLastName() );
assertNull( customer.getCreatedOn() );
See the Javadocs for details on other functionality offered.
21.2. Configuration Properties
It is possible to configure various aspects of Hibernate Envers behavior, such as table names, etc.
org.hibernate.envers.audit_table_prefix
-
String that will be prepended to the name of an audited entity to create the name of the entity and that will hold audit information.
org.hibernate.envers.audit_table_suffix
(default:_AUD
)-
String that will be appended to the name of an audited entity to create the name of the entity and that will hold audit information.
If you audit an entity with a table name Person, in the default setting Envers will generate a
Person_AUD
table to store historical data. org.hibernate.envers.revision_field_name
(default:REV
)-
Name of a field in the audit entity that will hold the revision number.
org.hibernate.envers.revision_type_field_name
(default:REVTYPE
)-
Name of a field in the audit entity that will hold the type of the revision (currently, this can be:
add
,mod
,del
). org.hibernate.envers.revision_on_collection_change
(default:true
)-
Should a revision be generated when a not-owned relation field changes (this can be either a collection in a one-to-many relation or the field using
mappedBy
attribute in a one-to-one relation). org.hibernate.envers.do_not_audit_optimistic_locking_field
(default:true
)-
When true, properties to be used for optimistic locking, annotated with
@Version
, will not be automatically audited (their history won’t be stored; it normally doesn’t make sense to store it). org.hibernate.envers.store_data_at_delete
(default:false
)-
Should the entity data be stored in the revision when the entity is deleted (instead of only storing the id and all other properties as null).
This is not normally needed, as the data is present in the last-but-one revision. Sometimes, however, it is easier and more efficient to access it in the last revision (then the data that the entity contained before deletion is stored twice).
org.hibernate.envers.default_schema
(default:null
- same schema as the table being audited)-
The default schema name that should be used for audit tables.
Can be overridden using the
@AuditTable( schema = "…" )
annotation.If not present, the schema will be the same as the schema of the table being audited.
org.hibernate.envers.default_catalog
(default:null
- same catalog as the table being audited)-
The default catalog name that should be used for audit tables.
Can be overridden using the
@AuditTable( catalog = "…" )
annotation.If not present, the catalog will be the same as the catalog of the normal tables.
org.hibernate.envers.audit_strategy
(default:org.hibernate.envers.strategy.DefaultAuditStrategy
)-
The audit strategy that should be used when persisting audit data. The default stores only the revision, at which an entity was modified.
An alternative, the
org.hibernate.envers.strategy.ValidityAuditStrategy
stores both the start revision and the end revision. Together these define when an audit row was valid, hence the name ValidityAuditStrategy. org.hibernate.envers.audit_strategy_validity_end_rev_field_name
(default:REVEND
)-
The column name that will hold the end revision number in audit entities. This property is only valid if the validity audit strategy is used.
org.hibernate.envers.audit_strategy_validity_store_revend_timestamp
(default:false
)-
Should the timestamp of the end revision be stored, until which the data was valid, in addition to the end revision itself. This is useful to be able to purge old Audit records out of a relational database by using table partitioning.
Partitioning requires a column that exists within the table. This property is only evaluated if the
ValidityAuditStrategy
is used. org.hibernate.envers.audit_strategy_validity_revend_timestamp_field_name
(default:REVEND_TSTMP
)-
Column name of the timestamp of the end revision until which the data was valid. Only used if the
ValidityAuditStrategy
is used, andorg.hibernate.envers.audit_strategy_validity_store_revend_timestamp
evaluates to true. org.hibernate.envers.use_revision_entity_with_native_id
(default:true
)-
Boolean flag that determines the strategy of revision number generation. Default implementation of revision entity uses native identifier generator.
If the current database engine does not support identity columns, users are advised to set this property to false.
In this case revision numbers are created by preconfigured
org.hibernate.id.enhanced.SequenceStyleGenerator
. See:org.hibernate.envers.DefaultRevisionEntity
andorg.hibernate.envers.enhanced.SequenceIdRevisionEntity
. org.hibernate.envers.track_entities_changed_in_revision
(default:false
)-
Should entity types, that have been modified during each revision, be tracked. The default implementation creates
REVCHANGES
table that stores entity names of modified persistent objects. Single record encapsulates the revision identifier (foreign key toREVINFO
table) and a string value. For more information, refer to Tracking entity names modified during revisions and Querying for entity types modified in a given revision. org.hibernate.envers.global_with_modified_flag
(default:false
, can be individually overridden with@Audited( withModifiedFlag = true )
)-
Should property modification flags be stored for all audited entities and all properties.
When set to true, for all properties an additional boolean column in the audit tables will be created, filled with information if the given property changed in the given revision.
When set to false, such column can be added to selected entities or properties using the
@Audited
annotation.For more information, refer to Tracking entity changes at the property level and Querying for entity revisions that modified a given property.
org.hibernate.envers.modified_flag_suffix
(default:_MOD
)-
The suffix for columns storing "Modified Flags".
For example, a property called "age", will by default get modified flag with column name "age_MOD".
org.hibernate.envers.modified_column_naming_strategy
(default:org.hibernate.envers.boot.internal.LegacyModifiedColumnNamingStrategy
)-
The naming strategy to be used for modified flag columns in the audit metadata.
org.hibernate.envers.embeddable_set_ordinal_field_name
(default:SETORDINAL
)-
Name of column used for storing ordinal of the change in sets of embeddable elements.
org.hibernate.envers.cascade_delete_revision
(default:false
)-
While deleting revision entry, remove data of associated audited entities. Requires database support for cascade row removal.
org.hibernate.envers.allow_identifier_reuse
(default:false
)-
Guarantees proper validity audit strategy behavior when application reuses identifiers of deleted entities. Exactly one row with
null
end date exists for each identifier. org.hibernate.envers.original_id_prop_name
(default:originalId
)-
Specifies the composite-id key property name used by the audit table mappings.
org.hibernate.envers.find_by_revision_exact_match
(default:false
)-
Specifies whether or not
AuditReader#find
methods which accept a revision-number argument are to find results based on fuzzy-match or exact-match behavior.The old (legacy) behavior has always been to perform a fuzzy-match where these methods would return a match if any revision existed for the primary-key with a revision-number less-than or equal-to the revision method argument. This behavior is great when you want to find the snapshot of a non-related entity based on another entity’s revision number.
The new (optional) behavior when this option is enabled forces the query to perform an exact-match instead. In order for these methods to return a non-
null
value, a revision entry must exist for the entity with the specified primary key and revision number; otherwise the result will benull
.
The following configuration options have been added recently and should be regarded as experimental:
|
21.3. Additional mapping annotations
The name of the audit table can be set on a per-entity basis, using the @AuditTable
annotation.
It may be tedious to add this annotation to every audited entity, so if possible, it’s better to use a prefix/suffix.
If you have a mapping with secondary tables, audit tables for them will be generated in the same way (by adding the prefix and suffix).
If you wish to overwrite this behavior, you can use the @SecondaryAuditTable
and @SecondaryAuditTables
annotations.
If you’d like to override auditing behavior of some fields/properties inherited from @MappedSuperclass
or in an embedded component,
you can apply the @AuditOverride
annotation on the subtype or usage site of the component.
If you want to audit a relation mapped with @OneToMany
and @JoinColumn
,
please see Mapping exceptions for a description of the additional @AuditJoinTable
annotation that you’ll probably want to use.
If you want to audit a relation, where the target entity is not audited (that is the case for example with dictionary-like entities, which don’t change and don’t have to be audited),
just annotate it with @Audited( targetAuditMode = RelationTargetAuditMode.NOT_AUDITED )
.
Then, while reading historic versions of your entity, the relation will always point to the "current" related entity.
By default Envers throws javax.persistence.EntityNotFoundException
when "current" entity does not exist in the database.
Apply @NotFound( action = NotFoundAction.IGNORE )
annotation to silence the exception and assign null value instead.
The hereby solution causes implicit eager loading of to-one relations.
If you’d like to audit properties of a superclass of an entity, which are not explicitly audited (they don’t have the @Audited
annotation on any properties or on the class),
you can set the @AuditOverride( forClass = SomeEntity.class, isAudited = true/false )
annotation.
The |
21.4. Choosing an audit strategy
After the basic configuration, it is important to choose the audit strategy that will be used to persist and retrieve audit information. There is a trade-off between the performance of persisting and the performance of querying the audit information. Currently, there are two audit strategies.
-
The default audit strategy persists the audit data together with a start revision. For each row inserted, updated or deleted in an audited table, one or more rows are inserted in the audit tables, together with the start revision of its validity. Rows in the audit tables are never updated after insertion. Queries of audit information use subqueries to select the applicable rows in the audit tables.
These subqueries are notoriously slow and difficult to index. -
The alternative is a validity audit strategy. This strategy stores the start-revision and the end-revision of audit information. For each row inserted, updated or deleted in an audited table, one or more rows are inserted in the audit tables, together with the start revision of its validity. But at the same time, the end-revision field of the previous audit rows (if available) is set to this revision. Queries on the audit information can then use 'between start and end revision' instead of subqueries as used by the default audit strategy.
The consequence of this strategy is that persisting audit information will be a bit slower because of the extra updates involved, but retrieving audit information will be a lot faster.
This can be improved even further by adding extra indexes.
21.4.1. Configuring the ValidityAuditStrategy
To better visualize how the ValidityAuditStrategy
works, consider the following exercise where
we replay the previous audit logging example for the Customer
entity.
First, you need to configure the ValidityAuditStrategy
:
ValidityAuditStrategy
options.put(
EnversSettings.AUDIT_STRATEGY,
ValidityAuditStrategy.class.getName()
);
If, you’re using the persistence.xml
configuration file,
then the mapping will look as follows:
<property
name="org.hibernate.envers.audit_strategy"
value="org.hibernate.envers.strategy.ValidityAuditStrategy"
/>
Once you configured the ValidityAuditStrategy
, the following schema is going to be automatically generated:
ValidityAuditStrategy
create table Customer (
id bigint not null,
created_on timestamp,
firstName varchar(255),
lastName varchar(255),
primary key (id)
)
create table Customer_AUD (
id bigint not null,
REV integer not null,
REVTYPE tinyint,
REVEND integer,
created_on timestamp,
firstName varchar(255),
lastName varchar(255),
primary key (id, REV)
)
create table REVINFO (
REV integer generated by default as identity,
REVTSTMP bigint,
primary key (REV)
)
alter table Customer_AUD
add constraint FK5ecvi1a0ykunrriib7j28vpdj
foreign key (REV)
references REVINFO
alter table Customer_AUD
add constraint FKqd4fy7ww1yy95wi4wtaonre3f
foreign key (REVEND)
references REVINFO
As you can see, the REVEND
column is added as well as its foreign key to the REVINFO
table.
When rerunning the previous Customer
audit log queries against the ValidityAuditStrategy
,
we get the following results:
Customer
entityselect
c.id as id1_1_,
c.REV as REV2_1_,
c.REVTYPE as REVTYPE3_1_,
c.REVEND as REVEND4_1_,
c.created_on as created_5_1_,
c.firstName as firstNam6_1_,
c.lastName as lastName7_1_
from
Customer_AUD c
where
c.REV <= ?
and c.REVTYPE <> ?
and (
c.REVEND > ?
or c.REVEND is null
)
-- binding parameter [1] as [INTEGER] - [1]
-- binding parameter [2] as [INTEGER] - [2]
-- binding parameter [3] as [INTEGER] - [1]
Compared to the default strategy, the |
21.5. Revision Log
When Envers starts a new revision, it creates a new revision entity which stores information about the revision.
By default, that includes just:
- revision number
-
An integral value (
int/Integer
orlong/Long
). Essentially, the primary key of the revision.A revision number value should always be increasing and never overflows.
The default implementations provided by Envers use an
int
data type which has an upper bounds ofInteger.MAX_VALUE
. It is critical that users consider whether this upper bounds is feasible for your application. If a large range is needed, consider using a custom revision entity mapping using along
data type.In the event that the revision number reaches its upper bounds wrapping around becoming negative, an
AuditException
will be thrown causing the current transaction to be rolled back. This guarantees that the audit history remains in a valid state that can be queried by the Envers Query API. - revision timestamp
-
Either a
long/Long
orjava.util.Date
value representing the instant at which the revision was made. When using ajava.util.Date
, instead of along/Long
for the revision timestamp, take care not to store it to a column data type which will lose precision.
Envers handles this information as an entity.
By default it uses its own internal class to act as the entity, mapped to the REVINFO
table.
You can, however, supply your own approach to collecting this information which might be useful to capture additional details such as who made a change
or the IP address from which the request came.
There are two things you need to make this work:
-
First, you will need to tell Envers about the entity you wish to use. Your entity must use the
@org.hibernate.envers.RevisionEntity
annotation. It must define the two attributes described above annotated with@org.hibernate.envers.RevisionNumber
and@org.hibernate.envers.RevisionTimestamp
, respectively. You can extend fromorg.hibernate.envers.DefaultRevisionEntity
, if you wish, to inherit all these required behaviors.Simply add the custom revision entity as you do your normal entities and Envers will find it.
It is an error for there to be multiple entities marked as @org.hibernate.envers.RevisionEntity
. -
Second, you need to tell Envers how to create instances of your revision entity which is handled by the
newRevision( Object revisionEntity )
method of theorg.hibernate.envers.RevisionListener
interface.You tell Envers your custom
org.hibernate.envers.RevisionListener
implementation to use by specifying it on the@org.hibernate.envers.RevisionEntity
annotation, using the value attribute. If yourRevisionListener
class is inaccessible from@RevisionEntity
(e.g. it exists in a different module), setorg.hibernate.envers.revision_listener
property to its fully qualified class name. Class name defined by the configuration parameter overrides the revision entity’s value attribute.
Considering we have a CurrentUser
utility which stores the currently logged user:
CurrentUser
utilitypublic 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();
}
}
Now, we need to provide a custom @RevisionEntity
to store the currently logged user
@RevisionEntity
example@Entity(name = "CustomRevisionEntity")
@Table(name = "CUSTOM_REV_INFO")
@RevisionEntity( CustomRevisionEntityListener.class )
public static class CustomRevisionEntity extends DefaultRevisionEntity {
private String username;
public String getUsername() {
return username;
}
public void setUsername( String username ) {
this.username = username;
}
}
With the custom RevisionEntity
implementation in place,
we only need to provide the RevisionEntity
implementation which acts as a factory
of RevisionEntity
instances.
@RevisionListener
examplepublic static class CustomRevisionEntityListener implements RevisionListener {
public void newRevision( Object revisionEntity ) {
CustomRevisionEntity customRevisionEntity =
( CustomRevisionEntity ) revisionEntity;
customRevisionEntity.setUsername(
CurrentUser.INSTANCE.get()
);
}
}
When generating the database schema, Envers creates the following RevisionEntity
table:
RevisionEntity
Envers tablecreate table CUSTOM_REV_INFO (
id integer not null,
timestamp bigint not null,
username varchar(255),
primary key (id)
)
You can see the username
column in place.
Now, when inserting a Customer
entity, Envers generates the following statements:
@RevisionEntity
instanceCurrentUser.INSTANCE.logIn( "Vlad Mihalcea" );
doInJPA( this::entityManagerFactory, entityManager -> {
Customer customer = new Customer();
customer.setId( 1L );
customer.setFirstName( "John" );
customer.setLastName( "Doe" );
entityManager.persist( customer );
} );
CurrentUser.INSTANCE.logOut();
insert
into
Customer
(created_on, firstName, lastName, id)
values
(?, ?, ?, ?)
-- binding parameter [1] as [TIMESTAMP] - [Thu Jul 27 15:45:00 EEST 2017]
-- binding parameter [2] as [VARCHAR] - [John]
-- binding parameter [3] as [VARCHAR] - [Doe]
-- binding parameter [4] as [BIGINT] - [1]
insert
into
CUSTOM_REV_INFO
(timestamp, username, id)
values
(?, ?, ?)
-- binding parameter [1] as [BIGINT] - [1501159500888]
-- binding parameter [2] as [VARCHAR] - [Vlad Mihalcea]
-- binding parameter [3] as [INTEGER] - [1]
insert
into
Customer_AUD
(REVTYPE, created_on, firstName, lastName, id, REV)
values
(?, ?, ?, ?, ?, ?)
-- binding parameter [1] as [INTEGER] - [0]
-- binding parameter [2] as [TIMESTAMP] - [Thu Jul 27 15:45:00 EEST 2017]
-- binding parameter [3] as [VARCHAR] - [John]
-- binding parameter [4] as [VARCHAR] - [Doe]
-- binding parameter [5] as [BIGINT] - [1]
-- binding parameter [6] as [INTEGER] - [1]
As demonstrated by the example above, the username is properly set and propagated to the CUSTOM_REV_INFO
table.
This strategy is deprecated since version 5.2. The alternative is to use dependency injection offered as of version 5.3. An alternative method to using the The method accepts a
|
As of Hibernate Envers 5.3, dependency injection is now supported for a This feature is up to the various dependency frameworks, such as CDI and Spring, to supply the
necessary implementation during Hibernate ORM bootstrap to support injection. If no qualifying
implementation is supplied, the |
21.6. Tracking entity names modified during revisions
By default, entity types that have been changed in each revision are not being tracked.
This implies the necessity to query all tables storing audited data in order to retrieve changes made during the specified revision.
Envers provides a simple mechanism that creates REVCHANGES
table which stores entity names of modified persistent objects.
Single record encapsulates the revision identifier (foreign key to REVINFO
table) and a string value.
Tracking of modified entity names can be enabled in three different ways:
-
Set
org.hibernate.envers.track_entities_changed_in_revision
parameter totrue
. In this caseorg.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity
will be implicitly used as the revision log entity. -
Create a custom revision entity that extends
org.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity
class.@Entity(name = "CustomTrackingRevisionEntity") @Table(name = "TRACKING_REV_INFO") @RevisionEntity public static class CustomTrackingRevisionEntity extends DefaultTrackingModifiedEntitiesRevisionEntity { }
-
Mark an appropriate field of a custom revision entity with
@org.hibernate.envers.ModifiedEntityNames
annotation. The property is required to be ofSet<String>
type.@Entity(name = "CustomTrackingRevisionEntity") @Table(name = "TRACKING_REV_INFO") @RevisionEntity public static class CustomTrackingRevisionEntity extends DefaultRevisionEntity { @ElementCollection @JoinTable( name = "REVCHANGES", joinColumns = @JoinColumn( name = "REV" ) ) @Column( name = "ENTITYNAME" ) @ModifiedEntityNames private Set<String> modifiedEntityNames = new HashSet<>(); public Set<String> getModifiedEntityNames() { return modifiedEntityNames; } }
Considering we have a Customer
entity illustrated by the following example:
Customer
entity before renaming@Audited
@Entity(name = "Customer")
public static class Customer {
@Id
private Long id;
private String firstName;
private String lastName;
@Temporal( TemporalType.TIMESTAMP )
@Column(name = "created_on")
@CreationTimestamp
private Date createdOn;
//Getters and setters are omitted for brevity
}
If the Customer
entity class name is changed to ApplicationCustomer
,
Envers is going to insert a new record in the REVCHANGES
table with the previous entity class name:
Customer
entity after renaming@Audited
@Entity(name = "Customer")
public static class ApplicationCustomer {
@Id
private Long id;
private String firstName;
private String lastName;
@Temporal( TemporalType.TIMESTAMP )
@Column(name = "created_on")
@CreationTimestamp
private Date createdOn;
//Getters and setters are omitted for brevity
}
insert
into
REVCHANGES
(REV, ENTITYNAME)
values
(?, ?)
-- binding parameter [1] as [INTEGER] - [1]
-- binding parameter [2] as [VARCHAR] - [org.hibernate.userguide.envers.EntityTypeChangeAuditTest$Customer]
Users, that have chosen one of the approaches listed above, can retrieve all entities modified in a specified revision by utilizing API described in Querying for entity types modified in a given revision.
Users are also allowed to implement custom mechanisms of tracking modified entity types.
In this case, they shall pass their own implementation of org.hibernate.envers.EntityTrackingRevisionListener
interface as the value of @org.hibernate.envers.RevisionEntity
annotation.
EntityTrackingRevisionListener
interface exposes one method that notifies whenever audited entity instance has been
added, modified or removed within current revision boundaries.
EntityTrackingRevisionListener
implementationpublic static class CustomTrackingRevisionListener implements EntityTrackingRevisionListener {
@Override
public void entityChanged(Class entityClass,
String entityName,
Serializable entityId,
RevisionType revisionType,
Object revisionEntity ) {
String type = entityClass.getName();
( (CustomTrackingRevisionEntity) revisionEntity ).addModifiedEntityType( type );
}
@Override
public void newRevision( Object revisionEntity ) {
}
}
The CustomTrackingRevisionListener
adds the fully-qualified class name to the modifiedEntityTypes
attribute of the CustomTrackingRevisionEntity
.
RevisionEntity
using the custom EntityTrackingRevisionListener
@Entity(name = "CustomTrackingRevisionEntity")
@Table(name = "TRACKING_REV_INFO")
@RevisionEntity( CustomTrackingRevisionListener.class )
public static class CustomTrackingRevisionEntity {
@Id
@GeneratedValue
@RevisionNumber
private int customId;
@RevisionTimestamp
private long customTimestamp;
@OneToMany(
mappedBy="revision",
cascade={
CascadeType.PERSIST,
CascadeType.REMOVE
}
)
private Set<EntityType> modifiedEntityTypes = new HashSet<>();
public Set<EntityType> getModifiedEntityTypes() {
return modifiedEntityTypes;
}
public void addModifiedEntityType(String entityClassName ) {
modifiedEntityTypes.add( new EntityType( this, entityClassName ) );
}
}
The CustomTrackingRevisionEntity
contains a @OneToMany
list of ModifiedTypeRevisionEntity
EntityType
encapsulates the entity type name before a class name modification@Entity(name = "EntityType")
public static class EntityType {
@Id
@GeneratedValue
private Integer id;
@ManyToOne
private CustomTrackingRevisionEntity revision;
private String entityClassName;
private EntityType() {
}
public EntityType(CustomTrackingRevisionEntity revision, String entityClassName) {
this.revision = revision;
this.entityClassName = entityClassName;
}
//Getters and setters are omitted for brevity
}
Now, when fetching the CustomTrackingRevisionEntity
, you can get access to the previous entity class name.
EntityType
through the CustomTrackingRevisionEntity
AuditReader auditReader = AuditReaderFactory.get( entityManager );
List<Number> revisions = auditReader.getRevisions(
ApplicationCustomer.class,
1L
);
CustomTrackingRevisionEntity revEntity = auditReader.findRevision(
CustomTrackingRevisionEntity.class,
revisions.get( 0 )
);
Set<EntityType> modifiedEntityTypes = revEntity.getModifiedEntityTypes();
assertEquals( 1, modifiedEntityTypes.size() );
EntityType entityType = modifiedEntityTypes.iterator().next();
assertEquals(
Customer.class.getName(),
entityType.getEntityClassName()
);
21.7. Tracking entity changes at the property level
By default, the only information stored by Envers are revisions of modified entities. This approach lets users create audit queries based on historical values of entity properties. Sometimes it is useful to store additional metadata for each revision, when you are interested also in the type of changes, not only about the resulting values.
The feature described in Tracking entity names modified during revisions makes it possible to tell which entities were modified in a given revision.
The feature described here takes it one step further. Modification Flags enable Envers to track which properties of audited entities were modified in a given revision.
Tracking entity changes at the property level can be enabled by:
-
setting
org.hibernate.envers.global_with_modified_flag
configuration property totrue
. This global switch will cause adding modification flags to be stored for all audited properties of all audited entities. -
using
@Audited( withModifiedFlag = true )
on a property or on an entity.
The trade-off coming with this functionality is an increased size of audit tables and a very little, almost negligible, performance drop during audit writes. This is due to the fact that every tracked property has to have an accompanying boolean column in the schema that stores information about the property modifications. Of course, it is Enver’s job to fill these columns accordingly - no additional work by the developer is required. Because of costs mentioned, it is recommended to enable the feature selectively, when needed with use of the granular configuration means described above.
@Audited(withModifiedFlag = true)
@Entity(name = "Customer")
public static class Customer {
@Id
private Long id;
private String firstName;
private String lastName;
@Temporal( TemporalType.TIMESTAMP )
@Column(name = "created_on")
@CreationTimestamp
private Date createdOn;
//Getters and setters are omitted for brevity
}
create table Customer_AUD (
id bigint not null,
REV integer not null,
REVTYPE tinyint,
created_on timestamp,
createdOn_MOD boolean,
firstName varchar(255),
firstName_MOD boolean,
lastName varchar(255),
lastName_MOD boolean,
primary key (id, REV)
)
As you can see, every property features a _MOD
column (e.g. createdOn_MOD
) in the audit log.
Customer customer = entityManager.find( Customer.class, 1L );
customer.setLastName( "Doe Jr." );
update
Customer
set
created_on = ?,
firstName = ?,
lastName = ?
where
id = ?
-- binding parameter [1] as [TIMESTAMP] - [2017-07-31 15:58:20.342]
-- binding parameter [2] as [VARCHAR] - [John]
-- binding parameter [3] as [VARCHAR] - [Doe Jr.]
-- binding parameter [4] as [BIGINT] - [1]
insert
into
REVINFO
(REV, REVTSTMP)
values
(null, ?)
-- binding parameter [1] as [BIGINT] - [1501505900439]
insert
into
Customer_AUD
(REVTYPE, created_on, createdOn_MOD, firstName, firstName_MOD, lastName, lastName_MOD, id, REV)
values
(?, ?, ?, ?, ?, ?, ?, ?, ?)
-- binding parameter [1] as [INTEGER] - [1]
-- binding parameter [2] as [TIMESTAMP] - [2017-07-31 15:58:20.342]
-- binding parameter [3] as [BOOLEAN] - [false]
-- binding parameter [4] as [VARCHAR] - [John]
-- binding parameter [5] as [BOOLEAN] - [false]
-- binding parameter [6] as [VARCHAR] - [Doe Jr.]
-- binding parameter [7] as [BOOLEAN] - [true]
-- binding parameter [8] as [BIGINT] - [1]
-- binding parameter [9] as [INTEGER] - [2]
To see how "Modified Flags" can be utilized, check out the very simple query API that uses them: Querying for entity revisions that modified a given property.
21.8. Selecting strategy for tracking property level changes
By default, Envers uses the legacy
modified column naming strategy.
This strategy is designed to add columns based on the following rule-set:
-
If property is annotated with
@Audited
and the modifiedColumnName attribute is specified, the column will directly be based on the supplied name. -
If property is not annotated with
@Audited
or if no modifiedColumnName attribute is given, the column will be named after the java class property, appended with the configured suffix, the default being_MOD
.
While this strategy has no performance drawbacks, it does present concerns for users who prefer consistency without verbosity. Lets take the following entity mapping as an example.
@Audited(withModifiedFlags = true)
@Entity
public class Customer {
@Id
private Integer id;
@Column(name = "customer_name")
private String name;
}
This mapping will actually lead to some inconsistent naming between columns, see below for how the model’s name will be stored in customer_name
but the modified column that tracks whether this column changes between revisions is named name_MOD
.
CREATE TABLE Customer_AUD (
id bigint not null,
REV integer not null,
REVTYPE tinyint not null,
customer_name varchar(255),
name_MOD boolean,
primary key(id, REV)
)
An additional strategy called improved
, aims to address these inconsistent column naming concerns.
This strategy uses the following rule-set:
-
Property is a Basic type (Single Column valued property)
-
Use the modifiedColumnName directly if one is supplied on the property mapping
-
Otherwise use the resolved ORM column name appended with the modified flag suffix configured value
-
-
Property is an Association (to-one mapping) with a Foreign Key using a single column
-
Use the modifiedColumnName directly if one is supplied on the property mapping
-
Otherwise use the resolved ORM column name appended with the modified flag suffix configured value
-
-
Property is an Association (to-one mapping) with a Foreign Key using multiple columns
-
Use the modifiedColumnName directly if one is supplied on the property mapping
-
Otherwise use the property name appended with the modified flag suffix configured value
-
-
Property is an Embeddable
-
Use the modifiedColumnName directly if one is supplied on the property mapping
-
Otherwise use the property name appended with the modified flag suffix configured value
-
While using this strategy, the same Customer
mapping will generate the following table schema:
CREATE TABLE Customer_AUD (
id bigint not null,
REV integer not null,
REVTYPE tinyint not null,
customer_name varchar(255),
customer_name_MOD boolean,
primary key(id, REV)
)
When already using Envers in conjunction with the modified columns flag feature, it is advised not to enable the new strategy immediately as schema changes would be required.
You will need to either migrate your existing schema manually to adhere to the rules above or use the explicit modifiedColumnName attribute on the @Audited
annotation for existing columns that use the feature.
To configure a custom strategy implementation or use the improved strategy, the configuration option org.hibernate.envers.modified_column_naming_strategy
will need to be set.
This option can be the fully qualified class name of a ModifiedColumnNameStrategy
implementation or legacy
or improved
for either of the two provided implementations.
21.9. Queries
You can think of historic data as having two dimensions:
- horizontal
-
The state of the database at a given revision. Thus, you can query for entities as they were at revision N.
- vertical
-
The revisions, at which entities changed. Hence, you can query for revisions, in which a given entity changed.
The queries in Envers are similar to Hibernate Criteria queries, so if you are familiar with them, using Envers queries will be much easier.
The main limitation of the current queries implementation is that you cannot traverse relations. You can only specify constraints on the ids of the related entities, and only on the "owning" side of the relation. This, however, will be changed in future releases.
The queries on the audited data will be in many cases much slower than corresponding queries on "live" data, as, especially for the default audit strategy, they involve correlated subselects. Queries are improved both in terms of speed and possibilities when using the validity audit strategy,
which stores both start and end revisions for entities. See Configuring the |
21.10. Querying for entities of a class at a given revision
The entry point for this type of queries is:
Customer
entity at a given revisionCustomer customer = (Customer) AuditReaderFactory
.get( entityManager )
.createQuery()
.forEntitiesAtRevision( Customer.class, revisions.get( 0 ) )
.getSingleResult();
assertEquals("Doe", customer.getLastName());
21.11. Querying for entities using filtering criteria
You can then specify constraints, which should be met by the entities returned, by adding restrictions,
which can be obtained using the AuditEntity
factory class.
For example, to select only entities where the firstName
property is equal to "John":
Customer
audit log with a given firstName
attribute valueList<Customer> customers = AuditReaderFactory
.get( entityManager )
.createQuery()
.forRevisionsOfEntity( Customer.class, true, true )
.add( AuditEntity.property( "firstName" ).eq( "John" ) )
.getResultList();
assertEquals(2, customers.size());
assertEquals( "Doe", customers.get( 0 ).getLastName() );
assertEquals( "Doe Jr.", customers.get( 1 ).getLastName() );
And, to select only entities whose relationships are related to a given entity, you can use either the target entity or its identifier.
Customer
entities whose address
attribute matches the given entity referenceAddress address = entityManager.getReference( Address.class, 1L );
List<Customer> customers = AuditReaderFactory
.get( entityManager )
.createQuery()
.forRevisionsOfEntity( Customer.class, true, true )
.add( AuditEntity.property( "address" ).eq( address ) )
.getResultList();
assertEquals(2, customers.size());
select
c.id as id1_3_,
c.REV as REV2_3_,
c.REVTYPE as REVTYPE3_3_,
c.REVEND as REVEND4_3_,
c.created_on as created_5_3_,
c.firstName as firstNam6_3_,
c.lastName as lastName7_3_,
c.address_id as address_8_3_
from
Customer_AUD c
where
c.address_id = ?
order by
c.REV asc
-- binding parameter [1] as [BIGINT] - [1]
The same SQL is generated even if we provide the identifier instead of the target entity reference.
Customer
entities whose address
identifier matches the given entity identifierList<Customer> customers = AuditReaderFactory
.get( entityManager )
.createQuery()
.forRevisionsOfEntity( Customer.class, true, true )
.add( AuditEntity.relatedId( "address" ).eq( 1L ) )
.getResultList();
assertEquals(2, customers.size());
Apart from strict equality matching, you can also use an IN
clause to provide multiple entity identifiers:
Customer
entities whose address
identifier matches one of the given entity identifiersList<Customer> customers = AuditReaderFactory
.get( entityManager )
.createQuery()
.forRevisionsOfEntity( Customer.class, true, true )
.add( AuditEntity.relatedId( "address" ).in( new Object[] { 1L, 2L } ) )
.getResultList();
assertEquals(2, customers.size());
select
c.id as id1_3_,
c.REV as REV2_3_,
c.REVTYPE as REVTYPE3_3_,
c.REVEND as REVEND4_3_,
c.created_on as created_5_3_,
c.firstName as firstNam6_3_,
c.lastName as lastName7_3_,
c.address_id as address_8_3_
from
Customer_AUD c
where
c.address_id in (
? , ?
)
order by
c.REV asc
-- binding parameter [1] as [BIGINT] - [1]
-- binding parameter [2] as [BIGINT] - [2]
You can limit the number of results, order them, and set aggregations and projections (except grouping) in the usual way.
When your query is complete, you can obtain the results by calling the getSingleResult()
or getResultList()
methods.
A full query, can look for example like this:
Customer
entities using filtering and paginationList<Customer> customers = AuditReaderFactory
.get( entityManager )
.createQuery()
.forRevisionsOfEntity( Customer.class, true, true )
.addOrder( AuditEntity.property( "lastName" ).desc() )
.add( AuditEntity.relatedId( "address" ).eq( 1L ) )
.setFirstResult( 1 )
.setMaxResults( 2 )
.getResultList();
assertEquals(1, customers.size());
select
c.id as id1_3_,
c.REV as REV2_3_,
c.REVTYPE as REVTYPE3_3_,
c.REVEND as REVEND4_3_,
c.created_on as created_5_3_,
c.firstName as firstNam6_3_,
c.lastName as lastName7_3_,
c.address_id as address_8_3_
from
Customer_AUD c
where
c.address_id = ?
order by
c.lastName desc
limit ?
offset ?
21.12. Querying for revisions, at which entities of a given class changed
The entry point for this type of queries is:
AuditQuery query = AuditReaderFactory.get( entityManager )
.createQuery()
.forRevisionsOfEntity( Customer.class, false, true );
You can add constraints to this query in the same way as to the previous one.
There are some additional possibilities:
-
using
AuditEntity.revisionNumber()
you can specify constraints, projections and order on the revision number, in which the audited entity was modified. -
similarly, using
AuditEntity.revisionProperty( propertyName )
you can specify constraints, projections and order on a property of the revision entity, corresponding to the revision in which the audited entity was modified. -
AuditEntity.revisionType()
gives you access as above to the type of the revision (ADD
,MOD
,DEL
).
Using these methods, you can order the query results by revision number, set projection or constraint the revision number to be greater or less than a specified value, etc.
For example, the following query will select the smallest revision number, at which entity of class MyEntity
with id entityId
has changed, after revision number 2:
Number revision = (Number) AuditReaderFactory
.get( entityManager )
.createQuery()
.forRevisionsOfEntity( Customer.class, false, true )
.addProjection( AuditEntity.revisionNumber().min() )
.add( AuditEntity.id().eq( 1L ) )
.add( AuditEntity.revisionNumber().gt( 2 ) )
.getSingleResult();
The second additional feature you can use in queries for revisions is the ability to maximize/minimize a property.
For example, if you want to select the smallest possible revision at which the value of the createdOn
attribute was larger then a given value,
you can run the following query:
Number revision = (Number) AuditReaderFactory
.get( entityManager )
.createQuery()
.forRevisionsOfEntity( Customer.class, false, true )
.addProjection( AuditEntity.revisionNumber().min() )
.add( AuditEntity.id().eq( 1L ) )
.add(
AuditEntity.property( "createdOn" )
.minimize()
.add( AuditEntity.property( "createdOn" )
.ge(
Timestamp.from(
LocalDateTime.now()
.minusDays( 1 )
.toInstant( ZoneOffset.UTC )
)
)
)
)
.getSingleResult();
The minimize()
and maximize()
methods return a criterion, to which you can add constraints,
which must be met by the entities with the maximized/minimized properties.
You probably also noticed that there are two boolean parameters, passed when creating the query.
selectEntitiesOnly
-
The first parameter is only valid when you don’t set an explicit projection.
If true, the result of the query will be a list of entities (which changed at revisions satisfying the specified constraints).
If false, the result will be a list of three element arrays:
-
the first element will be the changed entity instance.
-
the second will be an entity containing revision data (if no custom entity is used, this will be an instance of
DefaultRevisionEntity
). -
the third will be the type of the revision (one of the values of the
RevisionType
enumeration:ADD
,MOD
,DEL
).
-
selectDeletedEntities
-
The second parameter specifies if revisions, in which the entity was deleted should be included in the results.
If yes, such entities will have the revision type
DEL
and all attributes, except theid
, will be set tonull
.
Another useful feature is AggregatedAuditExpression#computeAggregationInInstanceContext()
. This can be used to create
an aggregate query based on the entity instance primary key.
For example, if you wanted to locate all customers but only wanted to retrieve the instances with the maximum revision number, you would use the following query:
List<Customer> results = AuditReaderFactory
.get( entityManager )
.createQuery()
.forRevisionsOfEntity( Customer.class, true, false )
.add( AuditEntity.revisionNumber().maximize().computeAggregationInInstanceContext() )
.getResultList();
In other words, the result set would contain a list of Customer
instances, one per primary key. Each instance would
hold the audited property data at the maximum revision number for each Customer
primary key.
21.13. Querying for entity revisions that modified a given property
For the two types of queries described above it’s possible to use special Audit
criteria called hasChanged()
and hasNotChanged()
that make use of the functionality described in Tracking entity changes at the property level.
Let’s have a look at various queries that can benefit from these two criteria.
First, you must make sure that your entity can track modification flags:
@Audited( withModifiedFlag = true )
The following query will return all revisions of the Customer
entity with the given id
,
for which the lastName
property has changed.
Customer
revisions for which the lastName
attribute has changedList<Customer> customers = AuditReaderFactory
.get( entityManager )
.createQuery()
.forRevisionsOfEntity( Customer.class, false, true )
.add( AuditEntity.id().eq( 1L ) )
.add( AuditEntity.property( "lastName" ).hasChanged() )
.getResultList();
select
c.id as id1_3_0_,
c.REV as REV2_3_0_,
defaultrev1_.REV as REV1_4_1_,
c.REVTYPE as REVTYPE3_3_0_,
c.REVEND as REVEND4_3_0_,
c.created_on as created_5_3_0_,
c.createdOn_MOD as createdO6_3_0_,
c.firstName as firstNam7_3_0_,
c.firstName_MOD as firstNam8_3_0_,
c.lastName as lastName9_3_0_,
c.lastName_MOD as lastNam10_3_0_,
c.address_id as address11_3_0_,
c.address_MOD as address12_3_0_,
defaultrev1_.REVTSTMP as REVTSTMP2_4_1_
from
Customer_AUD c cross
join
REVINFO defaultrev1_
where
c.id = ?
and c.lastName_MOD = ?
and c.REV=defaultrev1_.REV
order by
c.REV asc
-- binding parameter [1] as [BIGINT] - [1]
-- binding parameter [2] as [BOOLEAN] - [true]
Using this query we won’t get all other revisions in which lastName
wasn’t touched.
From the SQL query you can see that the lastName_MOD
column is being used in the WHERE clause,
hence the aforementioned requirement for tracking modification flags.
Of course, nothing prevents users from combining hasChanged
condition with some additional criteria.
Customer
revisions for which the lastName
attribute has changed and the firstName
attribute has not changedList<Customer> customers = AuditReaderFactory
.get( entityManager )
.createQuery()
.forRevisionsOfEntity( Customer.class, false, true )
.add( AuditEntity.id().eq( 1L ) )
.add( AuditEntity.property( "lastName" ).hasChanged() )
.add( AuditEntity.property( "firstName" ).hasNotChanged() )
.getResultList();
select
c.id as id1_3_0_,
c.REV as REV2_3_0_,
defaultrev1_.REV as REV1_4_1_,
c.REVTYPE as REVTYPE3_3_0_,
c.REVEND as REVEND4_3_0_,
c.created_on as created_5_3_0_,
c.createdOn_MOD as createdO6_3_0_,
c.firstName as firstNam7_3_0_,
c.firstName_MOD as firstNam8_3_0_,
c.lastName as lastName9_3_0_,
c.lastName_MOD as lastNam10_3_0_,
c.address_id as address11_3_0_,
c.address_MOD as address12_3_0_,
defaultrev1_.REVTSTMP as REVTSTMP2_4_1_
from
Customer_AUD c cross
join
REVINFO defaultrev1_
where
c.id=?
and c.lastName_MOD=?
and c.firstName_MOD=?
and c.REV=defaultrev1_.REV
order by
c.REV asc
-- binding parameter [1] as [BIGINT] - [1]
-- binding parameter [2] as [BOOLEAN] - [true]
-- binding parameter [3] as [BOOLEAN] - [false]
To get the Customer
entities changed at a given revisionNumber
with lastName
modified and firstName
untouched,
we have to use the forEntitiesModifiedAtRevision
query:
Customer
entity for a given revision if the lastName
attribute has changed and the firstName
attribute has not changedCustomer customer = (Customer) AuditReaderFactory
.get( entityManager )
.createQuery()
.forEntitiesModifiedAtRevision( Customer.class, 2 )
.add( AuditEntity.id().eq( 1L ) )
.add( AuditEntity.property( "lastName" ).hasChanged() )
.add( AuditEntity.property( "firstName" ).hasNotChanged() )
.getSingleResult();
select
c.id as id1_3_,
c.REV as REV2_3_,
c.REVTYPE as REVTYPE3_3_,
c.REVEND as REVEND4_3_,
c.created_on as created_5_3_,
c.createdOn_MOD as createdO6_3_,
c.firstName as firstNam7_3_,
c.firstName_MOD as firstNam8_3_,
c.lastName as lastName9_3_,
c.lastName_MOD as lastNam10_3_,
c.address_id as address11_3_,
c.address_MOD as address12_3_
from
Customer_AUD c
where
c.REV=?
and c.id=?
and c.lastName_MOD=?
and c.firstName_MOD=?
-- binding parameter [1] as [INTEGER] - [2]
-- binding parameter [2] as [BIGINT] - [1]
-- binding parameter [3] as [BOOLEAN] - [true]
-- binding parameter [4] as [BOOLEAN] - [false]
21.14. Querying for revisions of entity including property names that were modified
This feature described here is still considered experimental. It is subject to change in future releases based on user feedback to improve its usefulness. |
Sometimes it may be useful to query entity revisions and also determine all the properties of that revision which
were modified without having to issue multiple queries using hasChanged()
and hasNotChanged()
criteria.
You can now obtain this information easily by using the following query:
List results = AuditReaderFactory.get( entityManager )
.createQuery()
.forRevisionsOfEntityWithChanges( Customer.class, false )
.add( AuditEntity.id().eq( 1L ) )
.getResultList();
for ( Object entry : results ) {
final Object[] array = (Object[]) entry;
final Set<String> propertiesChanged = (Set<String>) array[3];
for ( String propertyName : propertiesChanged ) {
/* Do something useful with the modified property `propertyName` */
}
}
21.15. Querying for entity types modified in a given revision
The methods described below can be used only when the default mechanism of tracking changed entity types is enabled (see Tracking entity names modified during revisions). |
This basic query allows retrieving entity names and corresponding Java classes changed in a specified revision:
assertEquals(
"org.hibernate.userguide.envers.EntityTypeChangeAuditTest$Customer",
AuditReaderFactory
.get( entityManager )
.getCrossTypeRevisionChangesReader()
.findEntityTypes( 1 )
.iterator().next()
.getFirst()
);
assertEquals(
"org.hibernate.userguide.envers.EntityTypeChangeAuditTest$ApplicationCustomer",
AuditReaderFactory
.get( entityManager )
.getCrossTypeRevisionChangesReader()
.findEntityTypes( 2 )
.iterator().next()
.getFirst()
);
Other queries (also accessible from org.hibernate.envers.CrossTypeRevisionChangesReader
):
List<Object> findEntities(Number)
-
Returns snapshots of all audited entities changed (added, updated and removed) in a given revision. Executes
N + 1
SQL queries, whereN
is a number of different entity classes modified within specified revision. List<Object> findEntities(Number, RevisionType)
-
Returns snapshots of all audited entities changed (added, updated or removed) in a given revision filtered by modification type. Executes
N + 1
SQL queries, whereN
is a number of different entity classes modified within specified revision. Map<RevisionType, List<Object>> findEntitiesGroupByRevisionType(Number)
-
Returns a map containing lists of entity snapshots grouped by modification operation (e.g. addition, update and removal). Executes
3N + 1
SQL queries, whereN
is a number of different entity classes modified within specified revision.
21.16. Querying for entities using entity relation joins
Relation join queries are considered experimental and may change in future releases. |
Audit queries support the ability to apply constraints, projections, and sort operations based on entity relations. In order to traverse entity relations through an audit query, you must use the relation traversal API with a join type.
Relation joins can be applied to |
The basis for creating an entity relation join query is as follows:
AuditQuery innerJoinAuditQuery = AuditReaderFactory
.get( entityManager )
.createQuery()
.forEntitiesAtRevision( Customer.class, 1 )
.traverseRelation( "address", JoinType.INNER );
AuditQuery innerJoinAuditQuery = AuditReaderFactory
.get( entityManager )
.createQuery()
.forEntitiesAtRevision( Customer.class, 1 )
.traverseRelation( "address", JoinType.LEFT );
Like any other query, constraints may be added to restrict the results.
For example, to find all Customer
entities at a given revision whose addresses are in România
,
you can use the following query:
List<Customer> customers = AuditReaderFactory
.get( entityManager )
.createQuery()
.forEntitiesAtRevision( Customer.class, 1 )
.traverseRelation( "address", JoinType.INNER )
.add( AuditEntity.property( "country" ).eq( "România" ) )
.getResultList();
select
c.id as id1_3_,
c.REV as REV2_3_,
c.REVTYPE as REVTYPE3_3_,
c.REVEND as REVEND4_3_,
c.created_on as created_5_3_,
c.firstName as firstNam6_3_,
c.lastName as lastName7_3_,
c.address_id as address_8_3_
from
Customer_AUD c
inner join
Address_AUD a
on (
c.address_id=a.id
or (
c.address_id is null
)
and (
a.id is null
)
)
where
c.REV<=?
and c.REVTYPE<>?
and (
c.REVEND>?
or c.REVEND is null
)
and a.REV<=?
and a.country=?
and (
a.REVEND>?
or a.REVEND is null
)
-- binding parameter [1] as [INTEGER] - [1]
-- binding parameter [2] as [INTEGER] - [2]
-- binding parameter [3] as [INTEGER] - [1]
-- binding parameter [4] as [INTEGER] - [1]
-- binding parameter [5] as [VARCHAR] - [România]
-- binding parameter [6] as [INTEGER] - [1]
It is also possible to traverse beyond the first relation in an entity graph.
For example, to find all Customer
entities at a given revision
with the country attribute of the address property being România
:
List<Customer> customers = AuditReaderFactory
.get( entityManager )
.createQuery()
.forEntitiesAtRevision( Customer.class, 1 )
.traverseRelation( "address", JoinType.INNER )
.traverseRelation( "country", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "România" ) )
.getResultList();
assertEquals( 1, customers.size() );
select
cu.id as id1_5_,
cu.REV as REV2_5_,
cu.REVTYPE as REVTYPE3_5_,
cu.REVEND as REVEND4_5_,
cu.created_on as created_5_5_,
cu.firstName as firstNam6_5_,
cu.lastName as lastName7_5_,
cu.address_id as address_8_5_
from
Customer_AUD cu
inner join
Address_AUD a
on (
cu.address_id=a.id
or (
cu.address_id is null
)
and (
a.id is null
)
)
inner join
Country_AUD co
on (
a.country_id=co.id
or (
a.country_id is null
)
and (
co.id is null
)
)
where
cu.REV<=?
and cu.REVTYPE<>?
and (
cu.REVEND>?
or cu.REVEND is null
)
and a.REV<=?
and (
a.REVEND>?
or a.REVEND is null
)
and co.REV<=?
and co.name=?
and (
co.REVEND>?
or co.REVEND is null
)
-- binding parameter [1] as [INTEGER] - [1]
-- binding parameter [2] as [INTEGER] - [2]
-- binding parameter [3] as [INTEGER] - [1]
-- binding parameter [4] as [INTEGER] - [1]
-- binding parameter [5] as [INTEGER] - [1]
-- binding parameter [6] as [INTEGER] - [1]
-- binding parameter [7] as [VARCHAR] - [România]
-- binding parameter [8] as [INTEGER] - [1]
Constraints may also be added to the properties of nested joined relations, such as testing for null
.
For example, the following query illustrates how to find all Customer
entities at a given revision
having the address
in Cluj-Napoca
or the address
does not have any country entity reference:
List<Customer> customers = AuditReaderFactory
.get( entityManager )
.createQuery()
.forEntitiesAtRevision( Customer.class, 1 )
.traverseRelation( "address", JoinType.LEFT, "a" )
.add(
AuditEntity.or(
AuditEntity.property( "a", "city" ).eq( "Cluj-Napoca" ),
AuditEntity.relatedId( "country" ).eq( null )
)
)
.getResultList();
select
c.id as id1_5_,
c.REV as REV2_5_,
c.REVTYPE as REVTYPE3_5_,
c.REVEND as REVEND4_5_,
c.created_on as created_5_5_,
c.firstName as firstNam6_5_,
c.lastName as lastName7_5_,
c.address_id as address_8_5_
from
Customer_AUD c
left outer join
Address_AUD a
on (
c.address_id=a.id
or (
c.address_id is null
)
and (
a.id is null
)
)
where
c.REV<=?
and c.REVTYPE<>?
and (
c.REVEND>?
or c.REVEND is null
)
and (
a.REV is null
or a.REV<=?
and (
a.REVEND>?
or a.REVEND is null
)
)
and (
a.city=?
or a.country_id is null
)
-- binding parameter [1] as [INTEGER] - [1]
-- binding parameter [2] as [INTEGER] - [2]
-- binding parameter [3] as [INTEGER] - [1]
-- binding parameter [4] as [INTEGER] - [1]
-- binding parameter [5] as [INTEGER] - [1]
-- binding parameter [6] as [VARCHAR] - [Cluj-Napoca]
Queries can use the |
Disjunction criterion may also be applied to relation join queries.
For example, the following query will find all Customer
entities at a given revision
where the country name is România
or that the Customer
lives in Cluj-Napoca
:
List<Customer> customers = AuditReaderFactory
.get( entityManager )
.createQuery()
.forEntitiesAtRevision( Customer.class, 1 )
.traverseRelation( "address", JoinType.INNER, "a" )
.traverseRelation( "country", JoinType.INNER, "cn" )
.up()
.up()
.add(
AuditEntity.disjunction()
.add( AuditEntity.property( "a", "city" ).eq( "Cluj-Napoca" ) )
.add( AuditEntity.property( "cn", "name" ).eq( "România" ) )
)
.addOrder( AuditEntity.property( "createdOn" ).asc() )
.getResultList();
select
cu.id as id1_5_,
cu.REV as REV2_5_,
cu.REVTYPE as REVTYPE3_5_,
cu.REVEND as REVEND4_5_,
cu.created_on as created_5_5_,
cu.firstName as firstNam6_5_,
cu.lastName as lastName7_5_,
cu.address_id as address_8_5_
from
Customer_AUD cu
inner join
Address_AUD a
on (
cu.address_id=a.id
or (
cu.address_id is null
)
and (
a.id is null
)
)
inner join
Country_AUD co
on (
a.country_id=co.id
or (
a.country_id is null
)
and (
co.id is null
)
)
where
cu.REV<=?
and cu.REVTYPE<>?
and (
cu.REVEND>?
or cu.REVEND is null
)
and (
a.city=?
or co.name=?
)
and a.REV<=?
and (
a.REVEND>?
or a.REVEND is null
)
and co.REV<=?
and (
co.REVEND>?
or co.REVEND is null
)
order by
cu.created_on asc
-- binding parameter [1] as [INTEGER] - [1]
-- binding parameter [2] as [INTEGER] - [2]
-- binding parameter [3] as [INTEGER] - [1]
-- binding parameter [4] as [VARCHAR] - [Cluj-Napoca]
-- binding parameter [5] as [VARCHAR] - [România]
-- binding parameter [6] as [INTEGER] - [1]
-- binding parameter [7] as [INTEGER] - [1]
-- binding parameter [8] as [INTEGER] - [1]
-- binding parameter [9] as [INTEGER] - [1]
Lastly, this example illustrates how related entity properties can be compared in a single constraint.
Assuming the Customer
and the Address
were previously changed as follows:
Address
to match the Country
nameCustomer customer = entityManager.createQuery(
"select c " +
"from Customer c " +
"join fetch c.address a " +
"join fetch a.country " +
"where c.id = :id", Customer.class )
.setParameter( "id", 1L )
.getSingleResult();
customer.setLastName( "Doe Sr." );
customer.getAddress().setCity(
customer.getAddress().getCountry().getName()
);
The following query shows how to find the Customer
entities
where the city
property of the address
attribute equals the name
of the associated country
attribute.
List<Number> revisions = AuditReaderFactory.get( entityManager ).getRevisions(
Customer.class,
1L
);
List<Customer> customers = AuditReaderFactory
.get( entityManager )
.createQuery()
.forEntitiesAtRevision( Customer.class, revisions.get( revisions.size() - 1 ) )
.traverseRelation( "address", JoinType.INNER, "a" )
.traverseRelation( "country", JoinType.INNER, "cn" )
.up()
.up()
.add( AuditEntity.property( "a", "city" ).eqProperty( "cn", "name" ) )
.getResultList();
select
cu.id as id1_5_,
cu.REV as REV2_5_,
cu.REVTYPE as REVTYPE3_5_,
cu.REVEND as REVEND4_5_,
cu.created_on as created_5_5_,
cu.firstName as firstNam6_5_,
cu.lastName as lastName7_5_,
cu.address_id as address_8_5_
from
Customer_AUD cu
inner join
Address_AUD a
on (
cu.address_id=a.id
or (
cu.address_id is null
)
and (
a.id is null
)
)
inner join
Country_AUD cr
on (
a.country_id=cr.id
or (
a.country_id is null
)
and (
cr.id is null
)
)
where
cu.REV<=?
and cu.REVTYPE<>?
and a.city=cr.name
and (
cu.REVEND>?
or cu.REVEND is null
)
and a.REV<=?
and (
a.REVEND>?
or a.REVEND is null
)
and cr.REV<=?
and (
cr.REVEND>?
or cr.REVEND is null
)
-- binding parameter [1] as [INTEGER] - [2]
-- binding parameter [2] as [INTEGER] - [2]
-- binding parameter [3] as [INTEGER] - [2]
-- binding parameter [4] as [INTEGER] - [2]
-- binding parameter [5] as [INTEGER] - [2]
-- binding parameter [6] as [INTEGER] - [2]
-- binding parameter [7] as [INTEGER] - [2]
21.17. Querying for revision information without loading entities
Sometimes, it may be useful to load information about revisions to find out who performed specific revisions or to know what entity names were modified but the change log about the related audited entities isn’t needed. This API allows an efficient way to get the revision information entity log without instantiating the actual entities themselves.
Here is a simple example:
AuditQuery query = getAuditReader().createQuery()
.forRevisionsOfEntity( DefaultRevisionEntity.class, true )
.add( AuditEntity.revisionNumber().between( 1, 25 ) );
This query will return all revision information entities for revisions between 1 and 25 including those which are
related to deletions. If deletions are not of interest, you would pass false
as the second argument.
Note that this query uses the DefaultRevisionEntity
class type. The class provided will vary depending on the
configuration properties used to configure Envers or if you supply your own revision entity. Typically users who
will use this API will likely be providing a custom revision entity implementation to obtain custom information
being maintained per revision.
21.18. Conditional auditing
Envers persists audit data in reaction to various Hibernate events (e.g. post update
, post insert
, and so on),
using a series of event listeners from the org.hibernate.envers.event.spi
package.
By default, if the Envers jar is in the classpath, the event listeners are auto-registered with Hibernate.
Conditional auditing can be implemented by overriding some of the Envers event listeners. To use customized Envers event listeners, the following steps are needed:
-
Turn off automatic Envers event listeners registration by setting the
hibernate.envers.autoRegisterListeners
Hibernate property tofalse
. -
Create subclasses for appropriate event listeners. For example, if you want to conditionally audit entity insertions, extend the
org.hibernate.envers.event.spi.EnversPostInsertEventListenerImpl
class. Place the conditional-auditing logic in the subclasses, call the super method if auditing should be performed. -
Create your own implementation of
org.hibernate.integrator.spi.Integrator
, similar toorg.hibernate.envers.boot.internal.EnversIntegrator
. Use your event listener classes instead of the default ones. -
For the integrator to be automatically used when Hibernate starts up, you will need to add a
META-INF/services/org.hibernate.integrator.spi.Integrator
file to your jar. The file should contain the fully qualified name of the class implementing the interface.
The use of |
21.19. Understanding the Envers Schema
For each audited entity (that is, for each entity containing at least one audited field), an audit table is created.
By default, the audit table’s name is created by adding an "_AUD" suffix to the original table name,
but this can be overridden by specifying a different suffix/prefix in the configuration properties or per-entity using the @org.hibernate.envers.AuditTable
annotation.
The audit table contains the following columns:
- id
-
id
of the original entity (this can be more then one column in the case of composite primary keys). - revision number
-
an integer, which matches to the revision number in the revision entity table.
- revision type
-
The
org.hibernate.envers.RevisionType
enumeration ordinal stating if the change represents an INSERT, UPDATE or DELETE. - audited fields
-
properties from the original entity being audited.
The primary key of the audit table is the combination of the original id of the entity and the revision number, so there can be at most one historic entry for a given entity instance at a given revision.
The current entity data is stored in the original table and in the audit table. This is a duplication of data, however, as this solution makes the query system much more powerful, and as memory is cheap, hopefully, this won’t be a major drawback for the users.
A row in the audit table with entity id ID
, revision N
, and data D
means: entity with id ID
has data D
from revision N
upwards.
Hence, if we want to find an entity at revision M
, we have to search for a row in the audit table, which has the revision number smaller or equal to M
, but as large as possible.
If no such row is found, or a row with a "deleted" marker is found, it means that the entity didn’t exist at that revision.
The "revision type" field can currently have three values: 0
, 1
and 2
, which means ADD
, MOD
, and DEL
, respectively.
A row with a revision of type DEL
will only contain the id of the entity and no data (all fields NULL
), as it only serves as a marker saying "this entity was deleted at that revision".
Additionally, there is a revision entity table which contains the information about the global revision.
By default, the generated table is named REVINFO
and contains just two columns: ID
and TIMESTAMP
.
A row is inserted into this table on each new revision, that is, on each commit of a transaction, which changes audited data.
The name of this table can be configured, the name of its columns as well as adding additional columns can be achieved as discussed in Revision Log.
While global revisions are a good way to provide correct auditing of relations, some people have pointed out that this may be a bottleneck in systems where data is very often modified. One viable solution is to introduce an option to have an entity "locally revisioned", that is revisions would be created for it independently.
This would not enable correct versioning of relations, but it would work without the Another possibility is to introduce a notion of "revisioning groups", which would group entities sharing the same revision numbering. Each such group would have to consist of one or more strongly connected components belonging to the entity graph induced by relations between entities. Your opinions on the subject are very welcome on the forum. |
21.20. Generating Envers schema with Hibernate hbm2ddl tool
If you would like to generate the database schema file with Hibernate, you simply need to use the hbm2ddl too.
This task will generate the definitions of all entities, both of those which are audited by Envers and those which are not.
See the Schema generation chapter for more info.
For the following entities, Hibernate is going to generate the following database schema:
@Audited
@Entity(name = "Customer")
public static class Customer {
@Id
private Long id;
private String firstName;
private String lastName;
@Temporal( TemporalType.TIMESTAMP )
@Column(name = "created_on")
@CreationTimestamp
private Date createdOn;
@ManyToOne(fetch = FetchType.LAZY)
private Address address;
//Getters and setters omitted for brevity
}
@Audited
@Entity(name = "Address")
public static class Address {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private Country country;
private String city;
private String street;
private String streetNumber;
//Getters and setters omitted for brevity
}
@Audited
@Entity(name = "Country")
public static class Country {
@Id
private Long id;
private String name;
//Getters and setters omitted for brevity
}
create table Address (
id bigint not null,
city varchar(255),
street varchar(255),
streetNumber varchar(255),
country_id bigint,
primary key (id)
)
create table Address_AUD (
id bigint not null,
REV integer not null,
REVTYPE tinyint,
REVEND integer,
city varchar(255),
street varchar(255),
streetNumber varchar(255),
country_id bigint,
primary key (id, REV)
)
create table Country (
id bigint not null,
name varchar(255),
primary key (id)
)
create table Country_AUD (
id bigint not null,
REV integer not null,
REVTYPE tinyint,
REVEND integer,
name varchar(255),
primary key (id, REV)
)
create table Customer (
id bigint not null,
created_on timestamp,
firstName varchar(255),
lastName varchar(255),
address_id bigint,
primary key (id)
)
create table Customer_AUD (
id bigint not null,
REV integer not null,
REVTYPE tinyint,
REVEND integer,
created_on timestamp,
firstName varchar(255),
lastName varchar(255),
address_id bigint,
primary key (id, REV)
)
create table REVINFO (
REV integer generated by default as identity,
REVTSTMP bigint,
primary key (REV)
)
alter table Address
add constraint FKpr4rl83u5fv832kdihl6w3kii
foreign key (country_id)
references Country
alter table Address_AUD
add constraint FKgwp5sek4pjb4awy66sp184hrv
foreign key (REV)
references REVINFO
alter table Address_AUD
add constraint FK52pqkpismfxg2b9tmwtncnk0d
foreign key (REVEND)
references REVINFO
alter table Country_AUD
add constraint FKrix4g8hm9ui6sut5sy86ujggr
foreign key (REV)
references REVINFO
alter table Country_AUD
add constraint FKpjeqmdccv22y1lbtswjb84ghi
foreign key (REVEND)
references REVINFO
alter table Customer
add constraint FKfok4ytcqy7lovuiilldbebpd9
foreign key (address_id)
references Address
alter table Customer_AUD
add constraint FK5ecvi1a0ykunrriib7j28vpdj
foreign key (REV)
references REVINFO
alter table Customer_AUD
add constraint FKqd4fy7ww1yy95wi4wtaonre3f
foreign key (REVEND)
references REVINFO
21.21. Mapping exceptions
21.21.1. What isn’t and will not be supported
Bags are not supported because they can contain non-unique elements. Persisting a bag of `String`s violates the relational database principle that each table is a set of tuples.
In case of bags, however (which require a join table), if there is a duplicate element, the two tuples corresponding to the elements will be the same. Although Hibernate allows this, Envers (or more precisely the database connector) will throw an exception when trying to persist two identical elements because of a unique constraint violation.
There are at least two ways out if you need bag semantics:
-
use an indexed collection, with the
@javax.persistence.OrderColumn
annotation. -
provide a unique id for your elements with the
@CollectionId
annotation.
21.21.2. What isn’t and will be supported
-
Bag style collections with a
@CollectionId
identifier column (see HHH-3950).
21.22. @OneToMany
with @JoinColumn
When a collection is mapped using these two annotations, Hibernate doesn’t generate a join table. Envers, however, has to do this so that when you read the revisions in which the related entity has changed, you don’t get false results.
To be able to name the additional join table, there is a special annotation: @AuditJoinTable
, which has similar semantics to JPA @JoinTable
.
One special case is to have relations mapped with @OneToMany
with @JoinColumn
on the one side, and @ManyToOne
and @JoinColumn( insertable = false, updatable = false
) on the many side.
Such relations are, in fact, bidirectional, but the owning side is the collection.
To properly audit such relations with Envers, you can use the @AuditMappedBy
annotation.
It enables you to specify the reverse property (using the mappedBy
element).
In case of indexed collections, the index column must also be mapped in the referenced entity (using @Column( insertable = false, updatable = false )
, and specified using positionMappedBy
.
This annotation will affect only the way Envers works.
Please note that the annotation is experimental and may change in the future.
21.24. Benefits of audit table partitioning
Because audit tables tend to grow indefinitely, they can quickly become really large. When the audit tables have grown to a certain limit (varying per RDBMS and/or operating system) it makes sense to start using table partitioning. SQL table partitioning offers a lot of advantages including, but certainly not limited to:
-
Improved query performance by selectively moving rows to various partitions (or even purging old rows).
-
Faster data loads, index creation, etc.
21.25. Suitable columns for audit table partitioning
Generally, SQL tables must be partitioned on a column that exists within the table. As a rule, it makes sense to use either the end revision or the end revision timestamp column for partitioning of audit tables.
End revision information is not available for the default Therefore the following Envers configuration options are required:
Optionally, you can also override the default values using following properties:
For more information, see Configuration Properties. |
The reason why the end revision information should be used for audit table partitioning is based on the assumption that audit tables should be partitioned on an 'increasing level of relevancy', like so:
-
A couple of partitions with audit data that is not very (or no longer) relevant. This can be stored on slow media, and perhaps even be purged eventually.
-
Some partitions for audit data that is potentially relevant.
-
One partition for audit data that is most likely to be relevant. This should be stored on the fastest media, both for reading and writing.
21.26. Audit table partitioning example
In order to determine a suitable column for the 'increasing level of relevancy', consider a simplified example of a salary registration for an unnamed agency.
Currently, the salary table contains the following rows for a certain person X:
Year | Salary (USD) |
---|---|
2006 |
3300 |
2007 |
3500 |
2008 |
4000 |
2009 |
4500 |
The salary for the current fiscal year (2010) is unknown. The agency requires that all changes in registered salaries for a fiscal year are recorded (i.e., an audit trail). The rationale behind this is that decisions made at a certain date are based on the registered salary at that time. And at any time it must be possible to reproduce the reason why a certain decision was made at a certain date.
The following audit information is available, sorted in order of occurrence:
Year | Revision type | Revision timestamp | Salary (USD) | End revision timestamp |
---|---|---|---|---|
2006 |
ADD |
2007-04-01 |
3300 |
null |
2007 |
ADD |
2008-04-01 |
35 |
2008-04-02 |
2007 |
MOD |
2008-04-02 |
3500 |
null |
2008 |
ADD |
2009-04-01 |
3700 |
2009-07-01 |
2008 |
MOD |
2009-07-01 |
4100 |
2010-02-01 |
2008 |
MOD |
2010-02-01 |
4000 |
null |
2009 |
ADD |
2010-04-01 |
4500 |
null |
21.27. Determining a suitable partitioning column
To partition this data, the level of relevancy must be defined. Consider the following:
-
For the fiscal year 2006, there is only one revision. It has the oldest revision timestamp of all audit rows, but should still be regarded as relevant because it’s the latest modification for this fiscal year in the salary table (its end revision timestamp is null).
Also, note that it would be very unfortunate if in 2011 there would be an update of the salary for the fiscal year 2006 (which is possible until at least 10 years after the fiscal year), and the audit information would have been moved to a slow disk (based on the age of the revision timestamp). Remember that, in this case, Envers will have to update the end revision timestamp of the most recent audit row.
-
There are two revisions in the salary of the fiscal year 2007 which both have nearly the same revision timestamp and a different end revision timestamp.
On first sight, it is evident that the first revision was a mistake and probably not relevant. The only relevant revision for 2007 is the one with end revision timestamp value of null.
Based on the above, it is evident that only the end revision timestamp is suitable for audit table partitioning. The revision timestamp is not suitable.
21.28. Determining a suitable partitioning scheme
A possible partitioning scheme for the salary table would be as follows:
- end revision timestamp year = 2008
-
This partition contains audit data that is not very (or no longer) relevant.
- end revision timestamp year = 2009
-
This partition contains audit data that is potentially relevant.
- end revision timestamp year >= 2010 or null
-
This partition contains the most relevant audit data.
This partitioning scheme also covers the potential problem of the update of the end revision timestamp, which occurs if a row in the audited table is modified. Even though Envers will update the end revision timestamp of the audit row to the system date at the instant of modification, the audit row will remain in the same partition (the 'extension bucket').
And sometime in 2011, the last partition (or 'extension bucket') is split into two new partitions:
-
end revision timestamp year = 2010: This partition contains audit data that is potentially relevant (in 2011).
-
end revision timestamp year >= 2011 or null: This partition contains the most interesting audit data and is the new 'extension bucket'.
21.29. Envers links
-
JIRA issue tracker (when adding issues concerning Envers, be sure to select the "envers" component!)
22. Database Portability Considerations
22.1. Portability Basics
One of the selling points of Hibernate (and really Object/Relational Mapping as a whole) is the notion of database portability. This could mean an internal IT user migrating from one database vendor to another, or it could mean a framework or deployable application consuming Hibernate to simultaneously target multiple database products by their users. Regardless of the exact scenario, the basic idea is that you want Hibernate to help you run against any number of databases without changes to your code, and ideally without any changes to the mapping metadata.
22.2. Dialect
The first line of portability for Hibernate is the dialect, which is a specialization of the org.hibernate.dialect.Dialect
contract.
A dialect encapsulates all the differences in how Hibernate must communicate with a particular database to accomplish some task like getting a sequence value or structuring a SELECT query.
Hibernate bundles a wide range of dialects for many of the most popular databases.
If you find that your particular database is not among them, it is not terribly difficult to write your own.
22.3. Dialect resolution
Originally, Hibernate would always require that users specify which dialect to use. In the case of users looking to simultaneously target multiple databases with their build that was problematic. Generally, this required their users to configure the Hibernate dialect or defining their own method of setting that value.
Starting with version 3.2, Hibernate introduced the notion of automatically detecting the dialect to use based on the java.sql.DatabaseMetaData
obtained from a java.sql.Connection
to that database.
This was much better, except that this resolution was limited to databases Hibernate know about ahead of time and was in no way configurable or overrideable.
Starting with version 3.3, Hibernate has a far more powerful way to automatically determine which dialect to be used by relying on a series of delegates which implement the org.hibernate.dialect.resolver.DialectResolver
which defines only a single method:
public Dialect resolveDialect(DatabaseMetaData metaData) throws JDBCConnectionException
The basic contract here is that if the resolver 'understands' the given database metadata then it returns the corresponding Dialect; if not it returns null and the process continues to the next resolver.
The signature also identifies org.hibernate.exception.JDBCConnectionException
as possibly being thrown.
A JDBCConnectionException
here is interpreted to imply a non-transient (aka non-recoverable) connection problem and is used to indicate an immediate stop to resolution attempts.
All other exceptions result in a warning and continuing on to the next resolver.
The cool part about these resolvers is that users can also register their own custom resolvers which will be processed ahead of the built-in Hibernate ones. This might be useful in a number of different situations:
-
it allows easy integration for auto-detection of dialects beyond those shipped with Hibernate itself.
-
it allows you to specify to use a custom dialect when a particular database is recognized.
To register one or more resolvers, simply specify them (separated by commas, tabs or spaces) using the 'hibernate.dialect_resolvers' configuration setting (see the DIALECT_RESOLVERS
constant on org.hibernate.cfg.Environment
).
22.4. Identifier generation
When considering portability between databases, another important decision is selecting the identifier generation strategy you want to use. Originally, Hibernate provided the native generator for this purpose, which was intended to select between a sequence, identity, or table strategy depending on the capability of the underlying database.
However, an insidious implication of this approach comes about when targeting some databases which support identity generation and some which do not. identity generation relies on the SQL definition of an IDENTITY (or auto-increment) column to manage the identifier value. It is what is known as a post-insert generation strategy because the insert must actually happen before we can know the identifier value.
Because Hibernate relies on this identifier value to uniquely reference entities within a persistence context,
it must then issue the insert immediately when the user requests that the entity be associated with the session (e.g. like via save()
or persist()
), regardless of current transactional semantics.
Hibernate was changed slightly, once the implications of this were better understood, so now the insert could be delayed in cases where this is feasible. The underlying issue is that the actual semantics of the application itself changes in these cases. |
Starting with version 3.2.3, Hibernate comes with a set of enhanced identifier generators targeting portability in a much different way.
There are specifically 2 bundled enhanced generators:
|
The idea behind these generators is to port the actual semantics of the identifier value generation to the different databases.
For example, the org.hibernate.id.enhanced.SequenceStyleGenerator
mimics the behavior of a sequence on databases which do not support sequences by using a table.
22.5. Database functions
When it comes to database functions, Hibernate could benefit from an improvement. In terms of portability concerns, the function handling currently works pretty well in HQL, however, it is quite lacking in all other aspects. |
SQL functions can be referenced in many ways by users. However, not all databases support the same set of functions. Hibernate provides a means of mapping a logical function name to a delegate which knows how to render that particular function, perhaps even using a totally different physical function call.
Technically the SQL function registration is handled through the It is sort of implemented such that users can programmatically register functions with the |
23. Statistics
Hibernate can gather all sorts of statistics which can help you get a better insight into what Hibernate does behind the scenes.
By default, the statistics are not collected because this incurs an additional processing and memory overhead. To instruct Hibernate to start collecting statistics, you need to set the hibernate.generate_statistics
configuration property to true
:
<property
name="hibernate.generate_statistics"
value="true"
/>
23.1. org.hibernate.stat.Statistics methods
The Hibernate statistics are made available via the
Statistics
interface which exposes the following methods:
23.1.1. General statistics methods
isStatisticsEnabled
-
Are statistics enabled?
setStatisticsEnabled(boolean b)
-
Enable statistics based on the provided parameter.
clear
-
Reset all statistics.
logSummary
-
Print a summary of the current statistics into the application log.
getStartTime
-
The milliseconds (JVM standard
currentTimeMillis()
) since the initial creation of this Statistics instance or the last timeclear()
was called.
23.1.2. Aggregated statistics methods
getQueries
-
Get executed query strings. The maximum number of queries tracked by the Hibernate statistics is given by the
hibernate.statistics.query_max_size
property. getEntityStatistics(String entityName)
-
Find entity statistics for the given name.
getCollectionStatistics(String role)
-
Get collection statistics per role (collection name).
getNaturalIdStatistics(String entityName)
-
Get the Hibernate-specific natural id resolution statistics for the given entity.
getQueryStatistics(String queryString)
-
Get the statistics for the given query string (JPQL/HQL or native SQL).
getDomainDataRegionStatistics(String regionName)
-
Get the second-level cache statistics per domain data (entity, collection, natural-id) region.
getQueryRegionStatistics(String regionName)
-
Get the second-level cache statistics per query region.
getCacheRegionStatistics(String regionName)
-
Get statistics for either a domain-data or query-result region (this method checks both, preferring domain data region if one exists).
23.1.3. SessionFactory statistics methods
getEntityNames
-
Get the names of all entities configured with the current
SessionFactory
. getCollectionRoleNames
-
Get the names of all collection roles configured with the current
SessionFactory
.
23.1.4. Session statistics methods
getSessionCloseCount
-
Global number of sessions that got closed.
getSessionOpenCount
-
Global number of sessions that got opened.
getFlushCount
-
Get the global number of flush operations executed (either manual or automatic).
23.1.5. JDBC statistics methods
getPrepareStatementCount
-
The number of JDBC prepared statements that were acquired by Hibernate.
getCloseStatementCount
-
The number of JDBC prepared statements that were released by Hibernate.
getConnectCount
-
Get the global number of connections acquired by the Hibernate sessions (the actual number of connections used may be much smaller depending whether you use a connection pool or not).
23.1.6. Transaction statistics methods
getSuccessfulTransactionCount
-
The number of transactions that completed successfully.
getTransactionCount
-
The number of transactions we know to have completed.
23.1.7. Concurrency Control statistics methods
getOptimisticFailureCount
-
The number of Hibernate
StaleObjectStateException
s or JPAOptimisticLockException
s that occurred.
23.1.8. Entity statistics methods
getEntityDeleteCount
-
Get the global number of entity deletes.
getEntityInsertCount
-
Get the global number of entity inserts.
getEntityLoadCount
-
Get the global number of entity loads.
getEntityFetchCount
-
Get the global number of entity fetches.
getEntityUpdateCount
-
Get the global number of entity updates.
23.1.9. Collection statistics methods
getCollectionLoadCount
-
Global number of collections that were loaded.
getCollectionFetchCount
-
Global number of collections that were fetched.
getCollectionUpdateCount
-
Global number of collections that were updated.
getCollectionRemoveCount
-
Global number of collections that were removed.
getCollectionRecreateCount
-
Global number of collections that were recreated.
23.1.10. Query statistics methods
getQueryExecutionCount
-
Get the global number of executed queries.
getQueryExecutionMaxTime
-
Get the time in milliseconds of the slowest query.
getQueryExecutionMaxTimeQueryString
-
Get the query string for the slowest query.
getQueryPlanCacheHitCount
-
Get the global number of query plans successfully retrieved from cache.
getQueryPlanCacheMissCount
-
Get the global number of query plans lookups not found in cache.
23.1.11. Natural id statistics methods
getNaturalIdQueryExecutionCount
-
Get the global number of natural id queries executed against the database.
getNaturalIdQueryExecutionMaxTime
-
Get the global maximum query time for natural id queries executed against the database.
getNaturalIdQueryExecutionMaxTimeRegion
-
Get the region for the maximum natural id query time.
getNaturalIdQueryExecutionMaxTimeEntity
-
Get the entity for the maximum natural id query time.
23.1.12. Second-level cache statistics methods
getSecondLevelCacheRegionNames
-
Get all second-level domain data cache region names.
getSecondLevelCacheHitCount
-
Global number of cacheable entities/collections successfully retrieved from the cache.
getSecondLevelCacheMissCount
-
Global number of cacheable entities/collections not found in the cache and loaded from the database.
getSecondLevelCachePutCount
-
Global number of cacheable entities/collections put in the cache.
Second-level cache natural id statistics methods
getNaturalIdCacheHitCount
-
Get the global number of cached natural id lookups successfully retrieved from cache.
getNaturalIdCacheMissCount
-
Get the global number of cached natural id lookups not found in cache.
getNaturalIdCachePutCount
-
Get the global number of cacheable natural id lookups put in cache.
Second-level cache query statistics methods
getQueryCacheHitCount
-
Get the global number of cached queries successfully retrieved from cache.
getQueryCacheMissCount
-
Get the global number of cached queries not found in cache.
getQueryCachePutCount
-
Get the global number of cacheable queries put in cache.
Second-level cache timestamp statistics methods
getUpdateTimestampsCacheHitCount
-
Get the global number of timestamps successfully retrieved from cache.
getUpdateTimestampsCacheMissCount
-
Get the global number of timestamp requests that were not found in the cache.
getUpdateTimestampsCachePutCount
-
Get the global number of timestamps put in cache.
23.2. Query statistics max size
Traditionally, Hibernate stored all executed queries when statistics were enabled. However, this was a very bad default since, if your application runs millions of different queries, you’d risk running out of memory.
Therefore, to restrict the number of queries the Hibernate statistics can hold, the hibernate.statistics.query_max_size
property was added.
By default, the maximum number of queries retained is 5000, but you can increase this value via the hibernate.statistics.query_max_size
property.
So, if your application makes heavy use of the JPA Criteria API or if you simply have a very large number of queries, you might want to
raise the maximum number of queries that are being stored by the Statistics
instance.
If the maximum number of queries has been reached, Hibernate uses a Least recently used (LRU) policy to make room for new query entries.
23.3. Query plan cache statistics
Every entity query, be it JPQL/HQL or Criteria API, is compiled to an AST (Abstract Syntax Tree), and this process is resource-intensive. To speed up the entity query executions, Hibernate offers a query plan cache so that compiled plans can be reused.
To monitor the query plan cache you have the following statistics.
23.3.1. Query plan cache global statistics
The Statistics
instance provides two global counters which can give you an overall picture of the query plan cache effectiveness.
-
getQueryPlanCacheHitCount
-
getQueryPlanCacheMissCount
If the hit count is high and the miss count is low, then the query plan cache is effective, and the vast majority of entity queries are served from the query plan cache, rather than being compiled over and over again.
23.3.2. Query plan cache query-level statistics
The QueryStatistics
instance, which you can get via the getQueryStatistics(String queryString)
method of the Statistics
object, stores the following query plan cache metrics:
getPlanCacheHitCount
-
The number of query plans successfully fetched from the cache.
getQueryPlanCacheMissCount
-
The number of query plans not fetched from the cache.
getPlanCompilationTotalMicroseconds
-
The overall time spent to compile the plan for this particular query.
24. Configurations
24.1. Strategy configurations
Many configuration settings define pluggable strategies that Hibernate uses for various purposes. The configurations of many of these strategy type settings accept definition in various forms. The documentation of such configuration settings refers here. The types of forms available in such cases include:
- short name (if defined)
-
Certain built-in strategy implementations have a corresponding short name
- strategy instance
-
An instance of the strategy implementation to use can be specified
- strategy Class reference
-
A
java.lang.Class
reference of the strategy implementation to use - strategy Class name
-
The class name (
java.lang.String
) of the strategy implementation to use
24.2. General configuration
hibernate.dialect
(e.g.org.hibernate.dialect.PostgreSQL94Dialect
)-
The class name of a Hibernate
Dialect
from which Hibernate can generate SQL optimized for a particular relational database.In most cases, Hibernate can choose the correct
Dialect
implementation based on the JDBC metadata returned by the JDBC driver. hibernate.current_session_context_class
(e.g.jta
,thread
,managed
, or a custom class implementingorg.hibernate.context.spi.CurrentSessionContext
)-
Supplies a custom strategy for the scoping of the current
Session
.The definition of what exactly current means is controlled by the
CurrentSessionContext
implementation in use.Note that for backward compatibility, if a
CurrentSessionContext
is not configured but JTA is configured this will default to theJTASessionContext
.
24.3. JPA compliance
hibernate.jpa.compliance.transaction
(e.g.true
orfalse
(default value))-
This setting controls if Hibernate
Transaction
should behave as defined by the spec for JPA’sjavax.persistence.EntityTransaction
since it extends the JPA one. hibernate.jpa.compliance.query
(e.g.true
orfalse
(default value))-
Controls whether Hibernate’s handling of
javax.persistence.Query
(JPQL, Criteria and native query) should strictly follow the JPA spec.This includes both in terms of parsing or translating a query as well as calls to the
javax.persistence.Query
methods throwing spec defined exceptions whereas Hibernate might not. hibernate.jpa.compliance.list
(e.g.true
orfalse
(default value))-
Controls whether Hibernate should recognize what it considers a "bag" (
org.hibernate.collection.internal.PersistentBag
) as a List (org.hibernate.collection.internal.PersistentList
) or as a bag.If enabled, we will recognize it as a List where
javax.persistence.OrderColumn
is just missing (and its defaults will apply). hibernate.jpa.compliance.closed
(e.g.true
orfalse
(default value))-
JPA defines specific exceptions upon calling specific methods on
javax.persistence.EntityManager
andjavax.persistence.EntityManagerFactory
objects which have been closed previously.This setting controls whether the JPA spec-defined behavior or the Hibernate behavior will be used.
If enabled, Hibernate will operate in the JPA specified way, throwing exceptions when the spec says it should.
hibernate.jpa.compliance.proxy
(e.g.true
orfalse
(default value))-
The JPA spec says that a
javax.persistence.EntityNotFoundException
should be thrown when accessing an entity proxy which does not have an associated table row in the database.Traditionally, Hibernate does not initialize an entity proxy when accessing its identifier since we already know the identifier value, hence we can save a database roundtrip.
If enabled Hibernate will initialize the entity proxy even when accessing its identifier.
hibernate.jpa.compliance.global_id_generators
(e.g.true
orfalse
(default value) )-
The JPA spec says that the scope of TableGenerator and SequenceGenerator names is global to the persistence unit (across all generator types).
Traditionally, Hibernate has considered the names locally scoped.
If enabled, the names used by
@TableGenerator
and@SequenceGenerator
will be considered global so configuring two different generators with the same name will cause ajava.lang.IllegalArgumentException
to be thrown at boot time.
24.4. Database connection properties
hibernate.connection.driver_class
orjavax.persistence.jdbc.driver
(e.g.org.postgresql.Driver
)-
Names the JDBC
Driver
class name. hibernate.connection.url
orjavax.persistence.jdbc.url
(e.g.jdbc:postgresql:hibernate_orm_test
)-
Names the JDBC connection URL.
hibernate.connection.username
orjavax.persistence.jdbc.user
-
Names the JDBC connection user name.
hibernate.connection.password
orjavax.persistence.jdbc.password
-
Names the JDBC connection password.
hibernate.connection.isolation
(e.g.REPEATABLE_READ
orConnection.TRANSACTION_REPEATABLE_READ
)-
Names the JDBC connection transaction isolation level.
hibernate.connection.autocommit
(e.g.true
orfalse
(default value))-
Names the initial autocommit mode for JDBC Connections returned from a connection pool created in certain ConnectionProvider impl.
See discussion of
hibernate.connection.provider_disables_autocommit
as well. hibernate.connection.provider_disables_autocommit
(e.g.true
orfalse
(default value))-
Indicates a promise by the user that Connections that Hibernate obtains from the configured ConnectionProvider have auto-commit disabled when they are obtained from that provider, whether that provider is backed by a DataSource or some other Connection pooling mechanism. Generally, this occurs when:
-
Hibernate is configured to get Connections from an underlying DataSource, and that DataSource is already configured to disable auto-commit on its managed Connections.
-
Hibernate is configured to get Connections from a non-DataSource connection pool and that connection pool is already configured to disable auto-commit. For the Hibernate provided implementation this will depend on the value of
hibernate.connection.autocommit
setting.Hibernate uses this assurance as an opportunity to opt out of certain operations that may have a performance impact (although this impact is generally negligible). Specifically, when a transaction is started via the Hibernate or JPA transaction APIs Hibernate will generally immediately acquire a Connection from the provider and:
-
check whether the Connection is initially in auto-commit mode via a call to
Connection#getAutocommit
to know how to clean up the Connection when released. -
start a JDBC transaction by calling
Connection#setAutocommit(false)
.We can skip both of those steps if we know that the ConnectionProvider will always return Connections with auto-commit disabled. That is the purpose of this setting. By setting it to
true
, theConnection
acquisition can be delayed until the first SQL statement is needed to be executed. The connection acquisition delay allows you to reduce the database connection lease time, therefore allowing you to increase the transaction throughput.It is inappropriate to set this value to
true
when the Connections Hibernate gets from the provider do not, in fact, have auto-commit disabled.Doing so will lead to Hibernate executing SQL operations outside of any JDBC/SQL transaction.
-
hibernate.connection.handling_mode
-
Specifies how Hibernate should manage JDBC connections in terms of acquiring and releasing. This configuration property supersedes
hibernate.connection.acquisition_mode
andhibernate.connection.release_mode
.The connection handling mode strategies are defined by the
PhysicalConnectionHandlingMode
enumeration.The configuration can be either a
PhysicalConnectionHandlingMode
reference or its case-insensitiveString
representation.For more details about the
PhysicalConnectionHandlingMode
and Hibernate connection handling, check out the Connection handling section. hibernate.connection.acquisition_mode
(e.g.immediate
)
This setting is deprecated. You should use the |
Specifies how Hibernate should acquire JDBC connections. The possible values are given by org.hibernate.ConnectionAcquisitionMode
.
Should generally only configure this or hibernate.connection.release_mode
, not both.
hibernate.connection.release_mode
(e.g.auto
(default value))
This setting is deprecated. You should use the |
Specifies how Hibernate should release JDBC connections. The possible values are given by the current transaction mode (after_transaction
for JDBC transactions and after_statement
for JTA transactions).
Should generally only configure this or hibernate.connection.acquisition_mode
, not both.
hibernate.connection.datasource
-
Either a
javax.sql.DataSource
instance or a JNDI name under which to locate theDataSource
.For JNDI names, ses also
hibernate.jndi.class
,hibernate.jndi.url
,hibernate.jndi
. hibernate.connection
-
Names a prefix used to define arbitrary JDBC connection properties. These properties are passed along to the JDBC provider when creating a connection.
hibernate.connection.provider_class
(e.g.org.hibernate.hikaricp.internal. HikariCPConnectionProvider
)-
Names the
ConnectionProvider
to use for obtaining JDBC connections.Can reference:
-
an instance of
ConnectionProvider
-
a
Class<? extends ConnectionProvider>
object reference -
a fully qualified name of a class implementing
ConnectionProvider
The term
class
appears in the setting name due to legacy reasons. However, it can accept instances.
-
hibernate.jndi.class
-
Names the JNDI
javax.naming.InitialContext
class. hibernate.jndi.url
(e.g.java:global/jdbc/default
)-
Names the JNDI provider/connection url.
hibernate.jndi
-
Names a prefix used to define arbitrary JNDI
javax.naming.InitialContext
properties.These properties are passed along to
javax.naming.InitialContext#InitialContext(java.util.Hashtable)
method.
24.4.1. Hibernate internal connection pool options
hibernate.connection.initial_pool_size
(e.g. 1 (default value))-
Minimum number of connections for the built-in Hibernate connection pool.
hibernate.connection.pool_size
(e.g. 20 (default value))-
Maximum number of connections for the built-in Hibernate connection pool.
hibernate.connection.pool_validation_interval
(e.g. 30 (default value))-
The number of seconds between two consecutive pool validations. During validation, the pool size can increase or decrease based on the connection acquisition request count.
24.5. c3p0 properties
hibernate.c3p0.min_size
(e.g. 1)-
Minimum size of C3P0 connection pool. Refers to c3p0
minPoolSize
setting. hibernate.c3p0.max_size
(e.g. 5)-
Maximum size of C3P0 connection pool. Refers to c3p0
maxPoolSize
setting. hibernate.c3p0.timeout
(e.g. 30)-
Maximum idle time for C3P0 connection pool. Refers to c3p0
maxIdleTime
setting. hibernate.c3p0.max_statements
(e.g. 5)-
Maximum size of C3P0 statement cache. Refers to c3p0
maxStatements
setting. hibernate.c3p0.acquire_increment
(e.g. 2)-
The number of connections acquired at a time when there’s no connection available in the pool. Refers to c3p0
acquireIncrement
setting. hibernate.c3p0.idle_test_period
(e.g. 5)-
Idle time before a C3P0 pooled connection is validated. Refers to c3p0
idleConnectionTestPeriod
setting. hibernate.c3p0
-
A setting prefix used to indicate additional c3p0 properties that need to be passed to the underlying c3p0 connection pool.
24.6. Mapping Properties
24.6.1. Table qualifying options
hibernate.default_catalog
(e.g. A catalog name)-
Qualifies unqualified table names with the given catalog in generated SQL.
hibernate.default_schema
(e.g. A schema name)-
Qualify unqualified table names with the given schema or tablespace in generated SQL.
hibernate.schema_name_resolver
(e.g. The fully qualified name of anorg.hibernate.engine.jdbc.env.spi.SchemaNameResolver
implementation class)-
By default, Hibernate uses the
org.hibernate.dialect.Dialect#getSchemaNameResolver
. You can customize how the schema name is resolved by providing a custom implementation of theSchemaNameResolver
interface.
24.6.2. Identifier options
hibernate.id.new_generator_mappings
(e.g.true
(default value) orfalse
)-
Setting which indicates whether or not the new
IdentifierGenerator
are used forAUTO
,TABLE
andSEQUENCE
.Existing applications may want to disable this (set it
false
) for upgrade compatibility from 3.x and 4.x to 5.x. hibernate.use_identifier_rollback
(e.g.true
orfalse
(default value))-
If true, generated identifier properties are reset to default values when objects are deleted.
hibernate.id.optimizer.pooled.preferred
(e.g.none
,hilo
,legacy-hilo
,pooled
(default value),pooled-lo
,pooled-lotl
or a fully-qualified name of theOptimizer
implementation)-
When a generator specified an increment-size and an optimizer was not explicitly specified, which of the pooled optimizers should be preferred?
hibernate.id.generator.stored_last_used
(e.g.true
(default value) orfalse
)-
If true, the value stored in the table used by the
@TableGenerator
is the last value used, if false the value is the next value to be used. hibernate.model.generator_name_as_sequence_name
(e.g.true
(default value) orfalse
)-
If true, the value specified by the
generator
attribute of the@GeneratedValue
annotation should be used as the sequence/table name when no matching@SequenceGenerator
orTableGenerator
is found.The default value is
true
meaning that@GeneratedValue.generator()
will be used as the sequence/table name by default. Users migrating from earlier versions using the legacyhibernate_sequence
name should disable this setting. hibernate.ejb.identifier_generator_strategy_provider
(e.g. fully-qualified class name or an actualIdentifierGeneratorStrategyProvider
instance)-
This setting allows you to provide an instance or the class implementing the
org.hibernate.jpa.spi.IdentifierGeneratorStrategyProvider
interface, so you can provide a set ofIdentifierGenerator
strategies allowing to override the Hibernate Core default ones. hibernate.id.disable_delayed_identity_inserts
(e.g.true
orfalse
(default value))-
If true, inserts that use generated-identifiers (identity/sequences) will never be delayed and will always be inserted immediately. This should be used if you run into any errors with
DelayedPostInsertIdentifier
and should be considered a temporary fix. Please report your mapping that causes the problem to us so we can examine the default algorithm to see if your use case should be included.The default value is
false
which means Hibernate will use an algorithm to determine if the insert can be delayed or if the insert should be performed immediately. hibernate.id.sequence.increment_size_mismatch_strategy
(e.g.LOG
,FIX
,NONE
orEXCEPTION
(default value))-
This setting defines the
org.hibernate.id.SequenceMismatchStrategy
used when Hibernate detects a mismatch between a sequence configuration in an entity mapping and its database sequence object counterpart.The default value is given by the
org.hibernate.id.SequenceMismatchStrategy#EXCEPTION
, meaning that an Exception is thrown when detecting such a conflict.
24.6.3. Quoting options
hibernate.globally_quoted_identifiers
(e.g.true
orfalse
(default value))-
Should all database identifiers be quoted.
hibernate.globally_quoted_identifiers_skip_column_definitions
(e.g.true
orfalse
(default value))-
Assuming
hibernate.globally_quoted_identifiers
istrue
, this allows the global quoting to skip column-definitions as defined byjavax.persistence.Column
,javax.persistence.JoinColumn
, etc., and while it avoids column-definitions being quoted due to global quoting, they can still be explicitly quoted in the annotation/xml mappings. hibernate.auto_quote_keyword
(e.g.true
orfalse
(default value))-
Specifies whether to automatically quote any names that are deemed keywords.
24.6.4. Discriminator options
hibernate.discriminator.implicit_for_joined
(e.g.true
orfalse
(default value))-
The legacy behavior of Hibernate is to not use discriminators for joined inheritance (Hibernate does not need the discriminator). However, some JPA providers do need the discriminator for handling joined inheritance so, in the interest of portability, this capability has been added to Hibernate too.
Because want to make sure that legacy applications continue to work as well, that puts us in a bind in terms of how to handle implicit discriminator mappings. The solution is to assume that the absence of discriminator metadata means to follow the legacy behavior unless this setting is enabled.
With this setting enabled, Hibernate will interpret the absence of discriminator metadata as an indication to use the JPA-defined defaults for these absent annotations.
See Hibernate Jira issue HHH-6911 for additional background info.
hibernate.discriminator.ignore_explicit_for_joined
(e.g.true
orfalse
(default value))-
The legacy behavior of Hibernate is to not use discriminators for joined inheritance (Hibernate does not need the discriminator). However, some JPA providers do need the discriminator for handling joined inheritance so, in the interest of portability, this capability has been added to Hibernate too.
Existing applications rely (implicitly or explicitly) on Hibernate ignoring any
DiscriminatorColumn
declarations on joined inheritance hierarchies. This setting allows these applications to maintain the legacy behavior ofDiscriminatorColumn
annotations being ignored when paired with joined inheritance.See Hibernate Jira issue HHH-6911 for additional background info.
24.6.5. Naming strategies
hibernate.implicit_naming_strategy
(e.g.default
(default value),jpa
,legacy-jpa
,legacy-hbm
,component-path
)-
Used to specify the
ImplicitNamingStrategy
class to use. The following short names are defined for this setting:default
jpa
legacy-jpa
-
Uses the
ImplicitNamingStrategyLegacyJpaImpl
legacy-hbm
-
Uses the
ImplicitNamingStrategyLegacyHbmImpl
component-path
-
Uses the
ImplicitNamingStrategyComponentPathImpl
If this property happens to be empty, the fallback is to use the
default
strategy.
hibernate.physical_naming_strategy
(e.g.org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
(default value))-
Used to specify the
PhysicalNamingStrategy
class to use.
24.6.6. Metadata scanning options
hibernate.archive.scanner
-
Pass an implementation of
Scanner
. By default,StandardScanner
is used.Accepts:
-
an actual
Scanner
instance -
a reference to a Class that implements
Scanner
-
a fully qualified name of a Class that implements
Scanner
-
hibernate.archive.interpreter
-
Pass
ArchiveDescriptorFactory
to use in the scanning process.Accepts:
-
an actual
ArchiveDescriptorFactory
instance -
a reference to a Class that implements
ArchiveDescriptorFactory
-
a fully qualified name of a Class that implements
ArchiveDescriptorFactory
See information on
Scanner
about expected constructor forms.
-
hibernate.archive.autodetection
(e.g.hbm,class
(default value))-
Identifies a comma-separated list of values indicating the mapping types we should auto-detect during scanning.
Allowable values include:
class
-
scan classes (e.g.
.class
) to extract entity mapping metadata hbm
-
scan
hbm
mapping files (e.g.hbm.xml
) to extract entity mapping metadataBy default HBM, annotations, and JPA XML mappings are scanned.
When using JPA, to disable the automatic scanning of all entity classes, the
exclude-unlisted-classes
persistence.xml
element must be set to true. Therefore, when settingexclude-unlisted-classes
to true, only the classes that are explicitly declared in thepersistence.xml
configuration files are going to be taken into consideration.
hibernate.mapping.precedence
(e.g.hbm,class
(default value))-
Used to specify the order in which metadata sources should be processed. Value is a delimited-list whose elements are defined by
MetadataSourceType
.The default is
hbm,class
, thereforehbm.xml
files are processed first, followed by annotations (combined withorm.xml
mappings).When using JPA, the XML mapping overrides a conflicting annotation mapping that targets the same entity attribute.
24.6.7. JDBC-related options
hibernate.use_nationalized_character_data
(e.g.true
orfalse
(default value))-
Enable nationalized character support on all string / clob based attribute ( string, char, clob, text, etc. ).
hibernate.jdbc.lob.non_contextual_creation
(e.g.true
orfalse
(default value))-
Should we not use contextual LOB creation (aka based on
java.sql.Connection#createBlob()
et al)? The default value for HANA, H2, and PostgreSQL istrue
. hibernate.jdbc.time_zone
(e.g. Ajava.util.TimeZone
, ajava.time.ZoneId
or aString
representation of aZoneId
)-
Unless specified, the JDBC Driver uses the default JVM time zone. If a different time zone is configured via this setting, the JDBC PreparedStatement#setTimestamp is going to use a
Calendar
instance according to the specified time zone. hibernate.dialect.oracle.prefer_long_raw
(e.g.true
orfalse
(default value))-
This setting applies to Oracle Dialect only, and it specifies whether
byte[]
orByte[]
arrays should be mapped to the deprecatedLONG RAW
(when this configuration property value istrue
) or to aBLOB
column type (when this configuration property value isfalse
).
24.6.8. Bean Validation options
javax.persistence.validation.factory
(e.g.javax.validation.ValidationFactory
implementation)-
Specify the
javax.validation.ValidationFactory
implementation to use for Bean Validation. hibernate.check_nullability
(e.g.true
orfalse
)-
Enable nullability checking. Raises an exception if a property marked as not-null is null.
Default to
false
if Bean Validation is present in the classpath and Hibernate Annotations is used,true
otherwise. hibernate.validator.apply_to_ddl
(e.g.true
(default value) orfalse
)-
Bean Validation constraints will be applied in DDL if the automatic schema generation is enabled. In other words, the database schema will reflect the Bean Validation constraints.
To disable constraint propagation to DDL, set up
hibernate.validator.apply_to_ddl
tofalse
in the configuration file. Such a need is very uncommon and not recommended.
24.6.9. Misc options
hibernate.create_empty_composites.enabled
(e.g.true
orfalse
(default value))-
Enable instantiation of composite/embeddable objects when all of its attribute values are
null
. The default (and historical) behavior is that anull
reference will be used to represent the composite when all of its attributes arenull
s.This is an experimental feature that has known issues. It should not be used in production until it is stabilized. See Hibernate Jira issue HHH-11936 for details.
hibernate.entity_dirtiness_strategy
(e.g. fully-qualified class name or an actualCustomEntityDirtinessStrategy
instance)-
Setting to identify an
org.hibernate.CustomEntityDirtinessStrategy
to use. hibernate.default_entity_mode
(e.g.pojo
(default value) ordynamic-map
)-
Default
EntityMode
for entity representation for all sessions opened from thisSessionFactory
, defaults topojo
.
24.7. Bytecode Enhancement Properties
hibernate.enhancer.enableDirtyTracking
(e.g.true
orfalse
(default value))-
Enable dirty tracking feature in runtime bytecode enhancement.
hibernate.enhancer.enableLazyInitialization
(e.g.true
orfalse
(default value))-
Enable lazy loading feature in runtime bytecode enhancement. This way, even basic types (e.g.
@Basic(fetch = FetchType.LAZY
)) can be fetched lazily. hibernate.enhancer.enableAssociationManagement
(e.g.true
orfalse
(default value))-
Enable association management feature in runtime bytecode enhancement which automatically synchronizes a bidirectional association when only one side is changed.
hibernate.bytecode.provider
(e.g.bytebuddy
(default value))-
The
BytecodeProvider
built-in implementation flavor. Currently, onlybytebuddy
andjavassist
are valid values;bytebuddy
is the default and recommended choice;javassist
will be removed soon. hibernate.bytecode.use_reflection_optimizer
(e.g.true
orfalse
(default value))-
Should we use reflection optimization? The reflection optimizer implements the
ReflectionOptimizer
interface and improves entity instantiation and property getter/setter calls. hibernate.bytecode.enforce_legacy_proxy_classnames
(e.g.true
orfalse
(default value))-
Some other libraries, such as Spring, used to depend on a specific naming pattern used for proxy classes generated at runtime. Set this to
true
to have proxy class names conform to the old pattern.
24.8. Query settings
hibernate.query.plan_cache_max_size
(e.g.2048
(default value))-
The maximum number of entries including:
HQLQueryPlan
,FilterQueryPlan
,NativeSQLQueryPlan
.Maintained by
QueryPlanCache
. hibernate.query.plan_parameter_metadata_max_size
(e.g.128
(default value))-
The maximum number of strong references associated with
ParameterMetadata
maintained byQueryPlanCache
. hibernate.order_by.default_null_ordering
(e.g.none
,first
orlast
)-
Defines precedence of null values in
ORDER BY
clause. Defaults tonone
which varies between RDBMS implementation. hibernate.discriminator.force_in_select
(e.g.true
orfalse
(default value))-
For entities which do not explicitly say, should we force discriminators into SQL selects?
hibernate.query.substitutions
(e.g.true=1,false=0
)-
A comma-separated list of token substitutions to use when translating a Hibernate query to SQL.
hibernate.query.factory_class
(e.g.org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory
(default value) ororg.hibernate.hql.internal.classic.ClassicQueryTranslatorFactory
)-
Chooses the HQL parser implementation.
hibernate.query.jpaql_strict_compliance
(e.g.true
orfalse
(default value))-
Map from tokens in Hibernate queries to SQL tokens, such as function or literal names.
Should we strictly adhere to JPA Query Language (JPQL) syntax, or more broadly support all of Hibernate’s superset (HQL)?
Setting this to
true
may cause valid HQL to throw an exception because it violates the JPQL subset. hibernate.query.startup_check
(e.g.true
(default value) orfalse
)-
Should named queries be checked during startup?
hibernate.proc.param_null_passing
(e.g.true
orfalse
(default value))-
Global setting for whether
null
parameter bindings should be passed to database procedure/function calls as part ofProcedureCall
handling. Implicitly Hibernate will not pass thenull
, the intention being to allow any default argument values to be applied.This defines a global setting, which can then be controlled per parameter via
org.hibernate.procedure.ParameterRegistration#enablePassingNulls(boolean)
.Values are
true
(pass the NULLs) orfalse
(do not pass the NULLs). hibernate.jdbc.log.warnings
(e.g.true
orfalse
)-
Enable fetching JDBC statement warning for logging. Default value is given by
org.hibernate.dialect.Dialect#isJdbcLogWarningsEnabledByDefault()
. hibernate.session_factory.statement_inspector
(e.g. A fully-qualified class name, an instance, or aClass
object reference)-
Names a
StatementInspector
implementation to be applied to everySession
created by the currentSessionFactory
.Can reference a
StatementInspector
instance,StatementInspector
implementationClass
reference orStatementInspector
implementation class name (fully-qualified class name). hibernate.query.validate_parameters
(e.g.true
(default value) orfalse
)-
This configuration property can be used to disable parameters validation performed by
org.hibernate.query.Query#setParameter
when the Session is bootstrapped via JPAjavax.persistence.EntityManagerFactory
. hibernate.criteria.literal_handling_mode
(e.g.AUTO
(default value),BIND
orINLINE
)-
By default, Criteria queries use bind parameters for any literal that is not a numeric value. However, to increase the likelihood of JDBC statement caching, you might want to use bind parameters for numeric values too.
The
org.hibernate.query.criteria.LiteralHandlingMode#BIND
mode will use bind variables for any literal value. Theorg.hibernate.query.criteria.LiteralHandlingMode#INLINE
mode will inline literal values as is.To prevent SQL injection, never use
org.hibernate.query.criteria.LiteralHandlingMode#INLINE
with String variables. Always use constants with theorg.hibernate.query.criteria.LiteralHandlingMode#INLINE
mode.Valid options are defined by the
org.hibernate.query.criteria.LiteralHandlingMode
enum. The default value isorg.hibernate.query.criteria.LiteralHandlingMode#AUTO
. hibernate.query.fail_on_pagination_over_collection_fetch
(e.g.true
orfalse
(default value))-
Raises an exception when in-memory pagination over collection fetch is about to be performed.
Disabled by default. Set to true to enable.
hibernate.query.immutable_entity_update_query_handling_mode
(e.g.EXCEPTION
orWARNING
(default value))-
Defines how
Immutable
entities are handled when executing a bulk update query.By default, the (
ImmutableEntityUpdateQueryHandlingMode#WARNING
) mode is used, meaning that a warning log message is issued when an@Immutable
entity is to be updated via a bulk update statement.If the (
ImmutableEntityUpdateQueryHandlingMode#EXCEPTION
) mode is used, then aHibernateException
is thrown instead. hibernate.query.in_clause_parameter_padding
(e.g.true
orfalse
(default value))-
By default, the IN clause expands to include all bind parameter values.
However, for database systems supporting execution plan caching, there’s a better chance of hitting the cache if the number of possible IN clause parameters lowers.
For this reason, we can expand the bind parameters to power-of-two: 4, 8, 16, 32, 64. This way, an IN clause with 5, 6, or 7 bind parameters will use the 8 IN clause, therefore reusing its execution plan.
hibernate.query.omit_join_of_superclass_tables
(e.g.false
ortrue
(default value))-
When you use
javax.persistence.InheritanceType#JOINED
strategy for inheritance mapping and query a value from an entity, all superclass tables are joined in the query regardless you need them.With this setting set to true only superclass tables which are really needed are joined.
24.8.1. Multi-table bulk HQL operations
hibernate.hql.bulk_id_strategy
(e.g. A fully-qualified class name, an instance, or aClass
object reference)-
Provide a custom
org.hibernate.hql.spi.id.MultiTableBulkIdStrategy
implementation for handling multi-table bulk HQL operations. hibernate.hql.bulk_id_strategy.global_temporary.drop_tables
(e.g.true
orfalse
(default value))-
For databases that don’t support local tables, but just global ones, this configuration property allows you to DROP the global tables used for multi-table bulk HQL operations when the
SessionFactory
or theEntityManagerFactory
is closed. hibernate.hql.bulk_id_strategy.local_temporary.drop_tables
(e.g.true
orfalse
(default value))-
This configuration property allows you to DROP the local temporary tables used for multi-table bulk HQL operations when the
SessionFactory
or theEntityManagerFactory
is closed. This is useful when testing with a single connection pool against different schemas. hibernate.hql.bulk_id_strategy.persistent.drop_tables
(e.g.true
orfalse
(default value))-
This configuration property is used by the
PersistentTableBulkIdStrategy
, that mimics temporary tables for databases which do not support temporary tables. It follows a pattern similar to the ANSI SQL definition of the global temporary table using a "session id" column to segment rows from the various sessions.This configuration property allows you to DROP the tables used for multi-table bulk HQL operations when the
SessionFactory
or theEntityManagerFactory
is closed. hibernate.hql.bulk_id_strategy.persistent.schema
(e.g. Database schema name. By default, thehibernate.default_schema
is used.)-
This configuration property is used by the
PersistentTableBulkIdStrategy
, that mimics temporary tables for databases which do not support temporary tables. It follows a pattern similar to the ANSI SQL definition of the global temporary table using a "session id" column to segment rows from the various sessions.This configuration property defines the database schema used for storing the temporary tables used for bulk HQL operations.
hibernate.hql.bulk_id_strategy.persistent.catalog
(e.g. Database catalog name. By default, thehibernate.default_catalog
is used.)-
This configuration property is used by the
PersistentTableBulkIdStrategy
, that mimics temporary tables for databases which do not support temporary tables. It follows a pattern similar to the ANSI SQL definition of the global temporary table using a "session id" column to segment rows from the various sessions.This configuration property defines the database catalog used for storing the temporary tables used for bulk HQL operations.
hibernate.legacy_limit_handler
(e.g.true
orfalse
(default value))-
Setting which indicates whether or not to use
org.hibernate.dialect.pagination.LimitHandler
implementations that sacrifices performance optimizations to allow legacy 4.x limit behavior.Legacy 4.x behavior favored performing pagination in-memory by avoiding the use of the offset value, which is overall poor performance. In 5.x, the limit handler behavior favors performance, thus, if the dialect doesn’t support offsets, an exception is thrown instead.
hibernate.query.conventional_java_constants
(e.g.true
(default value) orfalse
)-
Setting which indicates whether or not Java constants follow the Java Naming conventions.
The default is
true
. Existing applications may want to disable this (set itfalse
) if non-conventional Java constants are used. However, there is a significant performance overhead for using non-conventional Java constants since Hibernate cannot determine if aliases should be treated as Java constants or not.Check out HHH-4959 for more details.
24.9. Batching properties
hibernate.jdbc.batch_size
(e.g. 5)-
Maximum JDBC batch size. A nonzero value enables batch updates.
hibernate.order_inserts
(e.g.true
orfalse
(default value))-
Forces Hibernate to order SQL inserts by the primary key value of the items being inserted. This preserves batching when using cascading.
hibernate.order_updates
(e.g.true
orfalse
(default value))-
Forces Hibernate to order SQL updates by the primary key value of the items being updated. This preserves batching when using cascading and reduces the likelihood of transaction deadlocks in highly-concurrent systems.
hibernate.jdbc.batch_versioned_data
(e.g.true
(default value) orfalse
)-
Should versioned entities be included in batching?
Set this property to
true
if your JDBC driver returns correct row counts from executeBatch(). This option is usually safe, but is disabled by default. If enabled, Hibernate uses batched DML for automatically versioned data. hibernate.batch_fetch_style
(e.g.LEGACY
(default value))-
Names the
BatchFetchStyle
to use.Can specify either the
BatchFetchStyle
name (case insensitively), or aBatchFetchStyle
instance.LEGACY
is the default value. hibernate.jdbc.batch.builder
(e.g. the fully qualified name of aBatchBuilder
implementation class type or an actual object instance)-
Names the
BatchBuilder
implementation to use.
24.9.1. Fetching properties
hibernate.max_fetch_depth
(e.g. a value between0
and3
)-
Sets a maximum depth for the outer join fetch tree for single-ended associations. A single-ended association is a one-to-one or many-to-one association. A value of
0
disables default outer join fetching. hibernate.default_batch_fetch_size
(e.g.4
,8
, or16
)-
The default size for Hibernate Batch fetching of associations (lazily fetched associations can be fetched in batches to prevent N+1 query problems).
hibernate.jdbc.fetch_size
(e.g.0
or an integer)-
A non-zero value determines the JDBC fetch size, by calling
Statement.setFetchSize()
. hibernate.jdbc.use_scrollable_resultset
(e.g.true
orfalse
)-
Enables Hibernate to use JDBC2 scrollable resultsets. This property is only relevant for user-supplied JDBC connections. Otherwise, Hibernate uses connection metadata.
hibernate.jdbc.use_streams_for_binary
(e.g.true
orfalse
(default value))-
Use streams when writing or reading
binary
orserializable
types to or from JDBC. This is a system-level property. hibernate.jdbc.use_get_generated_keys
(e.g.true
orfalse
)-
Allows Hibernate to use JDBC3
PreparedStatement.getGeneratedKeys()
to retrieve natively-generated keys after insert. You need the JDBC3+ driver and JRE1.4+. Disable this property if your driver has problems with the Hibernate identifier generators. By default, it tries to detect the driver capabilities from connection metadata. hibernate.jdbc.wrap_result_sets
(e.g.true
orfalse
(default value))-
Enable wrapping of JDBC result sets in order to speed up column name lookups for broken JDBC drivers.
hibernate.enable_lazy_load_no_trans
(e.g.true
orfalse
(default value))-
Initialize Lazy Proxies or Collections outside a given Transactional Persistence Context.
Although enabling this configuration can make
LazyInitializationException
go away, it’s better to use a fetch plan that guarantees that all properties are properly initialized before the Session is closed.In reality, you shouldn’t probably enable this setting anyway.
24.10. Statement logging and statistics
24.10.1. SQL statement logging
hibernate.show_sql
(e.g.true
orfalse
(default value))-
Write all SQL statements to the console. This is an alternative to setting the log category
org.hibernate.SQL
to debug. hibernate.format_sql
(e.g.true
orfalse
(default value))-
Pretty-print the SQL in the log and console.
hibernate.highlight_sql
(e.g.true
orfalse
(default value))-
Colorize the SQL in the console using ANSI escape codes.
hibernate.use_sql_comments
(e.g.true
orfalse
(default value))-
If true, Hibernate generates comments inside the SQL, for easier debugging.
24.10.2. Statistics settings
hibernate.generate_statistics
(e.g.true
orfalse
)-
Causes Hibernate to collect statistics for performance tuning.
hibernate.stats.factory
(e.g. the fully qualified name of aStatisticsFactory
implementation or an actual instance)-
The
StatisticsFactory
allow you to customize how the Hibernate Statistics are being collected. hibernate.session.events.log
(e.g.true
orfalse
)-
A setting to control whether the
org.hibernate.engine.internal.StatisticalLoggingSessionEventListener
is enabled on allSessions
(unless explicitly disabled for a givenSession
).The default value of this setting is determined by the value for
hibernate.generate_statistics
, meaning that if statistics are enabled, then logging of Session metrics is enabled by default too.
24.11. Cache Properties
hibernate.cache.region.factory_class
(e.g.jcache
)-
Either a shortcut name (e.g.
jcache
,ehcache
) or the fully-qualified name of theRegionFactory
implementation class. hibernate.cache.default_cache_concurrency_strategy
-
Setting used to give the name of the default
CacheConcurrencyStrategy
to use when@javax.persistence.Cacheable
,@org.hibernate.annotations.Cache
or@org.hibernate.annotations.Cache
is used to override the global setting. hibernate.cache.use_minimal_puts
(e.g.true
(default value) orfalse
)-
Optimizes second-level cache operation to minimize writes, at the cost of more frequent reads. This is most useful for clustered caches and is enabled by default for clustered cache implementations.
hibernate.cache.use_query_cache
(e.g.true
orfalse
(default value))-
Enables the query cache. You still need to set individual queries to be cachable.
hibernate.cache.use_second_level_cache
(e.g.true
(default value) orfalse
)-
Enable/disable the second-level cache, which is enabled by default, although the default
RegionFactor
isNoCachingRegionFactory
(meaning there is no actual caching implementation). hibernate.cache.query_cache_factory
(e.g. fully-qualified class name)-
A custom
TimestampsCacheFactory
interface. The default is the built-inStandardTimestampsCacheFactory
. hibernate.cache.region_prefix
(e.g. A string)-
A prefix for second-level cache region names.
hibernate.cache.use_structured_entries
(e.g.true
orfalse
(default value))-
Forces Hibernate to store data in the second-level cache in a more human-readable format.
hibernate.cache.auto_evict_collection_cache
(e.g.true
orfalse
(default: false))-
Enables the automatic eviction of a bi-directional association’s collection cache when an element in the
ManyToOne
collection is added/updated/removed without properly managing the change on theOneToMany
side. hibernate.cache.use_reference_entries
(e.g.true
orfalse
)-
Optimizes second-level cache operation to store immutable entities (aka "reference") which do not have associations into cache directly. In this case, disassembling and deep copy operations can be avoided. The default value of this property is
false
. hibernate.ejb.classcache
(e.g.hibernate.ejb.classcache.org.hibernate.ejb.test.Item
=read-write
)-
Sets the associated entity class cache concurrency strategy for the designated region. Caching configuration should follow the following pattern
hibernate.ejb.classcache.<fully.qualified.Classname> = usage[, region]
where usage is the cache strategy used and region the cache region name. hibernate.ejb.collectioncache
(e.g.hibernate.ejb.collectioncache.org.hibernate.ejb.test.Item.distributors
=read-write, RegionName
)-
Sets the associated collection cache concurrency strategy for the designated region. Caching configuration should follow the following pattern
hibernate.ejb.collectioncache.<fully.qualified.Classname>.<role> = usage[, region]
where usage is the cache strategy used and region the cache region name.
24.12. Infinispan properties
For more details about how to customize the Infinispan second-level cache provider, check out the Infinispan User Guide.
24.13. Transactions properties
hibernate.transaction.jta.platform
(e.g.JBossAS
,BitronixJtaPlatform
)-
Names the
JtaPlatform
implementation to use for integrating with JTA systems. Can reference either aJtaPlatform
instance or the name of theJtaPlatform
implementation class. hibernate.jta.prefer_user_transaction
(e.g.true
orfalse
(default value))-
Should we prefer using the
org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform#retrieveUserTransaction
over usingorg.hibernate.engine.transaction.jta.platform.spi.JtaPlatform#retrieveTransactionManager
? hibernate.transaction.jta.platform_resolver
-
Names the
JtaPlatformResolver
implementation to use. hibernate.jta.cacheTransactionManager
(e.g.true
(default value) orfalse
)-
A configuration value key used to indicate that it is safe to cache.
hibernate.jta.cacheUserTransaction
(e.g.true
orfalse
(default value))-
A configuration value key used to indicate that it is safe to cache.
hibernate.transaction.flush_before_completion
(e.g.true
orfalse
(default value))-
Causes the session be flushed during the before completion phase of the transaction. If possible, use built-in and automatic session context management instead.
hibernate.transaction.auto_close_session
(e.g.true
orfalse
(default value))-
Causes the session to be closed during the after completion phase of the transaction. If possible, use built-in and automatic session context management instead.
hibernate.transaction.coordinator_class
-
Names the implementation of
TransactionCoordinatorBuilder
to use for creatingTransactionCoordinator
instances.Can be a
TransactionCoordinatorBuilder
instance,TransactionCoordinatorBuilder
implementationClass
reference, aTransactionCoordinatorBuilder
implementation class name (fully-qualified name) or a short name.The following short names are defined for this setting:
jdbc
-
Manages transactions via calls to
java.sql.Connection
(default for non-JPA applications). 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 usejdbc
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 sethibernate.transaction.coordinator_class=jta
or provide a customTransactionCoordinatorBuilder
that builds aTransactionCoordinator
that properly coordinates with JTA-based transactions.
hibernate.jta.track_by_thread
(e.g.true
(default value) orfalse
)-
A transaction can be rolled back by another thread ("tracking by thread") and not the original application. Examples of this include a JTA transaction timeout handled by a background reaper thread.
The ability to handle this situation requires checking the Thread ID every time Session is called, so enabling this can certainly have a performance impact.
hibernate.transaction.factory_class
-
This is a legacy setting that’s been deprecated and you should use the hibernate.transaction.jta.platform
instead. hibernate.jta.allowTransactionAccess
(e.g.true
(default value) orfalse
)-
It allows access to the underlying
org.hibernate.Transaction
even when using JTA since the JPA specification prohibits this behavior.If this configuration property is set to
true
, access is granted to the underlyingorg.hibernate.Transaction
. If it’s set tofalse
, you won’t be able to access theorg.hibernate.Transaction
.The default behavior is to allow access unless the
Session
is bootstrapped via JPA.
24.14. Multi-tenancy settings
hibernate.multiTenancy
(e.g.NONE
(default value),SCHEMA
,DATABASE
, andDISCRIMINATOR
(not implemented yet))-
The multi-tenancy strategy in use.
hibernate.multi_tenant_connection_provider
(e.g.true
orfalse
(default value))-
Names a
MultiTenantConnectionProvider
implementation to use. AsMultiTenantConnectionProvider
is also a service, can be configured directly through theStandardServiceRegistryBuilder
. hibernate.tenant_identifier_resolver
-
Names a
CurrentTenantIdentifierResolver
implementation to resolve the current tenant identifier so that callingSessionFactory#openSession()
would get aSession
that’s connected to the right tenant.Can be a
CurrentTenantIdentifierResolver
instance,CurrentTenantIdentifierResolver
implementationClass
object reference or aCurrentTenantIdentifierResolver
implementation class name. hibernate.multi_tenant.datasource.identifier_for_any
(e.g.true
orfalse
(default value))-
When the
hibernate.connection.datasource
property value is resolved to ajavax.naming.Context
object, this configuration property defines the JNDI name used to locate theDataSource
used for fetching the initialConnection
which is used to access the database metadata of the underlying database(s) (in situations where we do not have a tenant id, like startup processing).
24.15. Automatic schema generation
hibernate.hbm2ddl.auto
(e.g.none
(default value),create-only
,drop
,create
,create-drop
,validate
, andupdate
)-
Setting to perform
SchemaManagementTool
actions automatically as part of theSessionFactory
lifecycle. Valid options are defined by theexternalHbm2ddlName
value of theAction
enum:none
-
No action will be performed.
create-only
-
Database creation will be generated.
drop
-
Database dropping will be generated.
create
-
Database dropping will be generated followed by database creation.
create-drop
-
Drop the schema and recreate it on SessionFactory startup. Additionally, drop the schema on SessionFactory shutdown.
validate
-
Validate the database schema.
update
-
Update the database schema.
javax.persistence.schema-generation.database.action
(e.g.none
(default value),create-only
,drop
,create
,create-drop
,validate
, andupdate
)-
Setting to perform
SchemaManagementTool
actions automatically as part of theSessionFactory
lifecycle. Valid options are defined by theexternalJpaName
value of theAction
enum:none
-
No action will be performed.
create
-
Database creation will be generated.
drop
-
Database dropping will be generated.
drop-and-create
-
Database dropping will be generated followed by database creation.
javax.persistence.schema-generation.scripts.action
(e.g.none
(default value),create-only
,drop
,create
,create-drop
,validate
, andupdate
)-
Setting to perform
SchemaManagementTool
actions writing the commands into a DDL script file. Valid options are defined by theexternalJpaName
value of theAction
enum:none
-
No action will be performed.
create
-
Database creation will be generated.
drop
-
Database dropping will be generated.
drop-and-create
-
Database dropping will be generated followed by database creation.
javax.persistence.schema-generation-connection
-
Allows passing a specific
java.sql.Connection
instance to be used bySchemaManagementTool
. javax.persistence.database-product-name
-
Specifies the name of the database provider in cases where a Connection to the underlying database is not available (aka, mainly in generating scripts). In such cases, a value for this setting must be specified.
The value of this setting is expected to match the value returned by
java.sql.DatabaseMetaData#getDatabaseProductName()
for the target database.Additionally, specifying
javax.persistence.database-major-version
and/orjavax.persistence.database-minor-version
may be required to understand exactly how to generate the required schema commands. javax.persistence.database-major-version
-
Specifies the major version of the underlying database, as would be returned by
java.sql.DatabaseMetaData#getDatabaseMajorVersion
for the target database.This value is used to help more precisely determine how to perform schema generation tasks for the underlying database in cases where
javax.persistence.database-product-name
does not provide enough distinction. javax.persistence.database-minor-version
-
Specifies the minor version of the underlying database, as would be returned by
java.sql.DatabaseMetaData#getDatabaseMinorVersion
for the target database.This value is used to help more precisely determine how to perform schema generation tasks for the underlying database in cases where
javax.persistence.database-product-name
andjavax.persistence.database-major-version
does not provide enough distinction. javax.persistence.schema-generation.create-source
-
Specifies whether schema generation commands for schema creation are to be determined based on object/relational mapping metadata, DDL scripts, or a combination of the two. See
SourceType
for valid set of values.If no value is specified, a default is assumed as follows:
-
if source scripts are specified (per
javax.persistence.schema-generation.create-script-source
), thenscript
is assumed -
otherwise,
metadata
is assumed
-
javax.persistence.schema-generation.drop-source
-
Specifies whether schema generation commands for schema dropping are to be determined based on object/relational mapping metadata, DDL scripts, or a combination of the two. See
SourceType
for valid set of values.If no value is specified, a default is assumed as follows:
-
if source scripts are specified (per
javax.persistence.schema-generation.drop-script-source
), then thescript
option is assumed -
otherwise,
metadata
is assumed
-
javax.persistence.schema-generation.create-script-source
-
Specifies the
create
script file as either ajava.io.Reader
configured for reading of the DDL script file or a string designating a filejava.net.URL
for the DDL script.Hibernate historically also accepted
hibernate.hbm2ddl.import_files
for a similar purpose, butjavax.persistence.schema-generation.create-script-source
should be preferred overhibernate.hbm2ddl.import_files
. javax.persistence.schema-generation.drop-script-source
-
Specifies the
drop
script file as either ajava.io.Reader
configured for reading of the DDL script file or a string designating a filejava.net.URL
for the DDL script. javax.persistence.schema-generation.scripts.create-target
-
For cases where the
javax.persistence.schema-generation.scripts.action
value indicates that schema creation commands should be written to DDL script file,javax.persistence.schema-generation.scripts.create-target
specifies either ajava.io.Writer
configured for output of the DDL script or a string specifying the file URL for the DDL script. javax.persistence.schema-generation.scripts.drop-target
-
For cases where the
javax.persistence.schema-generation.scripts.action
value indicates that schema dropping commands should be written to DDL script file,javax.persistence.schema-generation.scripts.drop-target
specifies either ajava.io.Writer
configured for output of the DDL script or a string specifying the file URL for the DDL script. hibernate.hbm2ddl.schema-generation.script.append
(e.g.true
(default value) orfalse
)-
For cases where the
javax.persistence.schema-generation.scripts.action
value indicates that schema commands should be written to DDL script file,hibernate.hbm2ddl.schema-generation.script.append
specifies if schema commands should be appended to the end of the file rather than written at the beginning of the file. Values aretrue
for appending schema commands to the end of the file,false
for writing achema commands at the beginning of the file. javax.persistence.hibernate.hbm2ddl.import_files
(e.g.import.sql
(default value))-
Comma-separated names of the optional files containing SQL DML statements executed during the
SessionFactory
creation. File order matters, the statements of a given file are executed before the statements of the following one.These statements are only executed if the schema is created, meaning that
hibernate.hbm2ddl.auto
is set tocreate
,create-drop
, orupdate
.javax.persistence.schema-generation.create-script-source
/javax.persistence.schema-generation.drop-script-source
should be preferred. javax.persistence.sql-load-script-source
-
JPA variant of
hibernate.hbm2ddl.import_files
. Specifies ajava.io.Reader
configured for reading of the SQL load script or a string designating the filejava.net.URL
for the SQL load script. A "SQL load script" is a script that performs some database initialization (INSERT, etc). hibernate.hbm2ddl.import_files_sql_extractor
-
Reference to the
ImportSqlCommandExtractor
implementation class to use for parsing source/import files as defined byjavax.persistence.schema-generation.create-script-source
,javax.persistence.schema-generation.drop-script-source
orhibernate.hbm2ddl.import_files
.Reference may refer to an instance, a Class implementing
ImportSqlCommandExtractor
or the fully-qualified name of theImportSqlCommandExtractor
implementation. If the fully-qualified name is given, the implementation must provide a no-arg constructor.The default value is
SingleLineSqlCommandExtractor
. hibernate.hbm2ddl.create_namespaces
(e.g.true
orfalse
(default value))-
Specifies whether to automatically create the database schema/catalog also.
javax.persistence.create-database-schemas
(e.g.true
orfalse
(default value))-
The JPA variant of
hibernate.hbm2ddl.create_namespaces
. Specifies whether the persistence provider is to create the database schema(s) in addition to creating database objects (tables, sequences, constraints, etc). The value of this boolean property should be set totrue
if the persistence provider is to create schemas in the database or to generate DDL that contains "CREATE SCHEMA" commands.If this property is not supplied (or is explicitly
false
), the provider should not attempt to create database schemas. hibernate.hbm2ddl.schema_filter_provider
-
Used to specify the
SchemaFilterProvider
to be used bycreate
,drop
,migrate
, andvalidate
operations on the database schema.SchemaFilterProvider
provides filters that can be used to limit the scope of these operations to specific namespaces, tables and sequences. All objects are included by default. hibernate.hbm2ddl.jdbc_metadata_extraction_strategy
(e.g.grouped
(default value) orindividually
)-
Setting to choose the strategy used to access the JDBC Metadata. Valid options are defined by the
strategy
value of theJdbcMetadaAccessStrategy
enum:grouped
-
SchemaMigrator
andSchemaValidator
execute a singlejava.sql.DatabaseMetaData#getTables(String, String, String, String[])
call to retrieve all the database table in order to determine if all thejavax.persistence.Entity
s have a corresponding mapped database tables. This strategy may requirehibernate.default_schema
and/orhibernate.default_catalog
to be provided. individually
-
SchemaMigrator
andSchemaValidator
execute onejava.sql.DatabaseMetaData#getTables(String, String, String, String[])
call for eachjavax.persistence.Entity
in order to determine if a corresponding database table exists.
hibernate.hbm2ddl.delimiter
(e.g.;
)-
Identifies the delimiter to use to separate schema management statements in script outputs. The default value is
;
. hibernate.schema_management_tool
(e.g. A schema name)-
Used to specify the
SchemaManagementTool
to use for performing schema management. The default is to useHibernateSchemaManagementTool
. hibernate.synonyms
(e.g.true
orfalse
(default value))-
If enabled, allows schema update and validation to support synonyms. Due to the possibility that this would return duplicate tables (especially in Oracle), this is disabled by default.
hibernate.hbm2ddl.extra_physical_table_types
(e.g.BASE TABLE
)-
Identifies a comma-separated list of values to specify extra table types, other than the default
TABLE
value, to recognize as defining a physical table by schema update, creation and validation. hibernate.hbm2ddl.default_constraint_mode
(CONSTRAINT
(default value) orNO_CONSTRAINT
)-
Default
javax.persistence.ConstraintMode
for foreign key mapping ifPROVIDER_DEFAULT
strategy used. hibernate.schema_update.unique_constraint_strategy
(e.g.DROP_RECREATE_QUIETLY
,RECREATE_QUIETLY
,SKIP
)-
Unique columns and unique keys both use unique constraints in most dialects.
SchemaUpdate
needs to create these constraints, but DBs support for finding existing constraints is extremely inconsistent. Further, non-explicitly-named unique constraints use randomly generated characters.Therefore, the
UniqueConstraintSchemaUpdateStrategy
offers the following options:DROP_RECREATE_QUIETLY
-
Default option. Attempt to drop, then (re-)create each unique constraint. Ignore any exceptions being thrown.
RECREATE_QUIETLY
-
Attempts to (re-)create unique constraints, ignoring exceptions thrown if the constraint already existed.
SKIP
-
Does not attempt to create unique constraints on a schema update.
hibernate.hbm2ddl.charset_name
(e.g.Charset.defaultCharset()
)-
Defines the charset (encoding) used for all input/output schema generation resources. By default, Hibernate uses the default charset given by
Charset.defaultCharset()
. This configuration property allows you to override the default JVM setting so that you can specify which encoding is used when reading and writing schema generation resources (e.g. File, URL). hibernate.hbm2ddl.halt_on_error
(e.g.true
orfalse
(default value))-
Whether the schema migration tool should halt on error, therefore terminating the bootstrap process. By default, the
EntityManagerFactory
orSessionFactory
are created even if the schema migration throws exceptions. To prevent this default behavior, set this property value totrue
.
24.16. Exception handling
hibernate.jdbc.sql_exception_converter
(e.g. fully-qualified name of class implementingSQLExceptionConverter
)-
The
SQLExceptionConverter
to use for convertingSQLExceptions
to Hibernate’sJDBCException
hierarchy. The default is to use the configuredDialect
's preferredSQLExceptionConverter
. hibernate.native_exception_handling_51_compliance
(e.g.true
orfalse
(default value))-
Indicates if exception handling for a
SessionFactory
built via Hibernate’s native bootstrapping should behave the same as native exception handling in Hibernate ORM 5.1. When set totrue
,HibernateException
will not be wrapped or converted according to the JPA specification. This setting will be ignored for aSessionFactory
built via JPA bootstrapping.
24.17. Session events
hibernate.session.events.auto
-
Fully qualified class name implementing the
SessionEventListener
interface. hibernate.session_factory.interceptor
(e.g.org.hibernate.EmptyInterceptor
(default value))-
Names an
Interceptor
implementation to be applied to everySession
created by the currentorg.hibernate.SessionFactory
.Can reference:
-
Interceptor
instance -
Interceptor
implementationClass
object reference -
Interceptor
implementation class name
-
hibernate.ejb.interceptor
(e.g.hibernate.session_factory.interceptor
(default value))-
Deprecated setting. Use hibernate.session_factory.session_scoped_interceptor
instead. hibernate.session_factory.session_scoped_interceptor
(e.g. fully-qualified class name or class reference)-
Names an
org.hibernate.Interceptor
implementation to be applied to theorg.hibernate.SessionFactory
and propagated to eachSession
created from theSessionFactory
.This setting identifies an
Interceptor
implementation that is to be applied to everySession
opened from theSessionFactory
, but unlikehibernate.session_factory.interceptor
, a unique instance of theInterceptor
is used for eachSession
.Can reference:
-
Interceptor
instance -
Interceptor
implementationClass
object reference -
java.util.function.Supplier
instance which is used to retrieve theInterceptor
instanceSpecifically, this setting cannot name an Interceptor
instance.
-
hibernate.ejb.interceptor.session_scoped
(e.g. fully-qualified class name or class reference)-
Deprecated setting. Use hibernate.session_factory.session_scoped_interceptor
instead.An optional Hibernate interceptor.
The interceptor instance is specific to a given Session instance (and hence is not thread-safe) has to implement
org.hibernate.Interceptor
and have a no-arg constructor.This property cannot be combined with
hibernate.ejb.interceptor
. hibernate.ejb.session_factory_observer
(e.g. fully-qualified class name or class reference)-
Specifies a
SessionFactoryObserver
to be applied to the SessionFactory. The class must have a no-arg constructor. hibernate.ejb.event
(e.g.hibernate.ejb.event.pre-load
=com.acme.SecurityListener,com.acme.AuditListener
)-
Event listener list for a given event type. The list of event listeners is a comma separated fully qualified class name list.
24.18. JMX settings
hibernate.jmx.enabled
(e.g.true
orfalse
(default value))-
Enable JMX.
hibernate.jmx.usePlatformServer
(e.g.true
orfalse
(default value))-
Uses the platform MBeanServer as returned by
ManagementFactory#getPlatformMBeanServer()
. hibernate.jmx.agentId
-
The agent identifier of the associated
MBeanServer
. hibernate.jmx.defaultDomain
-
The domain name of the associated
MBeanServer
. hibernate.jmx.sessionFactoryName
-
The
SessionFactory
name appended to the object name the Manageable Bean is registered with. If null, thehibernate.session_factory_name
configuration value is used. org.hibernate.core
-
The default object domain appended to the object name the Manageable Bean is registered with.
24.19. JACC settings
hibernate.jacc.enabled
(e.g.true
orfalse
(default value))-
Is JACC enabled?
hibernate.jacc
(e.g.hibernate.jacc.allowed.org.jboss.ejb3.test.jacc.AllEntity
)-
The property name defines the role (e.g.
allowed
) and the entity class name (e.g.org.jboss.ejb3.test.jacc.AllEntity
), while the property value defines the authorized actions (e.g.insert,update,read
). hibernate.jacc_context_id
-
A String identifying the policy context whose PolicyConfiguration interface is to be returned. The value passed to this parameter must not be null.
24.20. ClassLoaders properties
hibernate.classLoaders
-
Used to define a
java.util.Collection<ClassLoader>
or theClassLoader
instance Hibernate should use for class-loading and resource-lookups. hibernate.classLoader.application
-
Names the
ClassLoader
used to load user application classes. hibernate.classLoader.resources
-
Names the
ClassLoader
Hibernate should use to perform resource loading. hibernate.classLoader.hibernate
-
Names the
ClassLoader
responsible for loading Hibernate classes. By default, this is theClassLoader
that loaded this class. hibernate.classLoader.environment
-
Names the
ClassLoader
used when Hibernate is unable to locates classes on thehibernate.classLoader.application
orhibernate.classLoader.hibernate
.
24.21. Bootstrap properties
hibernate.integrator_provider
(e.g. The fully qualified name of anIntegratorProvider
)-
Used to define a list of
Integrator
which is used during the bootstrap process to integrate various services. hibernate.strategy_registration_provider
(e.g. The fully qualified name of anStrategyRegistrationProviderList
)-
Used to define a list of
StrategyRegistrationProvider
which is used during the bootstrap process to provide registrations of strategy selector(s). hibernate.type_contributors
(e.g. The fully qualified name of anTypeContributorList
)-
Used to define a list of
TypeContributor
which is used during the bootstrap process to contribute types. hibernate.persister.resolver
(e.g. The fully qualified name of aPersisterClassResolver
or aPersisterClassResolver
instance)-
Used to define an implementation of the
PersisterClassResolver
interface which can be used to customize how an entity or a collection is being persisted. hibernate.persister.factory
(e.g. The fully qualified name of aPersisterFactory
or aPersisterFactory
instance)-
Like a
PersisterClassResolver
, thePersisterFactory
can be used to customize how an entity or a collection are being persisted. hibernate.service.allow_crawling
(e.g.true
(default value) orfalse
)-
Crawl all available service bindings for an alternate registration of a given Hibernate
Service
. hibernate.metadata_builder_contributor
(e.g. The instance, the class or the fully qualified class name of aMetadataBuilderContributor
)-
Used to define an instance, the class or the fully qualified class name of a
MetadataBuilderContributor
which can be used to configure theMetadataBuilder
when bootstrapping via the JPAEntityManagerFactory
.
24.22. Miscellaneous properties
hibernate.dialect_resolvers
-
Names any additional
DialectResolver
implementations to register with the standardDialectFactory
. hibernate.session_factory_name
(e.g. A JNDI name)-
Setting used to name the Hibernate
SessionFactory
. Naming theSessionFactory
allows for it to be properly serialized across JVMs as long as the same name is used on each JVM.If
hibernate.session_factory_name_is_jndi
is set totrue
, this is also the name under which theSessionFactory
is bound into JNDI on startup and from which it can be obtained from JNDI. hibernate.session_factory_name_is_jndi
(e.g.true
(default value) orfalse
)-
Does the value defined by
hibernate.session_factory_name
represent a JNDI namespace into which theorg.hibernate.SessionFactory
should be bound and made accessible?Defaults to
true
for backward compatibility. Set this tofalse
if naming a SessionFactory is needed for serialization purposes, but no writable JNDI context exists in the runtime environment or if the user simply does not want JNDI to be used. hibernate.ejb.entitymanager_factory_name
(e.g. By default, the persistence unit name is used, otherwise a randomly generated UUID)-
Internally, Hibernate keeps track of all
EntityManagerFactory
instances using theEntityManagerFactoryRegistry
. The name is used as a key to identify a givenEntityManagerFactory
reference. hibernate.ejb.cfgfile
(e.g.hibernate.cfg.xml
(default value))-
XML configuration file to use to configure Hibernate.
hibernate.ejb.discard_pc_on_close
(e.g.true
orfalse
(default value))-
If true, the persistence context will be discarded (think
clear()
when the method is called). Otherwise, the persistence context will stay alive till the transaction completion: all objects will remain managed, and any change will be synchronized with the database (default to false, ie wait for transaction completion). hibernate.ejb.metamodel.population
(e.g.enabled
ordisabled
, orignoreUnsupported
(default value))-
Setting that indicates whether to build the JPA types.
Accepts three values:
- enabled
-
Do the build.
- disabled
-
Do not do the build.
- ignoreUnsupported
-
Do the build, but ignore any non-JPA features that would otherwise result in a failure (e.g.
@Any
annotation).
hibernate.jpa.static_metamodel.population
(e.g.enabled
ordisabled
, orskipUnsupported
(default value))-
Setting that controls whether we seek out JPA static metamodel classes and populate them.
Accepts three values:
- enabled
-
Do the population.
- disabled
-
Do not do the population.
- skipUnsupported
-
Do the population, but ignore any non-JPA features that would otherwise result in the population failing (e.g.
@Any
annotation).
hibernate.delay_cdi_access
(e.g.true
orfalse
(default value))-
Defines delayed access to CDI
BeanManager
. Starting in 5.1 the preferred means for CDI bootstrapping is throughExtendedBeanManager
. hibernate.resource.beans.container
(e.g. fully-qualified class name)-
Identifies an explicit
org.hibernate.resource.beans.container.spi.BeanContainer
to be used.Note that, for CDI-based containers, setting this is not necessary. Simply pass the
BeanManager
to use viajavax.persistence.bean.manager
and optionally specifyhibernate.delay_cdi_access
.This setting is more meant to integrate non-CDI bean containers such as Spring.
hibernate.allow_update_outside_transaction
(e.g.true
orfalse
(default value))-
Setting that allows to perform update operations outside of a transaction boundary.
Accepts two values:
- true
-
allows to flush an update out of a transaction
- false
-
does not allow
hibernate.collection_join_subquery
(e.g.true
(default value) orfalse
)-
Setting which indicates whether or not the new JOINs over collection tables should be rewritten to subqueries.
hibernate.allow_refresh_detached_entity
(e.g.true
(default value when using Hibernate native bootstrapping) orfalse
(default value when using JPA bootstrapping))-
Setting that allows to call
javax.persistence.EntityManager#refresh(entity)
orSession#refresh(entity)
on a detached instance even when theorg.hibernate.Session
is obtained from a JPAjavax.persistence.EntityManager
. hibernate.use_entity_where_clause_for_collections
(e.g.,true
(default) orfalse
)-
Setting controls whether an entity’s "where" clause, mapped using
@Where(clause = "…")
or<entity … where="…">
is taken into account when loading one-to-many or many-to-many collections of that type of entity. hibernate.event.merge.entity_copy_observer
(e.g.disallow
(default value),allow
,log
(testing purpose only) or fully-qualified class name)-
Setting that specifies how Hibernate will respond when multiple representations of the same persistent entity ("entity copy") is detected while merging.
The possible values are:
- disallow
-
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
EntityCopyAllowedLoggedObserver
.
In addition, the application may customize the behavior by providing an implementation of 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 cascade = CascadeType.ALL
.
The entity state resulting from merging an entity copy will be overwritten when another entity copy is merged.
For more details, check out the Merge gotchas section.
24.23. Envers properties
hibernate.envers.autoRegisterListeners
(e.g.true
(default value) orfalse
)-
When set to
false
, the Envers entity listeners are no longer auto-registered, so you need to register them manually during the bootstrap process. hibernate.integration.envers.enabled
(e.g.true
(default value) orfalse
)-
Enable or disable the Hibernate Envers
Service
integration. hibernate.listeners.envers.autoRegister
-
Legacy setting. Use
hibernate.envers.autoRegisterListeners
orhibernate.integration.envers.enabled
instead.
24.24. Spatial properties
hibernate.integration.spatial.enabled
(e.g.true
(default value) orfalse
)-
Enable or disable the Hibernate Spatial
Service
integration. hibernate.spatial.connection_finder
(e.g.org.geolatte.geom.codec.db.oracle.DefaultConnectionFinder
)-
Define the fully-qualified name of class implementing the
org.geolatte.geom.codec.db.oracle.ConnectionFinder
interface.
24.25. Internal properties
The following configuration properties are used internally, and you shouldn’t probably have to configured them in your application.
hibernate.enable_specj_proprietary_syntax
(e.g.true
orfalse
(default value))-
Enable or disable the SpecJ proprietary mapping syntax which differs from JPA specification. Used during performance testing only.
hibernate.temp.use_jdbc_metadata_defaults
(e.g.true
(default value) orfalse
)-
This setting is used to control whether we should consult the JDBC metadata to determine certain Settings default values when the database may not be available (mainly in tools usage).
hibernate.connection_provider.injection_data
-
Connection provider settings to be injected (a
Map
instance) in the currently configured connection provider. hibernate.jandex_index
-
Names a Jandex
org.jboss.jandex.Index
instance to use.
25. Mapping annotations
25.1. JPA annotations
25.1.1. @Access
The @Access
annotation is used to specify the access type of the associated entity class, mapped superclass, or embeddable class, or entity attribute.
See the Access type section for more info.
25.1.2. @AssociationOverride
The @AssociationOverride
annotation is used to override an association mapping (e.g. @ManyToOne
, @OneToOne
, @OneToMany
, @ManyToMany
) inherited from a mapped superclass or an embeddable.
See the Overriding Embeddable types section for more info.
25.1.3. @AssociationOverrides
The @AssociationOverrides
is used to group several @AssociationOverride
annotations.
25.1.4. @AttributeOverride
The @AttributeOverride
annotation is used to override an attribute mapping inherited from a mapped superclass or an embeddable.
See the Overriding Embeddable types section for more info.
25.1.5. @AttributeOverrides
The @AttributeOverrides
is used to group several @AttributeOverride
annotations.
25.1.6. @Basic
The @Basic
annotation is used to map a basic attribute type to a database column.
See the Basic types chapter for more info.
25.1.7. @Cacheable
The @Cacheable
annotation is used to specify whether an entity should be stored in the second-level cache.
If the persistence.xml
shared-cache-mode
XML attribute is set to ENABLE_SELECTIVE
, then only the entities annotated with the @Cacheable
are going to be stored in the second-level cache.
If shared-cache-mode
XML attribute value is DISABLE_SELECTIVE
, then the entities marked with the @Cacheable
annotation are not going to be stored in the second-level cache, while all the other entities are stored in the cache.
See the Caching chapter for more info.
25.1.8. @CollectionTable
The @CollectionTable
annotation is used to specify the database table that stores the values of a basic or an embeddable type collection.
See the Collections of embeddable types section for more info.
25.1.9. @Column
The @Column
annotation is used to specify the mapping between a basic entity attribute and the database table column.
See the @Column
annotation section for more info.
25.1.10. @ColumnResult
The @ColumnResult
annotation is used in conjunction with the @SqlResultSetMapping
or @ConstructorResult
annotations to map a SQL column for a given SELECT query.
See the Entity associations with named native queries section for more info.
25.1.11. @ConstructorResult
The @ConstructorResult
annotation is used in conjunction with the @SqlResultSetMapping
annotations to map columns of a given SELECT query to a certain object constructor.
See the Multiple scalar values NamedNativeQuery
with ConstructorResult
section for more info.
25.1.12. @Convert
The @Convert
annotation is used to specify the AttributeConverter
implementation used to convert the currently annotated basic attribute.
If the AttributeConverter
uses autoApply
, then all entity attributes with the same target type are going to be converted automatically.
See the AttributeConverter
section for more info.
25.1.13. @Converter
The @Converter
annotation is used to specify that the currently annotated AttributeConverter
implementation can be used as a JPA basic attribute converter.
If the autoApply
attribute is set to true
, then the JPA provider will automatically convert all basic attributes with the same Java type as defined by the current converter.
See the AttributeConverter
section for more info.
25.1.14. @Converts
See the AttributeConverter
section for more info.
25.1.15. @DiscriminatorColumn
The @DiscriminatorColumn
annotation is used to specify the discriminator column name and the discriminator type for the SINGLE_TABLE
and JOINED
inheritance strategies.
See the Discriminator section for more info.
25.1.16. @DiscriminatorValue
The @DiscriminatorValue
annotation is used to specify what value of the discriminator column is used for mapping the currently annotated entity.
See the Discriminator section for more info.
25.1.17. @ElementCollection
The @ElementCollection
annotation is used to specify a collection of a basic or embeddable types.
See the Collections section for more info.
25.1.18. @Embeddable
The @Embeddable
annotation is used to specify embeddable types. Like basic types, embeddable types do not have any identity, being managed by their owning entity.
See the Embeddables section for more info.
25.1.19. @Embedded
The @Embedded
annotation is used to specify that a given entity attribute represents an embeddable type.
See the Embeddables section for more info.
25.1.20. @EmbeddedId
The @EmbeddedId
annotation is used to specify the entity identifier is an embeddable type.
See the Composite identifiers with @EmbeddedId
section for more info.
25.1.21. @Entity
The @Entity
annotation is used to specify that the currently annotated class represents an entity type.
Unlike basic and embeddable types, entity types have an identity and their state is managed by the underlying Persistence Context.
See the Entity section for more info.
25.1.22. @EntityListeners
The @EntityListeners
annotation is used to specify an array of callback listener classes that are used by the currently annotated entity.
See the JPA callbacks section for more info.
25.1.23. @EntityResult
The @EntityResult
annotation is used with the @SqlResultSetMapping
annotation to map the selected columns to an entity.
See the Entity associations with named native queries section for more info.
25.1.24. @Enumerated
The @Enumerated
annotation is used to specify that an entity attribute represents an enumerated type.
See the @Enumerated
basic type section for more info.
25.1.25. @ExcludeDefaultListeners
The @ExcludeDefaultListeners
annotation is used to specify that the currently annotated entity skips the invocation of any default listener.
See the Exclude default entity listeners section for more info.
25.1.26. @ExcludeSuperclassListeners
The @ExcludeSuperclassListeners
annotation is used to specify that the currently annotated entity skips the invocation of listeners declared by its superclass.
See the Exclude default entity listeners section for more info.
25.1.27. @FieldResult
The @FieldResult
annotation is used with the @EntityResult
annotation to map the selected columns to the fields of some specific entity.
See the Entity associations with named native queries section for more info.
25.1.28. @ForeignKey
The @ForeignKey
annotation is used to specify the associated foreign key of a @JoinColumn
mapping.
The @ForeignKey
annotation is only used if the automated schema generation tool is enabled, in which case, it allows you to customize the underlying foreign key definition.
See the @ManyToOne
with @ForeignKey
section for more info.
25.1.29. @GeneratedValue
The @GeneratedValue
annotation specifies that the entity identifier value is automatically generated using an identity column, a database sequence, or a table generator.
Hibernate supports the @GeneratedValue
mapping even for UUID
identifiers.
See the Automatically-generated identifiers section for more info.
25.1.30. @Id
The @Id
annotation specifies the entity identifier.
An entity must always have an identifier attribute which is used when loading the entity in a given Persistence Context.
See the Identifiers section for more info.
25.1.31. @IdClass
The @IdClass
annotation is used if the current entity defined a composite identifier.
A separate class encapsulates all the identifier attributes, which are mirrored by the current entity mapping.
See the Composite identifiers with @IdClass
section for more info.
25.1.32. @Index
The @Index
annotation is used by the automated schema generation tool to create a database index.
See the Columns index chapter for more info.
25.1.33. @Inheritance
The @Inheritance
annotation is used to specify the inheritance strategy of a given entity class hierarchy.
See the Inheritance section for more info.
25.1.34. @JoinColumn
The @JoinColumn
annotation is used to specify the FOREIGN KEY column used when joining an entity association or an embeddable collection.
See the @ManyToOne
with @JoinColumn
section for more info.
25.1.35. @JoinColumns
The @JoinColumns
annotation is used to group multiple @JoinColumn
annotations, which are used when mapping entity association or an embeddable collection using a composite identifier.
25.1.36. @JoinTable
The @JoinTable
annotation is used to specify the link table between two other database tables.
See the @JoinTable
mapping section for more info.
25.1.37. @Lob
The @Lob
annotation is used to specify that the currently annotated entity attribute represents a large object type.
See the BLOB
mapping section for more info.
25.1.38. @ManyToMany
The @ManyToMany
annotation is used to specify a many-to-many database relationship.
See the @ManyToMany
mapping section for more info.
25.1.39. @ManyToOne
The @ManyToOne
annotation is used to specify a many-to-one database relationship.
See the @ManyToOne
mapping section for more info.
25.1.40. @MapKey
The @MapKey
annotation is used to specify the key of a java.util.Map
association for which the key type is either the primary key or an attribute of the entity which represents the value of the map.
See the @MapKey
mapping section for more info.
25.1.41. @MapKeyClass
The @MapKeyClass
annotation is used to specify the type of the map key of a java.util.Map
associations.
See the @MapKeyClass
mapping section for more info.
25.1.42. @MapKeyColumn
The @MapKeyColumn
annotation is used to specify the database column which stores the key of a java.util.Map
association for which the map key is a basic type.
See the @MapKeyType
mapping section for an example of @MapKeyColumn
annotation usage.
25.1.43. @MapKeyEnumerated
The @MapKeyEnumerated
annotation is used to specify that the key of java.util.Map
association is a Java Enum.
See the @MapKeyEnumerated
mapping section for more info.
25.1.44. @MapKeyJoinColumn
The @MapKeyJoinColumn
annotation is used to specify that the key of java.util.Map
association is an entity association.
The map key column is a FOREIGN KEY in a link table that also joins the Map
owner’s table with the table where the Map
value resides.
See the @MapKeyJoinColumn
mapping section for more info.
25.1.45. @MapKeyJoinColumns
The @MapKeyJoinColumns
annotation is used to group several @MapKeyJoinColumn
mappings when the java.util.Map
association key uses a composite identifier.
25.1.46. @MapKeyTemporal
The @MapKeyTemporal
annotation is used to specify that the key of java.util.Map
association is a @TemporalType
(e.g. DATE
, TIME
, TIMESTAMP
).
See the @MapKeyTemporal
mapping section for more info.
25.1.47. @MappedSuperclass
The @MappedSuperclass
annotation is used to specify that the currently annotated type attributes are inherited by any subclass entity.
See the @MappedSuperclass
section for more info.
25.1.48. @MapsId
The @MapsId
annotation is used to specify that the entity identifier is mapped by the currently annotated @ManyToOne
or @OneToOne
association.
See the @MapsId
mapping section for more info.
25.1.49. @NamedAttributeNode
The @NamedAttributeNode
annotation is used to specify each individual attribute node that needs to be fetched by an Entity Graph.
See the Fetch graph section for more info.
25.1.50. @NamedEntityGraph
The @NamedEntityGraph
annotation is used to specify an Entity Graph that can be used by an entity query to override the default fetch plan.
See the Fetch graph section for more info.
25.1.51. @NamedEntityGraphs
The @NamedEntityGraphs
annotation is used to group multiple @NamedEntityGraph
annotations.
25.1.52. @NamedNativeQueries
The @NamedNativeQueries
annotation is used to group multiple @NamedNativeQuery
annotations.
See the Custom CRUD mapping section for more info.
25.1.53. @NamedNativeQuery
The @NamedNativeQuery
annotation is used to specify a native SQL query that can be retrieved later by its name.
See the Custom CRUD mapping section for more info.
25.1.54. @NamedQueries
The @NamedQueries
annotation is used to group multiple @NamedQuery
annotations.
25.1.55. @NamedQuery
The @NamedQuery
annotation is used to specify a JPQL query that can be retrieved later by its name.
See the @NamedQuery
section for more info.
25.1.56. @NamedStoredProcedureQueries
The @NamedStoredProcedureQueries
annotation is used to group multiple @NamedStoredProcedureQuery
annotations.
25.1.57. @NamedStoredProcedureQuery
The @NamedStoredProcedureQuery
annotation is used to specify a stored procedure query that can be retrieved later by its name.
See the Using named queries to call stored procedures section for more info.
25.1.58. @NamedSubgraph
The @NamedSubgraph
annotation used to specify a subgraph in an Entity Graph.
See the Fetch subgraph section for more info.
25.1.59. @OneToMany
The @OneToMany
annotation is used to specify a one-to-many database relationship.
See the @OneToMany
mapping section for more info.
25.1.60. @OneToOne
The @OneToOne
annotation is used to specify a one-to-one database relationship.
See the @OneToOne
mapping section for more info.
25.1.61. @OrderBy
The @OrderBy
annotation is used to specify the entity attributes used for sorting when fetching the currently annotated collection.
See the @OrderBy
mapping section for more info.
25.1.62. @OrderColumn
The @OrderColumn
annotation is used to specify that the current annotation collection order should be materialized in the database.
See the @OrderColumn
mapping section for more info.
25.1.63. @PersistenceContext
The @PersistenceContext
annotation is used to specify the EntityManager
that needs to be injected as a dependency.
See the @PersistenceContext
mapping section for more info.
25.1.64. @PersistenceContexts
The @PersistenceContexts
annotation is used to group multiple @PersistenceContext
annotations.
25.1.65. @PersistenceProperty
The @PersistenceProperty
annotation is used by the @PersistenceContext
annotation to declare JPA provider properties that are passed to the underlying container when the EntityManager
instance is created.
See the @PersistenceProperty
mapping section for more info.
25.1.66. @PersistenceUnit
The @PersistenceUnit
annotation is used to specify the EntityManagerFactory
that needs to be injected as a dependency.
See the @PersistenceUnit
mapping section for more info.
25.1.67. @PersistenceUnits
The @PersistenceUnits
annotation is used to group multiple @PersistenceUnit
annotations.
25.1.68. @PostLoad
The @PostLoad
annotation is used to specify a callback method that fires after an entity is loaded.
See the JPA callbacks section for more info.
25.1.69. @PostPersist
The @PostPersist
annotation is used to specify a callback method that fires after an entity is persisted.
See the JPA callbacks section for more info.
25.1.70. @PostRemove
The @PostRemove
annotation is used to specify a callback method that fires after an entity is removed.
See the JPA callbacks section for more info.
25.1.71. @PostUpdate
The @PostUpdate
annotation is used to specify a callback method that fires after an entity is updated.
See the JPA callbacks section for more info.
25.1.72. @PrePersist
The @PrePersist
annotation is used to specify a callback method that fires before an entity is persisted.
See the JPA callbacks section for more info.
25.1.73. @PreRemove
The @PreRemove
annotation is used to specify a callback method that fires before an entity is removed.
See the JPA callbacks section for more info.
25.1.74. @PreUpdate
The @PreUpdate
annotation is used to specify a callback method that fires before an entity is updated.
See the JPA callbacks section for more info.
25.1.75. @PrimaryKeyJoinColumn
The @PrimaryKeyJoinColumn
annotation is used to specify that the primary key column of the currently annotated entity is also a foreign key to some other entity
(e.g. a base class table in a JOINED
inheritance strategy, the primary table in a secondary table mapping, or the parent table in a @OneToOne
relationship).
See the @PrimaryKeyJoinColumn
mapping section for more info.
25.1.76. @PrimaryKeyJoinColumns
The @PrimaryKeyJoinColumns
annotation is used to group multiple @PrimaryKeyJoinColumn
annotations.
25.1.77. @QueryHint
The @QueryHint
annotation is used to specify a JPA provider hint used by a @NamedQuery
or a @NamedNativeQuery
annotation.
See the @QueryHint
section for more info.
25.1.78. @SecondaryTable
The @SecondaryTable
annotation is used to specify a secondary table for the currently annotated entity.
See the @SecondaryTable
mapping section for more info.
25.1.79. @SecondaryTables
The @SecondaryTables
annotation is used to group multiple @SecondaryTable
annotations.
25.1.80. @SequenceGenerator
The @SequenceGenerator
annotation is used to specify the database sequence used by the identifier generator of the currently annotated entity.
See the @SequenceGenerator
mapping section for more info.
25.1.81. @SqlResultSetMapping
The @SqlResultSetMapping
annotation is used to specify the ResultSet
mapping of a native SQL query or stored procedure.
See the SqlResultSetMapping
mapping section for more info.
25.1.82. @SqlResultSetMappings
The @SqlResultSetMappings
annotation is group multiple @SqlResultSetMapping
annotations.
25.1.83. @StoredProcedureParameter
The @StoredProcedureParameter
annotation is used to specify a parameter of a @NamedStoredProcedureQuery
.
See the Using named queries to call stored procedures section for more info.
25.1.84. @Table
The @Table
annotation is used to specify the primary table of the currently annotated entity.
See the @Table
mapping section for more info.
25.1.85. @TableGenerator
The @TableGenerator
annotation is used to specify the database table used by the identity generator of the currently annotated entity.
See the @TableGenerator
mapping section for more info.
25.1.86. @Temporal
The @Temporal
annotation is used to specify the TemporalType
of the currently annotated java.util.Date
or java.util.Calendar
entity attribute.
See the Basic temporal types chapter for more info.
25.1.87. @Transient
The @Transient
annotation is used to specify that a given entity attribute should not be persisted.
See the @Transient
mapping section for more info.
25.1.88. @UniqueConstraint
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 currently annotated entity.
See the Columns unique constraint chapter for more info.
25.1.89. @Version
The @Version
annotation is used to specify the version attribute used for optimistic locking.
See the Optimistic locking mapping section for more info.
25.2. Hibernate annotations
25.2.1. @AccessType
The @AccessType
annotation is deprecated.
You should use either the JPA @Access
or the Hibernate native @AttributeAccessor
annotation.
25.2.2. @Any
The @Any
annotation is used to define the any-to-one association
which can point to one of several entity types.
See the @Any
mapping section for more info.
25.2.3. @AnyMetaDef
The @AnyMetaDef
annotation is used to provide metadata about an @Any
or @ManyToAny
mapping.
See the @Any
mapping section for more info.
25.2.4. @AnyMetaDefs
The @AnyMetaDefs
annotation is used to group multiple @AnyMetaDef
annotations.
25.2.5. @AttributeAccessor
The @AttributeAccessor
annotation is used to specify a custom PropertyAccessStrategy
.
Should only be used to name a custom PropertyAccessStrategy
.
For property/field access type, the JPA @Access
annotation should be preferred.
However, if this annotation is used with either value="property" or value="field", it will act just as the corresponding usage of the JPA @Access
annotation.
25.2.6. @BatchSize
The @BatchSize
annotation is used to specify the size for batch loading the entries of a lazy collection.
See the Batch fetching section for more info.
25.2.7. @Cache
The @Cache
annotation is used to specify the CacheConcurrencyStrategy
of a root entity or a collection.
See the Caching chapter for more info.
25.2.8. @Cascade
The @Cascade
annotation is used to apply the Hibernate specific CascadeType
strategies (e.g. CascadeType.LOCK
, CascadeType.SAVE_UPDATE
, CascadeType.REPLICATE
) on a given association.
For JPA cascading, prefer using the javax.persistence.CascadeType
instead.
When combining both JPA and Hibernate CascadeType
strategies, Hibernate will merge both sets of cascades.
See the Cascading chapter for more info.
25.2.9. @Check
The @Check
annotation is used to specify an arbitrary SQL CHECK constraint which can be defined at the class level.
See the Database-level checks chapter for more info.
25.2.10. @CollectionId
The @CollectionId
annotation is used to specify an identifier column for an idbag collection.
You might want to use the JPA @OrderColumn
instead.
25.2.11. @CollectionType
The @CollectionType
annotation is used to specify a custom collection type.
The collection can also name a @Type
, which defines the Hibernate Type of the collection elements.
See the Custom collection types chapter for more info.
25.2.12. @ColumnDefault
The @ColumnDefault
annotation is used to specify the DEFAULT
DDL value to apply when using the automated schema generator.
The same behavior can be achieved using the definition
attribute of the JPA @Column
annotation.
See the Default value for a database column chapter for more info.
25.2.13. @Columns
See the @Columns
mapping section for more info.
25.2.14. @ColumnTransformer
The @ColumnTransformer
annotation is used to customize how a given column value is read from or written into the database.
See the @ColumnTransformer
mapping section for more info.
25.2.15. @ColumnTransformers
The @ColumnTransformers
annotation is used to group multiple @ColumnTransformer
annotations.
25.2.16. @CreationTimestamp
The @CreationTimestamp
annotation is used to specify that the currently annotated temporal type must be initialized with the current JVM timestamp value.
See the @CreationTimestamp
mapping section for more info.
25.2.17. @DiscriminatorFormula
The @DiscriminatorFormula
annotation is used to specify a Hibernate @Formula
to resolve the inheritance discriminator value.
See the @DiscriminatorFormula
section for more info.
25.2.18. @DiscriminatorOptions
The @DiscriminatorOptions
annotation is used to provide the force
and insert
Discriminator properties.
See the Discriminator section for more info.
25.2.19. @DynamicInsert
The @DynamicInsert
annotation is used to specify that the INSERT
SQL statement should be generated whenever an entity is to be persisted.
By default, Hibernate uses a cached INSERT
statement that sets all table columns.
When the entity is annotated with the @DynamicInsert
annotation, the PreparedStatement
is going to include only the non-null columns.
See the @CreationTimestamp
mapping section for more info on how @DynamicInsert
works.
25.2.20. @DynamicUpdate
The @DynamicUpdate
annotation is used to specify that the UPDATE
SQL statement should be generated whenever an entity is modified.
By default, Hibernate uses a cached UPDATE
statement that sets all table columns.
When the entity is annotated with the @DynamicUpdate
annotation, the PreparedStatement
is going to include only the columns whose values have been changed.
See the @DynamicUpdate
section for more info.
For reattachment of detached entities, the dynamic update is not possible without having the |
25.2.22. @Fetch
The @Fetch
annotation is used to specify the Hibernate specific FetchMode
(e.g. JOIN
, SELECT
, SUBSELECT
) used for the currently annotated association.
See the @Fetch
mapping section for more info.
25.2.23. @FetchProfile
The @FetchProfile
annotation is used to specify a custom fetching profile, similar to a JPA Entity Graph.
See the Fetch mapping section for more info.
25.2.24. @FetchProfile.FetchOverride
The @FetchProfile.FetchOverride
annotation is used in conjunction with the @FetchProfile
annotation,
and it’s used for overriding the fetching strategy of a particular entity association.
See the Fetch profile section for more info.
25.2.25. @FetchProfiles
The @FetchProfiles
annotation is used to group multiple @FetchProfile
annotations.
25.2.26. @Filter
The @Filter
annotation is used to add filters to an entity or the target entity of a collection.
See the Filter mapping section for more info.
25.2.27. @FilterDef
The @FilterDef
annotation is used to specify a @Filter
definition (name, default condition and parameter types, if any).
See the Filter mapping section for more info.
25.2.28. @FilterDefs
The @FilterDefs
annotation is used to group multiple @FilterDef
annotations.
25.2.29. @FilterJoinTable
The @FilterJoinTable
annotation is used to add @Filter
capabilities to a join table collection.
See the FilterJoinTable mapping section for more info.
25.2.30. @FilterJoinTables
The @FilterJoinTables
annotation is used to group multiple @FilterJoinTable
annotations.
25.2.32. @ForeignKey
The @ForeignKey
annotation is deprecated. Use the JPA 2.1 @ForeignKey
annotation instead.
25.2.33. @Formula
The @Formula
annotation is used to specify an SQL fragment that is executed in order to populate a given entity attribute.
See the @Formula
mapping section for more info.
25.2.34. @Generated
The @Generated
annotation is used to specify that the currently annotated entity attribute is generated by the database.
See the @Generated
mapping section for more info.
25.2.35. @GeneratorType
The @GeneratorType
annotation is used to provide a ValueGenerator
and a GenerationTime
for the currently annotated generated attribute.
See the @GeneratorType
mapping section for more info.
25.2.36. @GenericGenerator
The @GenericGenerator
annotation can be used to configure any Hibernate identifier generator.
See the @GenericGenerator
mapping section for more info.
25.2.37. @GenericGenerators
The @GenericGenerators
annotation is used to group multiple @GenericGenerator
annotations.
25.2.38. @Immutable
The @Immutable
annotation is used to specify that the annotated entity, attribute, or collection is immutable.
See the @Immutable
mapping section for more info.
25.2.40. @IndexColumn
The @IndexColumn
annotation is deprecated. Use the JPA @OrderColumn
annotation instead.
25.2.41. @JoinColumnOrFormula
The @JoinColumnOrFormula
annotation is used to specify that the entity association is resolved either through a FOREIGN KEY join (e.g. @JoinColumn
) or using the result of a given SQL formula (e.g. @JoinFormula
).
See the @JoinColumnOrFormula
mapping section for more info.
25.2.42. @JoinColumnsOrFormulas
The @JoinColumnsOrFormulas
annotation is used to group multiple @JoinColumnOrFormula
annotations.
25.2.43. @JoinFormula
The @JoinFormula
annotation is used as a replacement for @JoinColumn
when the association does not have a dedicated FOREIGN KEY column.
See the @JoinFormula
mapping section for more info.
25.2.44. @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.
See the @LazyCollection
mapping section for more info.
25.2.45. @LazyGroup
The @LazyGroup
annotation is used to specify that an entity attribute should be fetched along with all the other attributes belonging to the same group.
To load entity attributes lazily, bytecode enhancement is needed. By default, all non-collection attributes are loaded in one group named "DEFAULT".
This annotation allows defining different groups of attributes to be initialized together when access one attribute in the group.
See the @LazyGroup
mapping section for more info.
25.2.46. @LazyToOne
The @LazyToOne
annotation is used to specify the laziness options, represented by LazyToOneOption
, available for a @OneToOne
or @ManyToOne
association.
LazyToOneOption
defines the following alternatives:
- FALSE
-
Eagerly load the association. This one is not needed since the JPA
FetchType.EAGER
offers the same behavior. - NO_PROXY
-
This option will fetch the association lazily while returning real entity object.
- PROXY
-
This option will fetch the association lazily while returning a proxy instead.
See the @LazyToOne
mapping example section for more info.
25.2.47. @ListIndexBase
The @ListIndexBase
annotation is used to specify the start value for a list index, as stored in the database.
By default, List
indexes are stored starting at zero. Generally used in conjunction with @OrderColumn
.
See the @ListIndexBase
mapping section for more info.
25.2.48. @Loader
The @Loader
annotation is used to override the default SELECT
query used for loading an entity.
See the Custom CRUD mapping section for more info.
25.2.49. @ManyToAny
The @ManyToAny
annotation is used to specify a many-to-one association when the target type is dynamically resolved.
See the @ManyToAny
mapping section for more info.
25.2.50. @MapKeyType
The @MapKeyType
annotation is used to specify the map key type.
See the @MapKeyType
mapping section for more info.
25.2.51. @MetaValue
The @MetaValue
annotation is used by the @AnyMetaDef
annotation to specify the association between a given discriminator value and an entity type.
See the @Any
mapping section for more info.
25.2.52. @NamedNativeQueries
The @NamedNativeQueries
annotation is used to group multiple @NamedNativeQuery
annotations.
25.2.53. @NamedNativeQuery
The @NamedNativeQuery
annotation extends the JPA @NamedNativeQuery
with Hibernate specific features, like:
-
flush mode for this particular query
-
if the query should be cached, and which cache region should be used
-
the selected entity
CacheModeType
strategy -
the JDBC
Statement
fetch size -
the JDBC
Statement
execution timeout -
if the query is a
CallableStatement
, targeting a stored procedure or a database function -
what SQL-level comment should be sent to the database
-
if the query is read-only, hence it does not store the resulted entities into the currently running Persistence Context
See the Hibernate @NamedNativeQuery
section for more info.
25.2.54. @NamedQueries
The @NamedQueries
annotation is used to group multiple @NamedQuery
annotations.
25.2.55. @NamedQuery
The @NamedQuery
annotation extends the JPA @NamedQuery
with Hibernate specific features, like:
-
flush mode for this particular query
-
if the query should be cached, and which cache region should be used
-
the selected entity
CacheModeType
strategy -
the JDBC
Statement
fetch size -
the JDBC
Statement
execution timeout -
if the query is a
CallableStatement
, targeting a stored procedure or a database function -
what SQL-level comment should be sent to the database
-
if the query is read-only, hence it does not store the resulted entities into the currently running Persistence Context
See the @NamedQuery
section for more info.
25.2.56. @Nationalized
The @Nationalized
annotation is used to specify that the currently annotated attribute is a character type (e.g. String
, Character
, Clob
) that is stored in a nationalized column type (NVARCHAR
, NCHAR
, NCLOB
).
See the @Nationalized
mapping section for more info.
25.2.57. @NaturalId
The @NaturalId
annotation is used to specify that the currently annotated attribute is part of the natural id of the entity.
See the Natural Ids section for more info.
25.2.58. @NaturalIdCache
The @NaturalIdCache
annotation is used to specify that the natural id values associated with the annotated entity should be stored in the second-level cache.
See the @NaturalIdCache
mapping section for more info.
25.2.59. @NotFound
The @NotFound
annotation is used to specify the NotFoundAction
strategy for when an element is not found in a given association.
The NotFoundAction
defines two possibilities:
EXCEPTION
-
An exception is thrown when an element is not found (default and recommended).
IGNORE
-
Ignore the element when not found in the database.
See the @NotFound
mapping section for more info.
25.2.60. @OnDelete
The @OnDelete
annotation is used to specify the delete strategy employed by the currently annotated collection, array or joined subclasses.
This annotation is used by the automated schema generation tool to generated the appropriate FOREIGN KEY DDL cascade directive.
The two possible strategies are defined by the OnDeleteAction
enumeration:
- CASCADE
-
Use the database FOREIGN KEY cascade capabilities.
- NO_ACTION
-
Take no action.
See the @OnDelete
cascade chapter for more info.
25.2.61. @OptimisticLock
The @OptimisticLock
annotation is used to specify if the currently annotated attribute will trigger an entity version increment upon being modified.
See the Excluding attributes section for more info.
25.2.62. @OptimisticLocking
The @OptimisticLocking
annotation is used to specify the currently annotated entity’s optimistic locking strategy.
The four possible strategies are defined by the OptimisticLockType
enumeration:
- NONE
-
The implicit optimistic locking mechanism is disabled.
- VERSION
-
The implicit optimistic locking mechanism is using a dedicated version column.
- ALL
-
The implicit optimistic locking mechanism is using all attributes as part of an expanded WHERE clause restriction for the
UPDATE
andDELETE
SQL statements. - DIRTY
-
The implicit optimistic locking mechanism is using the dirty attributes (the attributes that were modified) as part of an expanded WHERE clause restriction for the
UPDATE
andDELETE
SQL statements.
See the Versionless optimistic locking section for more info.
25.2.63. @OrderBy
The @OrderBy
annotation is used to specify a SQL ordering directive for sorting the currently annotated collection.
It differs from the JPA @OrderBy
annotation because the JPA annotation expects a JPQL order-by fragment, not an SQL directive.
See the @OrderBy
mapping section for more info.
25.2.64. @ParamDef
The @ParamDef
annotation is used in conjunction with @FilterDef
so that the Hibernate Filter can be customized with runtime-provided parameter values.
See the Filter mapping section for more info.
25.2.65. @Parameter
The @Parameter
annotation is a generic parameter (basically a key/value combination) used to parametrize other annotations,
like @CollectionType
, @GenericGenerator
, and @Type
, @TypeDef
.
25.2.66. @Parent
The @Parent
annotation is used to specify that the currently annotated embeddable attribute references back the owning entity.
See the @Parent
mapping section for more info.
25.2.67. @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.
See the @Persister
mapping section for more info.
25.2.68. @Polymorphism
The @Polymorphism
annotation is used to define the PolymorphismType
Hibernate will apply to entity hierarchies.
There are two possible PolymorphismType
options:
- EXPLICIT
-
The currently annotated entity is retrieved only if explicitly asked.
- IMPLICIT
-
The currently annotated entity is retrieved if any of its super entities are retrieved. This is the default option.
See the @Polymorphism
section for more info.
25.2.69. @Proxy
The @Proxy
annotation is used to specify a custom proxy implementation for the currently annotated entity.
See the @Proxy
mapping section for more info.
25.2.70. @RowId
The @RowId
annotation is used to specify the database column used as a ROWID
pseudocolumn.
For instance, Oracle defines the ROWID
pseudocolumn which provides the address of every table row.
According to Oracle documentation, ROWID
is the fastest way to access a single row from a table.
See the @RowId
mapping section for more info.
25.2.71. @SelectBeforeUpdate
The @SelectBeforeUpdate
annotation is used to specify that the currently annotated entity state be selected from the database when determining whether to perform an update when the detached entity is reattached.
See the OptimisticLockType.DIRTY
mapping section for more info on how @SelectBeforeUpdate
works.
25.2.72. @Sort
The @Sort
annotation is deprecated. Use the Hibernate specific @SortComparator
or @SortNatural
annotations instead.
25.2.73. @SortComparator
The @SortComparator
annotation is used to specify a Comparator
for sorting the Set
/Map
in-memory.
See the @SortComparator
mapping section for more info.
25.2.74. @SortNatural
The @SortNatural
annotation is used to specify that the Set
/Map
should be sorted using natural sorting.
See the @SortNatural
mapping section for more info.
25.2.75. @Source
The @Source
annotation is used in conjunction with a @Version
timestamp entity attribute indicating
the SourceType
of the timestamp value.
The SourceType
offers two options:
- DB
-
Get the timestamp from the database.
- VM
-
Get the timestamp from the current JVM.
See the Database-generated version timestamp mapping section for more info.
25.2.76. @SQLDelete
The @SQLDelete
annotation is used to specify a custom SQL DELETE
statement for the currently annotated entity or collection.
See the Custom CRUD mapping section for more info.
25.2.77. @SQLDeleteAll
The @SQLDeleteAll
annotation is used to specify a custom SQL DELETE
statement when removing all elements of the currently annotated collection.
See the Custom CRUD mapping section for more info.
25.2.78. @SqlFragmentAlias
The @SqlFragmentAlias
annotation is used to specify an alias for a Hibernate @Filter
.
The alias (e.g. myAlias
) can then be used in the @Filter
condition
clause using the {alias}
(e.g. {myAlias}
) placeholder.
See the @SqlFragmentAlias
mapping section for more info.
25.2.79. @SQLInsert
The @SQLInsert
annotation is used to specify a custom SQL INSERT
statement for the currently annotated entity or collection.
See the Custom CRUD mapping section for more info.
25.2.80. @SQLUpdate
The @SQLUpdate
annotation is used to specify a custom SQL UPDATE
statement for the currently annotated entity or collection.
See the Custom CRUD mapping section for more info.
25.2.81. @Subselect
The @Subselect
annotation is used to specify an immutable and read-only entity using a custom SQL SELECT
statement.
See the Mapping the entity to a SQL query section for more info.
25.2.82. @Synchronize
The @Synchronize
annotation is usually used in conjunction with the @Subselect
annotation to specify the list of database tables used by the @Subselect
SQL query.
With this information in place, Hibernate will properly trigger an entity flush whenever a query targeting the @Subselect
entity is to be executed while the Persistence Context has scheduled some insert/update/delete actions against the database tables used by the @Subselect
SQL query.
Therefore, the @Synchronize
annotation prevents the derived entity from returning stale data when executing entity queries against the @Subselect
entity.
See the Mapping the entity to a SQL query section for more info.
25.2.83. @Table
The @Table
annotation is used to specify additional information to a JPA @Table
annotation, like custom INSERT
, UPDATE
or DELETE
statements or a specific FetchMode
.
See the @SecondaryTable
mapping section for more info about Hibernate-specific @Table
mapping.
25.2.85. @Target
The @Target
annotation is used to specify an explicit target implementation when the currently annotated association is using an interface type.
See the @Target
mapping section for more info.
25.2.86. @Tuplizer
The @Tuplizer
annotation is used to specify a custom tuplizer for the currently annotated entity or embeddable.
For entities, the tupelizer must implement the EntityTuplizer
interface.
For embeddables, the tupelizer must implement the ComponentTuplizer
interface.
See the @Tuplizer
mapping section for more info.
25.2.87. @Tuplizers
The @Tuplizers
annotation is used to group multiple @Tuplizer
annotations.
25.2.88. @Type
The @Type
annotation is used to specify the Hibernate @Type
used by the currently annotated basic attribute.
See the @Type
mapping section for more info.
25.2.89. @TypeDef
The @TypeDef
annotation is used to specify a @Type
definition which can later be reused for multiple basic attribute mappings.
See the @TypeDef
mapping section for more info.
25.2.91. @UpdateTimestamp
The @UpdateTimestamp
annotation is used to specify that the currently annotated timestamp attribute should be updated with the current JVM timestamp whenever the owning entity gets modified.
-
java.util.Date
-
java.util.Calendar
-
java.sql.Date
-
java.sql.Time
-
java.sql.Timestamp
See the @UpdateTimestamp
mapping section for more info.
25.2.92. @ValueGenerationType
The @ValueGenerationType
annotation is used to specify that the current annotation type should be used as a generator annotation type.
See the @ValueGenerationType
mapping section for more info.
25.2.93. @Where
The @Where
annotation is used to specify a custom SQL WHERE
clause used when fetching an entity or a collection.
See the @Where
mapping section for more info.
25.2.94. @WhereJoinTable
The @WhereJoinTable
annotation is used to specify a custom SQL WHERE
clause used when fetching a join collection table.
See the @WhereJoinTable
mapping section for more info.
26. Performance Tuning and Best Practices
Every enterprise system is unique. However, having a very efficient data access layer is a common requirement for many enterprise applications. Hibernate comes with a great variety of features that can help you tune the data access layer.
26.1. Schema management
Although Hibernate provides the update
option for the hibernate.hbm2ddl.auto
configuration property,
this feature is not suitable for a production environment.
An automated schema migration tool (e.g. Flyway, Liquibase) allows you to use any database-specific DDL feature (e.g. Rules, Triggers, Partitioned Tables). Every migration should have an associated script, which is stored on the Version Control System, along with the application source code.
When the application is deployed on a production-like QA environment, and the deployment worked as expected, then pushing the deployment to a production environment should be straightforward since the latest schema migration was already tested.
You should always use an automatic schema migration tool and have all the migration scripts stored in the Version Control System. |
26.2. Logging
Whenever you’re using a framework that generates SQL statements on your behalf, you have to ensure that the generated statements are the ones that you intended in the first place.
There are several alternatives to logging statements. You can log statements by configuring the underlying logging framework. For Log4j, you can use the following appenders:
### log just the SQL
log4j.logger.org.hibernate.SQL=debug
### log JDBC bind parameters ###
log4j.logger.org.hibernate.type=trace
log4j.logger.org.hibernate.type.descriptor.sql=trace
However, there are some other alternatives like using datasource-proxy or p6spy.
The advantage of using a JDBC Driver
or DataSource
proxy is that you can go beyond simple SQL logging:
-
statement execution time
-
JDBC batching logging
Another advantage of using a DataSource
proxy is that you can assert the number of executed statements at test time.
This way, you can have the integration tests fail when a N+1 query issue is automatically detected.
While simple statement logging is fine, using datasource-proxy or p6spy is even better. |
26.3. JDBC batching
JDBC allows us to batch multiple SQL statements and to send them to the database server into a single request. This saves database roundtrips, and so it reduces response time significantly.
Not only INSERT
and UPDATE
statements, but even DELETE
statements can be batched as well.
For INSERT
and UPDATE
statements, make sure that you have all the right configuration properties in place, like ordering inserts and updates and activating batching for versioned data.
Check out this article for more details on this topic.
For DELETE
statements, there is no option to order parent and child statements, so cascading can interfere with the JDBC batching process.
Unlike any other framework which doesn’t automate SQL statement generation, Hibernate makes it very easy to activate JDBC-level batching as indicated in the Batching chapter.
26.4. Mapping
Choosing the right mappings is very important for a high-performance data access layer. From the identifier generators to associations, there are many options to choose from, yet not all choices are equal from a performance perspective.
26.4.1. Identifiers
When it comes to identifiers, you can either choose a natural id or a synthetic key.
For natural identifiers, the assigned identifier generator is the right choice.
For synthetic keys, the application developer can either choose a randomly generated fixed-size sequence (e.g. UUID) or a natural identifier. Natural identifiers are very practical, being more compact than their UUID counterparts, so there are multiple generators to choose from:
-
IDENTITY
-
SEQUENCE
-
TABLE
Although the TABLE
generator addresses the portability concern, in reality, it performs poorly because it requires emulating a database sequence using a separate transaction and row-level locks.
For this reason, the choice is usually between IDENTITY
and SEQUENCE
.
If the underlying database supports sequences, you should always use them for your Hibernate entity identifiers. Only if the relational database does not support sequences (e.g. MySQL 5.7), you should use the |
If you’re using the SEQUENCE
generator, then you should be using the enhanced identifier generators that were enabled by default in Hibernate 5.
The pooled and the pooled-lo optimizers are very useful to reduce the number of database roundtrips when writing multiple entities per database transaction.
26.4.2. Associations
JPA offers four entity association types:
-
@ManyToOne
-
@OneToOne
-
@OneToMany
-
@ManyToMany
And an @ElementCollection
for collections of embeddables.
Because object associations can be bidirectional, there are many possible combinations of associations. However, not every possible association type is efficient from a database perspective.
The closer the association mapping is to the underlying database relationship, the better it will perform. On the other hand, the more exotic the association mapping, the better the chance of being inefficient. |
Therefore, the @ManyToOne
and the @OneToOne
child-side association are best to represent a FOREIGN KEY
relationship.
The parent-side @OneToOne
association requires bytecode enhancement
so that the association can be loaded lazily. Otherwise, the parent-side association is always fetched even if the association is marked with FetchType.LAZY
.
For this reason, it’s best to map @OneToOne
association using @MapsId
so that the PRIMARY KEY
is shared between the child and the parent entities.
When using @MapsId
, the parent-side association becomes redundant since the child-entity can be easily fetched using the parent entity identifier.
For collections, the association can be either:
-
unidirectional
-
bidirectional
For unidirectional collections, Set
s are the best choice because they generate the most efficient SQL statements.
Unidirectional List
s are less efficient than a @ManyToOne
association.
Bidirectional associations are usually a better choice because the @ManyToOne
side controls the association.
Embeddable collections (@ElementCollection
) are unidirectional associations, hence Set
s are the most efficient, followed by ordered List
s, whereas bags (unordered List
s) are the least efficient.
The @ManyToMany
annotation is rarely a good choice because it treats both sides as unidirectional associations.
For this reason, it’s much better to map the link table as depicted in the Bidirectional many-to-many with link entity lifecycle section.
Each FOREIGN KEY
column will be mapped as a @ManyToOne
association.
On each parent-side, a bidirectional @OneToMany
association is going to map to the aforementioned @ManyToOne
relationship in the link entity.
Just because you have support for collections, it does not mean that you have to turn any one-to-many database relationship into a collection. Sometimes, a |
26.5. Inheritance
JPA offers SINGLE_TABLE
, JOINED
, and TABLE_PER_CLASS
to deal with inheritance mapping, and each of these strategies has advantages and disadvantages.
-
SINGLE_TABLE
performs the best in terms of executed SQL statements. However, you cannot useNOT NULL
constraints on the column-level. You can still use triggers and rules to enforce such constraints, but it’s not as straightforward. -
JOINED
addresses the data integrity concerns because every subclass is associated with a different table. Polymorphic queries or@OneToMany
base class associations don’t perform very well with this strategy. However, polymorphic@ManyToOne
associations are fine, and they can provide a lot of value. -
TABLE_PER_CLASS
should be avoided since it does not render efficient SQL statements.
26.6. Fetching
Fetching too much data is the number one performance issue for the vast majority of JPA applications. |
Hibernate supports both entity queries (JPQL/HQL and Criteria API) and native SQL statements. Entity queries are useful only if you need to modify the fetched entities, therefore benefiting from the automatic dirty checking mechanism.
For read-only transactions, you should fetch DTO projections because they allow you to select just as many columns as you need to fulfill a certain business use case. This has many benefits like reducing the load on the currently running Persistence Context because DTO projections don’t need to be managed.
26.6.1. Fetching associations
Related to associations, there are two major fetch strategies:
-
EAGER
-
LAZY
EAGER
fetching is almost always a bad choice.
Prior to JPA, Hibernate used to have all associations as The |
So, EAGER
fetching is to be avoided. For this reason, it’s better if all associations are marked as LAZY
by default.
However, LAZY
associations must be initialized prior to being accessed. Otherwise, a LazyInitializationException
is thrown.
There are good and bad ways to treat the LazyInitializationException
.
The best way to deal with LazyInitializationException
is to fetch all the required associations prior to closing the Persistence Context.
The JOIN FETCH
directive is good for @ManyToOne
and OneToOne
associations, and for at most one collection (e.g. @OneToMany
or @ManyToMany
).
If you need to fetch multiple collections, to avoid a Cartesian Product, you should use secondary queries which are triggered either by navigating the LAZY
association or by calling Hibernate#initialize(Object proxy)
method.
26.7. Caching
Hibernate has two caching layers:
-
the first-level cache (Persistence Context) which provides application-level repeatable reads.
-
the second-level cache which, unlike application-level caches, doesn’t store entity aggregates but normalized dehydrated entity entries.
The first-level cache is not a caching solution "per se", being more useful for ensuring READ COMMITTED
isolation level.
While the first-level cache is short-lived, being cleared when the underlying EntityManager
is closed, the second-level cache is tied to an EntityManagerFactory
.
Some second-level caching providers offer support for clusters. Therefore, a node needs only to store a subset of the whole cached data.
Although the second-level cache can reduce transaction response time since entities are retrieved from the cache rather than from the database, there are other options to achieve the same goal, and you should consider these alternatives prior to jumping to a second-level cache layer:
-
tuning the underlying database cache so that the working set fits into memory, therefore reducing Disk I/O traffic.
-
optimizing database statements through JDBC batching, statement caching, indexing can reduce the average response time, therefore increasing throughput as well.
-
database replication is also a very valuable option to increase read-only transaction throughput.
After properly tuning the database, to further reduce the average response time and increase the system throughput, application-level caching becomes inevitable.
Typically, a key-value application-level cache like Memcached or Redis is a common choice to store data aggregates. If you can duplicate all data in the key-value store, you have the option of taking down the database system for maintenance without completely losing availability since read-only traffic can still be served from the cache.
One of the main challenges of using an application-level cache is ensuring data consistency across entity aggregates. That’s where the second-level cache comes to the rescue. Being tightly integrated with Hibernate, the second-level cache can provide better data consistency since entries are cached in a normalized fashion, just like in a relational database. Changing a parent entity only requires a single entry cache update, as opposed to cache entry invalidation cascading in key-value stores.
The second-level cache provides four cache concurrency strategies:
-
READ_ONLY
-
NONSTRICT_READ_WRITE
-
READ_WRITE
-
TRANSACTIONAL
READ_WRITE
is a very good default concurrency strategy since it provides strong consistency guarantees without compromising throughput.
The TRANSACTIONAL
concurrency strategy uses JTA. Hence, it’s more suitable when entities are frequently modified.
Both READ_WRITE
and TRANSACTIONAL
use write-through caching, while NONSTRICT_READ_WRITE
is a read-through caching strategy.
For this reason, NONSTRICT_READ_WRITE
is not very suitable if entities are changed frequently.
When using clustering, the second-level cache entries are spread across multiple nodes.
When using Infinispan distributed cache, only READ_WRITE
and NONSTRICT_READ_WRITE
are available for read-write caches.
Bear in mind that NONSTRICT_READ_WRITE
offers a weaker consistency guarantee since stale updates are possible.
For more about Hibernate Performance Tuning, check out the High-Performance Hibernate presentation from Devoxx France. |
27. Legacy Bootstrapping
The legacy way to bootstrap a SessionFactory is via the org.hibernate.cfg.Configuration
object.
Configuration
represents, essentially, a single point for specifying all aspects of building the SessionFactory
: everything from settings, to mappings, to strategies, etc.
I like to think of Configuration
as a big pot to which we add a bunch of stuff (mappings, settings, etc) and from which we eventually get a SessionFactory.
There are some significant drawbacks to the legacy bootstrapping mechanism which led to its deprecation and the development of the new approach, which is discussed in Native Bootstrapping.
|
You can obtain the Configuration
by instantiating it directly.
You then specify mapping metadata (XML mapping documents, annotated classes) that describe your applications object model and its mapping to a SQL database.
Configuration cfg = new Configuration()
// addResource does a classpath resource lookup
.addResource( "Item.hbm.xml" )
.addResource( "Bid.hbm.xml" )
// calls addResource using "/org/hibernate/auction/User.hbm.xml"
.addClass( org.hibernate.auction.User.class )
// parses Address class for mapping annotations
.addAnnotatedClass( Address.class )
// reads package-level (package-info.class) annotations in the named package
.addPackage( "org.hibernate.auction" )
.setProperty( "hibernate.dialect", "org.hibernate.dialect.H2Dialect" )
.setProperty( "hibernate.connection.datasource", "java:comp/env/jdbc/test" )
.setProperty( "hibernate.order_updates", "true" );
There are other ways to specify Configuration information, including:
-
Place a file named hibernate.properties in a root directory of the classpath
-
Pass an instance of java.util.Properties to
Configuration#setProperties
-
Via a
hibernate.cfg.xml
file -
System properties using Java
-Dproperty=value
28. Migration
Mapping Configuration methods to the corresponding methods in the new APIs.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29. Legacy Domain Model
hbm.xml
<!--
~ 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>.
-->
<version
column="version_column"
name="propertyName"
type="typename"
access="field|property|ClassName"
unsaved-value="null|negative|undefined"
generated="never|always"
insert="true|false"
node="element-name|@attribute-name|element/@attribute|."
/>
column |
The name of the column holding the version number. Optional, defaults to the property name. |
name |
The name of a property of the persistent class. |
type |
The type of the version number. Optional, defaults to |
access |
Hibernate’s strategy for accessing the property value. Optional, defaults to |
unsaved-value |
Indicates that an instance is newly instantiated and thus unsaved.
This distinguishes it from detached instances that were saved or loaded in a previous session.
The default value, |
generated |
Indicates that the version property value is generated by the database. Optional, defaults to |
insert |
Whether or not to include the |
hbm.xml
<!--
~ 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>.
-->
<timestamp
column="timestamp_column"
name="propertyName"
access="field|property|ClassName"
unsaved-value="null|undefined"
source="vm|db"
generated="never|always"
node="element-name|@attribute-name|element/@attribute|."
/>
column |
The name of the column which holds the timestamp. Optional, defaults to the property name |
name |
The name of a JavaBeans style property of Java type |
access |
The strategy Hibernate uses to access the property value. Optional, defaults to |
unsaved-value |
A version property which indicates that the instance is newly instantiated and unsaved.
This distinguishes it from detached instances that were saved or loaded in a previous session.
The default value of |
source |
Whether Hibernate retrieves the timestamp from the database or the current JVM. Database-based timestamps incur an overhead because Hibernate needs to query the database each time to determine the incremental next value. However, database-derived timestamps are safer to use in a clustered environment. Not all database dialects are known to support the retrieval of the database’s current timestamp. Others may also be unsafe for locking because of lack of precision. |
generated |
Whether the timestamp property value is generated by the database. Optional, defaults to |
30. Legacy Hibernate Criteria Queries
This appendix covers the legacy Hibernate New development should focus on the JPA javax.persistence.criteria.CriteriaQuery API.
Eventually, Hibernate-specific criteria features will be ported as extensions to the JPA |
Hibernate features an intuitive, extensible criteria query API.
30.1. Creating a Criteria
instance
The interface org.hibernate.Criteria
represents a query against a particular persistent class.
The Session
is a factory for Criteria
instances.
Criteria crit = sess.createCriteria( Cat.class );
crit.setMaxResults( 50 );
List cats = crit.list();
30.2. JPA vs. Hibernate entity name
When using the Session#createCriteria(String entityName)
or StatelessSession#createCriteria(String entityName)
,
the entityName means the fully-qualified name of the underlying entity and not the name denoted by the name
attribute of the JPA @Entity
annotation.
Considering you have the following entity:
@Entity(name = "ApplicationEvent")
public static class Event {
@Id
private Long id;
private String name;
}
If you provide the JPA entity name to a legacy Criteria query:
List<Event> events = entityManager.unwrap( Session.class )
.createCriteria( "ApplicationEvent" )
.list();
Hibernate is going to throw the following MappingException
:
org.hibernate.MappingException: Unknown entity: ApplicationEvent
On the other hand, the Hibernate entity name (the fully qualified class name) works just fine:
List<Event> events = entityManager.unwrap( Session.class )
.createCriteria( Event.class.getName() )
.list();
For more about this topic, check out the HHH-2597 JIRA issue.
30.3. Narrowing the result set
An individual query criterion is an instance of the interface org.hibernate.criterion.Criterion
.
The class org.hibernate.criterion.Restrictions
defines factory methods for obtaining certain built-in Criterion
types.
List cats = sess.createCriteria( Cat.class )
.add( Restrictions.like( "name", "Fritz%" ) )
.add( Restrictions.between( "weight", minWeight, maxWeight ) )
.list();
Restrictions can be grouped logically.
List cats = sess.createCriteria( Cat.class )
.add( Restrictions.like( "name", "Fritz%" ) )
.add( Restrictions.or(
Restrictions.eq( "age", new Integer(0) ),
Restrictions.isNull( "age" ) )
)
.list();
List cats = sess.createCriteria( Cat.class )
.add( Restrictions.in( "name", new String[] { "Fritz", "Izi", "Pk" } ) )
.add( Restrictions.disjunction()
.add( Restrictions.isNull("age") )
.add( Restrictions.eq("age", new Integer(0) ) )
.add( Restrictions.eq("age", new Integer(1) ) )
.add( Restrictions.eq("age", new Integer(2) ) )
) )
.list();
There are a range of built-in criterion types (Restrictions
subclasses).
One of the most useful Restrictions
allows you to specify SQL directly.
List cats = sess.createCriteria( Cat.class )
.add( Restrictions.sqlRestriction(
"lower({alias}.name) like lower(?)", "Fritz%", Hibernate.STRING )
)
.list();
The {alias}
placeholder will be replaced by the row alias of the queried entity.
You can also obtain a criterion from a Property
instance.
You can create a Property
by calling Property.forName()
:
Property age = Property.forName( "age" );
List cats = sess.createCriteria( Cat.class )
.add( Restrictions.disjunction()
.add( age.isNull() )
.add( age.eq( new Integer(0) ) )
.add( age.eq( new Integer(1) ) )
.add( age.eq( new Integer(2) ) )
) )
.add( Property.forName("name").in( new String[] { "Fritz", "Izi", "Pk" } ) )
.list();
30.4. Ordering the results
You can order the results using org.hibernate.criterion.Order
.
List cats = sess.createCriteria( Cat.class )
.add( Restrictions.like( "name", "F%" ) )
.addOrder( Order.asc( "name" ).nulls( NullPrecedence.LAST ) )
.addOrder( Order.desc( "age" ) )
.setMaxResults( 50 )
.list();
List cats = sess.createCriteria( Cat.class )
.add( Property.forName( "name" ).like( "F%" ) )
.addOrder( Property.forName( "name" ).asc() )
.addOrder( Property.forName( "age" ).desc() )
.setMaxResults( 50 )
.list();
30.5. Associations
By navigating associations using createCriteria()
, you can specify constraints upon related entities:
List cats = sess.createCriteria( Cat.class )
.add( Restrictions.like( "name", "F%" ) )
.createCriteria( "kittens" )
.add( Restrictions.like( "name", "F%" ) )
.list();
The second createCriteria()
returns a new instance of Criteria
that refers to the elements of the kittens
collection.
There is also an alternate form that is useful in certain circumstances:
List cats = sess.createCriteria( Cat.class )
.createAlias( "kittens", "kt" )
.createAlias( "mate", "mt" )
.add( Restrictions.eqProperty( "kt.name", "mt.name" ) )
.list();
Note that createAlias()
does not create a new instance of Criteria
.
The kittens collections held by the Cat
instances returned by the previous two queries are not pre-filtered by the criteria.
If you want to retrieve just the kittens that match the criteria, you must use a ResultTransformer
.
List cats = sess.createCriteria( Cat.class )
.createCriteria( "kittens", "kt" )
.add( Restrictions.eq( "name", "F%" ) )
.setResultTransformer( Criteria.ALIAS_TO_ENTITY_MAP )
.list();
Iterator iter = cats.iterator();
while ( iter.hasNext() ) {
Map map = (Map) iter.next();
Cat cat = (Cat) map.get( Criteria.ROOT_ALIAS );
Cat kitten = (Cat) map.get( "kt" );
}
Additionally, you may manipulate the result set using a left outer join:
List cats = session.createCriteria( Cat.class )
.createAlias( "mate", "mt", Criteria.LEFT_JOIN, Restrictions.like( "mt.name", "good%" ) )
.addOrder( Order.asc( "mt.age" ) )
.list();
This will return all of the Cat
s with a mate whose name starts with "good" ordered by their mate’s age, and all cats who do not have a mate.
This is useful when there is a need to order or limit in the database prior to returning complex/large result sets,
and removes many instances where multiple queries would have to be performed and the results unioned by Java in memory.
Without this feature, firstly all of the cats without a mate would need to be loaded in one query. Then a second query would need to retrieve the cats with mates whose name started with "good" sorted by the mates age. Thirdly, in memory, the lists would need to be joined manually.
30.6. Dynamic association fetching
You can specify association fetching semantics at runtime using setFetchMode()
.
List cats = sess.createCriteria( Cat.class )
.add( Restrictions.like( "name", "Fritz%" ) )
.setFetchMode( "mate", FetchMode.EAGER )
.setFetchMode( "kittens", FetchMode.EAGER )
.list();
This query will fetch both mate
and kittens
by outer join.
30.7. Components
To add a restriction against a property of an embedded component, the component property name should be prepended to the property name when creating the Restriction
.
The criteria object should be created on the owning entity, and cannot be created on the component itself.
For example, suppose the Cat
has a component property fullName
with sub-properties firstName
and lastName
:
List cats = session.createCriteria( Cat.class )
.add( Restrictions.eq( "fullName.lastName", "Cattington" ) )
.list();
Note: this does not apply when querying collections of components, for that see Collections below.
30.8. Collections
When using criteria against collections, there are two distinct cases.
One is if the collection contains entities (e.g. <one-to-many/>
or <many-to-many/>
) or components (<composite-element/>
),
and the second is if the collection contains scalar values (<element/>
).
In the first case, the syntax is as given above in the section Associations where we restrict the kittens
collection.
Essentially, we create a Criteria
object against the collection property and restrict the entity or component properties using that instance.
For querying a collection of basic values, we still create the Criteria
object against the collection,
but to reference the value, we use the special property "elements".
For an indexed collection, we can also reference the index property using the special property "indices".
List cats = session.createCriteria( Cat.class )
.createCriteria( "nickNames" )
.add( Restrictions.eq( "elements", "BadBoy" ) )
.list();
30.9. Example queries
The class org.hibernate.criterion.Example
allows you to construct a query criterion from a given instance.
Cat cat = new Cat();
cat.setSex( 'F' );
cat.setColor( Color.BLACK );
List results = session.createCriteria( Cat.class )
.add( Example.create( cat ) )
.list();
Version properties, identifiers and associations are ignored. By default, null valued properties are excluded.
You can adjust how the Example
is applied.
Example example = Example.create( cat )
.excludeZeroes() //exclude zero valued properties
.excludeProperty( "color" ) //exclude the property named "color"
.ignoreCase() //perform case insensitive string comparisons
.enableLike(); //use like for string comparisons
List results = session.createCriteria( Cat.class )
.add( example )
.list();
You can even use examples to place criteria upon associated objects.
List results = session.createCriteria( Cat.class )
.add( Example.create( cat ) )
.createCriteria( "mate" )
.add( Example.create( cat.getMate() )
)
.list();
30.10. Projections, aggregation and grouping
The class org.hibernate.criterion.Projections
is a factory for Projection
instances.
You can apply a projection to a query by calling setProjection()
.
List results = session.createCriteria( Cat.class )
.setProjection( Projections.rowCount() )
.add( Restrictions.eq( "color", Color.BLACK ) )
.list();
List results = session.createCriteria( Cat.class )
.setProjection( Projections.projectionList()
.add( Projections.rowCount() )
.add( Projections.avg( "weight" ) )
.add( Projections.max( "weight" ) )
.add( Projections.groupProperty( "color" ) )
)
.list();
There is no explicit "group by" necessary in a criteria query.
Certain projection types are defined to be grouping projections, which also appear in the SQL group by
clause.
An alias can be assigned to a projection so that the projected value can be referred to in restrictions or orderings. Here are two different ways to do this:
List results = session.createCriteria( Cat.class )
.setProjection( Projections.alias( Projections.groupProperty( "color" ), "colr" ) )
.addOrder( Order.asc( "colr" ) )
.list();
List results = session.createCriteria( Cat.class )
.setProjection( Projections.groupProperty( "color" ).as( "colr" ) )
.addOrder( Order.asc( "colr" ) )
.list();
The alias()
and as()
methods simply wrap a projection instance in another, aliased, instance of Projection
.
As a shortcut, you can assign an alias when you add the projection to a projection list:
List results = session.createCriteria( Cat.class )
.setProjection( Projections.projectionList()
.add( Projections.rowCount(), "catCountByColor" )
.add( Projections.avg( "weight" ), "avgWeight" )
.add( Projections.max( "weight" ), "maxWeight" )
.add( Projections.groupProperty( "color" ), "color" )
)
.addOrder( Order.desc( "catCountByColor" ) )
.addOrder( Order.desc( "avgWeight" ) )
.list();
List results = session.createCriteria( Domestic.class, "cat" )
.createAlias( "kittens", "kit" )
.setProjection( Projections.projectionList()
.add( Projections.property( "cat.name" ), "catName" )
.add( Projections.property( "kit.name" ), "kitName" )
)
.addOrder( Order.asc( "catName" ) )
.addOrder( Order.asc( "kitName" ) )
.list();
You can also use Property.forName()
to express projections:
List results = session.createCriteria( Cat.class )
.setProjection( Property.forName( "name" ) )
.add( Property.forName( "color" ).eq( Color.BLACK ) )
.list();
List results = session.createCriteria( Cat.class )
.setProjection(Projections.projectionList()
.add( Projections.rowCount().as( "catCountByColor" ) )
.add( Property.forName( "weight" ).avg().as( "avgWeight" ) )
.add( Property.forName( "weight" ).max().as( "maxWeight" ) )
.add( Property.forName( "color" ).group().as( "color" ) )
)
.addOrder( Order.desc( "catCountByColor" ) )
.addOrder( Order.desc( "avgWeight" ) )
.list();
30.11. Detached queries and subqueries
The DetachedCriteria
class allows you to create a query outside the scope of a session and then execute it using an arbitrary Session
.
DetachedCriteria query = DetachedCriteria.forClass( Cat.class )
.add( Property.forName( "sex" ).eq( 'F' ) );
Session session = ....;
Transaction txn = session.beginTransaction();
List results = query.getExecutableCriteria( session ).setMaxResults( 100 ).list();
txn.commit();
session.close();
A DetachedCriteria
can also be used to express a subquery.
Criterion
instances involving subqueries can be obtained via Subqueries
or Property
.
DetachedCriteria avgWeight = DetachedCriteria.forClass( Cat.class )
.setProjection( Property.forName( "weight" ).avg() );
session.createCriteria( Cat.class )
.add( Property.forName( "weight" ).gt( avgWeight ) )
.list();
DetachedCriteria weights = DetachedCriteria.forClass( Cat.class )
.setProjection( Property.forName( "weight" ) );
session.createCriteria( Cat.class )
.add( Subqueries.geAll( "weight", weights ) )
.list();
Correlated subqueries are also possible:
DetachedCriteria avgWeightForSex = DetachedCriteria.forClass( Cat.class, "cat2" )
.setProjection( Property.forName( "weight" ).avg() )
.add( Property.forName( "cat2.sex" ).eqProperty( "cat.sex" ) );
session.createCriteria( Cat.class, "cat" )
.add( Property.forName( "weight" ).gt( avgWeightForSex ) )
.list();
Example of multi-column restriction based on a subquery:
DetachedCriteria sizeQuery = DetachedCriteria.forClass( Man.class )
.setProjection( Projections.projectionList()
.add( Projections.property( "weight" ) )
.add( Projections.property( "height" ) )
)
.add( Restrictions.eq( "name", "John" ) );
session.createCriteria( Woman.class )
.add( Subqueries.propertiesEq( new String[] { "weight", "height" }, sizeQuery ) )
.list();
30.12. Queries by natural identifier
For most queries, including criteria queries, the query cache is not efficient because query cache invalidation occurs too frequently. However, there is a special kind of query where you can optimize the cache invalidation algorithm: lookups by a constant natural key. In some applications, this kind of query occurs frequently. The Criteria API provides special provision for this use case.
First, map the natural key of your entity using <natural-id>
and enable use of the second-level cache.
<class name="User">
<cache usage="read-write"/>
<id name="id">
<generator class="increment"/>
</id>
<natural-id>
<property name="name"/>
<property name="org"/>
</natural-id>
<property name="password"/>
</class>
This functionality is not intended for use with entities with mutable natural keys.
Once you have enabled the Hibernate query cache, the Restrictions.naturalId()
allows you to make use of the more efficient cache algorithm.
session.createCriteria( User.class )
.add( Restrictions.naturalId()
.set( "name", "gavin" )
.set( "org", "hb" )
)
.setCacheable( true )
.uniqueResult();
31. Legacy Hibernate Native Queries
31.1. Legacy named SQL queries
Named SQL queries can also be defined during mapping and called in exactly the same way as a named HQL query.
In this case, you do not need to call addEntity()
anymore.
<sql-query>
mapping element<sql-query name = "persons">
<return alias="person" class="eg.Person"/>
SELECT person.NAME AS {person.name},
person.AGE AS {person.age},
person.SEX AS {person.sex}
FROM PERSON person
WHERE person.NAME LIKE :namePattern
</sql-query>
List people = session
.getNamedQuery( "persons" )
.setParameter( "namePattern", namePattern )
.setMaxResults( 50 )
.list();
The <return-join>
element is use to join associations and the <load-collection>
element is used to define queries which initialize collections.
<sql-query name = "personsWith">
<return alias="person" class="eg.Person"/>
<return-join alias="address" property="person.mailingAddress"/>
SELECT person.NAME AS {person.name},
person.AGE AS {person.age},
person.SEX AS {person.sex},
address.STREET AS {address.street},
address.CITY AS {address.city},
address.STATE AS {address.state},
address.ZIP AS {address.zip}
FROM PERSON person
JOIN ADDRESS address
ON person.ID = address.PERSON_ID AND address.TYPE='MAILING'
WHERE person.NAME LIKE :namePattern
</sql-query>
A named SQL query may return a scalar value.
You must declare the column alias and Hibernate type using the <return-scalar>
element:
<sql-query name = "mySqlQuery">
<return-scalar column = "name" type="string"/>
<return-scalar column = "age" type="long"/>
SELECT p.NAME AS name,
p.AGE AS age,
FROM PERSON p WHERE p.NAME LIKE 'Hiber%'
</sql-query>
You can externalize the resultset mapping information in a <resultset>
element which will allow you to either reuse them across several named queries or through the setResultSetMapping()
API.
<resultset name = "personAddress">
<return alias="person" class="eg.Person"/>
<return-join alias="address" property="person.mailingAddress"/>
</resultset>
<sql-query name = "personsWith" resultset-ref="personAddress">
SELECT person.NAME AS {person.name},
person.AGE AS {person.age},
person.SEX AS {person.sex},
address.STREET AS {address.street},
address.CITY AS {address.city},
address.STATE AS {address.state},
address.ZIP AS {address.zip}
FROM PERSON person
JOIN ADDRESS address
ON person.ID = address.PERSON_ID AND address.TYPE='MAILING'
WHERE person.NAME LIKE :namePattern
</sql-query>
You can, alternatively, use the resultset mapping information in your hbm files directly in Java code.
List cats = session
.createSQLQuery( "select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id" )
.setResultSetMapping( "catAndKitten" )
.list();
31.2. Legacy return-property to explicitly specify column/alias names
You can explicitly tell Hibernate what column aliases to use with <return-property>
, instead of using the {}
syntax to let Hibernate inject its own aliases.
For example:
<sql-query name = "mySqlQuery">
<return alias = "person" class = "eg.Person">
<return-property name = "name" column = "myName"/>
<return-property name = "age" column = "myAge"/>
<return-property name = "sex" column = "mySex"/>
</return>
SELECT person.NAME AS myName,
person.AGE AS myAge,
person.SEX AS mySex,
FROM PERSON person WHERE person.NAME LIKE :name
</sql-query>
<return-property>
also works with multiple columns.
This solves a limitation with the {}
syntax which cannot allow fine grained control of multi-column properties.
<sql-query name = "organizationCurrentEmployments">
<return alias = "emp" class = "Employment">
<return-property name = "salary">
<return-column name = "VALUE"/>
<return-column name = "CURRENCY"/>
</return-property>
<return-property name = "endDate" column = "myEndDate"/>
</return>
SELECT EMPLOYEE AS {emp.employee}, EMPLOYER AS {emp.employer},
STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate},
REGIONCODE as {emp.regionCode}, EID AS {emp.id}, VALUE, CURRENCY
FROM EMPLOYMENT
WHERE EMPLOYER = :id AND ENDDATE IS NULL
ORDER BY STARTDATE ASC
</sql-query>
In this example <return-property>
was used in combination with the {}
syntax for injection.
This allows users to choose how they want to refer column and properties.
If your mapping has a discriminator you must use <return-discriminator>
to specify the discriminator column.
31.3. Legacy stored procedures for querying
Hibernate provides support for queries via stored procedures and functions. Most of the following documentation is equivalent for both. The stored procedure/function must return a resultset as the first out-parameter to be able to work with Hibernate. An example of such a stored function in Oracle 9 and higher is as follows:
CREATE OR REPLACE FUNCTION selectAllEmployments
RETURN SYS_REFCURSOR
AS
st_cursor SYS_REFCURSOR;
BEGIN
OPEN st_cursor FOR
SELECT EMPLOYEE, EMPLOYER,
STARTDATE, ENDDATE,
REGIONCODE, EID, VALUE, CURRENCY
FROM EMPLOYMENT;
RETURN st_cursor;
END;
To use this query in Hibernate you need to map it via a named query.
<sql-query name = "selectAllEmployees_SP" callable = "true">
<return alias="emp" class="Employment">
<return-property name = "employee" column = "EMPLOYEE"/>
<return-property name = "employer" column = "EMPLOYER"/>
<return-property name = "startDate" column = "STARTDATE"/>
<return-property name = "endDate" column = "ENDDATE"/>
<return-property name = "regionCode" column = "REGIONCODE"/>
<return-property name = "id" column = "EID"/>
<return-property name = "salary">
<return-column name = "VALUE"/>
<return-column name = "CURRENCY"/>
</return-property>
</return>
{ ? = call selectAllEmployments() }
</sql-query>
Stored procedures currently only return scalars and entities.
<return-join>
and <load-collection>
are not supported.
31.4. Legacy rules/limitations for using stored procedures
You cannot use stored procedures with Hibernate unless you follow some procedure/function rules.
If they do not follow those rules they are not usable with Hibernate.
If you still want to use these procedures you have to execute them via session.doWork()
.
The rules are different for each database since database vendors have different stored procedure semantics/syntax.
Stored procedure queries cannot be paged with setFirstResult()/setMaxResults()
.
The recommended call form is standard SQL92: { ? = call functionName(<parameters>) }
or { ? = call procedureName(<parameters>}
.
Native call syntax is not supported.
For Oracle the following rules apply:
-
A function must return a result set.
-
The first parameter of a procedure must be an
OUT
that returns a result set. This is done by using aSYS_REFCURSOR
type in Oracle 9 or 10. In Oracle you need to define aREF CURSOR
type. See Oracle literature for further information.
For Sybase or MS SQL server the following rules apply:
-
The procedure must return a result set. Note that since these servers can return multiple result sets and update counts, Hibernate will iterate the results and take the first result that is a result set as its return value. Everything else will be discarded.
-
If you can enable
SET NOCOUNT ON
in your procedure it will probably be more efficient, but this is not a requirement.
31.5. Legacy custom SQL for create, update and delete
Hibernate can use custom SQL for create, update, and delete operations. The SQL can be overridden at the statement level or individual column level. This section describes statement overrides. For columns, see Column transformers: read and write expressions. The following example shows how to define custom SQL operations using annotations.
<class name = "Person">
<id name = "id">
<generator class = "increment"/>
</id>
<property name = "name" not-null = "true"/>
<sql-insert>INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )</sql-insert>
<sql-update>UPDATE PERSON SET NAME=UPPER(?) WHERE ID=?</sql-update>
<sql-delete>DELETE FROM PERSON WHERE ID=?</sql-delete>
</class>
If you expect to call a stored procedure, be sure to set the |
To check that the execution happens correctly, Hibernate allows you to define one of those three strategies:
-
none: no check is performed; the store procedure is expected to fail upon issues
-
count: use of rowcount to check that the update is successful
-
param: like COUNT but using an output parameter rather that the standard mechanism
To define the result check style, use the check
parameter which is again available in annotations as well as in xml.
Last but not least, stored procedures are in most cases required to return the number of rows inserted, updated and deleted. Hibernate always registers the first statement parameter as a numeric output parameter for the CUD operations:
CREATE OR REPLACE FUNCTION updatePerson (uid IN NUMBER, uname IN VARCHAR2)
RETURN NUMBER IS
BEGIN
update PERSON
set
NAME = uname,
where
ID = uid;
return SQL%ROWCOUNT;
END updatePerson;
31.6. Legacy custom SQL for loading
You can also declare your own SQL (or HQL) queries for entity loading. As with inserts, updates, and deletes, this can be done at the individual column level as described in For columns, see Column transformers: read and write expressions or at the statement level. Here is an example of a statement level override:
<sql-query name = "person">
<return alias = "pers" class = "Person" lock-mode= "upgrade"/>
SELECT NAME AS {pers.name}, ID AS {pers.id}
FROM PERSON
WHERE ID=?
FOR UPDATE
</sql-query>
This is just a named query declaration, as discussed earlier. You can reference this named query in a class mapping:
<class name = "Person">
<id name = "id">
<generator class = "increment"/>
</id>
<property name = "name" not-null = "true"/>
<loader query-ref = "person"/>
</class>
This even works with stored procedures.
You can even define a query for collection loading:
<set name = "employments" inverse = "true">
<key/>
<one-to-many class = "Employment"/>
<loader query-ref = "employments"/>
</set>
<sql-query name = "employments">
<load-collection alias = "emp" role = "Person.employments"/>
SELECT {emp.*}
FROM EMPLOYMENT emp
WHERE EMPLOYER = :id
ORDER BY STARTDATE ASC, EMPLOYEE ASC
</sql-query>
You can also define an entity loader that loads a collection by join fetching:
<sql-query name = "person">
<return alias = "pers" class = "Person"/>
<return-join alias = "emp" property = "pers.employments"/>
SELECT NAME AS {pers.*}, {emp.*}
FROM PERSON pers
LEFT OUTER JOIN EMPLOYMENT emp
ON pers.ID = emp.PERSON_ID
WHERE ID=?
</sql-query>
32. References
-
[PoEAA] Martin Fowler. Patterns of Enterprise Application Architecture. Addison-Wesley Publishing Company. 2003.
-
[JPwH] Christian Bauer & Gavin King. Java Persistence with Hibernate, Second Edition. Manning Publications Co. 2015.