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’s domain model is the central character in an ORM. They make up the classes you wish to map. Hibernate works best if these classes follow the Plain Old Java Object (POJO) / JavaBean programming model. However, none of these rules are hard requirements. Indeed, Hibernate assumes very little about the nature of your persistent objects. You can express a domain model in other ways (using trees of java.util.Map
instances, for example).
Historically applications using Hibernate would have used its proprietary XML mapping file format for this purpose. With the coming of JPA, most of this information is now defined in a way that is portable across ORM/JPA providers using annotations (and/or standardized XML format). This chapter will focus on JPA mapping where possible. For Hibernate mapping features not supported by JPA we will prefer Hibernate extension annotations.
2.1. Mapping types
Hibernate understands both the Java and JDBC representations of application data. The ability to read/write this data from/to the database is the function of a Hibernate type. A type, in this usage, is an implementation of the org.hibernate.type.Type
interface. This Hibernate type also describes various aspects of behavior of the Java type such as how to check for equality, how to clone values, etc.
The Hibernate type is neither a Java type nor a SQL data type. It provides information about both of these as well as understanding marshalling between. | |
---|---|
To help understand the type categorizations, let’s look at a simple table and domain model that we wish to map.
Example 1. Simple table and domain model
create table Contact (
id INTEGER NOT NULL,
first_name VARCHAR,
middle_name VARCHAR,
last_name VARCHAR,
notes VARCHAR,
starred BIT,
website VARCHAR
)
public class Contact {
private Integer id;
private Name name;
private String notes;
private URL website;
private boolean starred;
// getters and setters ommitted
}
public class Name {
private String first;
private String middle;
private String last;
// getters and setters ommitted
}
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 TypesEmbeddable types
the name attribute is an example of an embeddable type, which is discussed in details in Embeddable Types
Collection types
- although not featured n the aforementioned example, collection types are also a distinct category among value types. Collection types are further discussed in Collections
2.1.2. Entity types
Entities, by nature of their unique identifier, exist independently of other objects whereas values do not. Entities are domain model classes which correlate to rows in a database table, using a unique identifier. Because of the requirement for a unique identifier, entities exist independently and define their own lifecycle. The Contact
class itself would be an example of an entity.
Mapping entities is discussed in detail in Entity.
2.2. Naming strategies
Part of the mapping of an object model to the relational database is mapping names from the object model to the corresponding database names. Hibernate looks at this as 2 stage process:
The first stage is determining a proper logical name from the domain model mapping. A logical name can be either explicitly specified by the user (using
@Column
or@Table
e.g.) or it can be implicitly determined by Hibernate through an ImplicitNamingStrategy contract.Second is the resolving of this logical name to a physical name which is defined by the PhysicalNamingStrategy contract.
Historically Hibernate defined just a single org.hibernate.cfg.NamingStrategy . That singular NamingStrategy contract actually combined the separate concerns that are now modeled individually as ImplicitNamingStrategy and PhysicalNamingStrategy. |
|
---|---|
At the core, the idea behind each naming strategy is to minimize the amount of repetitive information a developer must provider for mapping a domain model.
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) | |
---|---|
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 strategylegacy-hbm
for
org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl
- compliant with the original Hibernate NamingStrategylegacy-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 the
org.hibernate.boot.model.naming.ImplicitNamingStrategy
contractFQN of a class that implements the the
org.hibernate.boot.model.naming.ImplicitNamingStrategy
contract
Secondly applications and integrations can leverage org.hibernate.boot.MetadataBuilder#applyImplicitNamingStrategy
to specify the ImplicitNamingStrategy to use. See
Bootstrap
for additional details on bootstrapping.
2.2.2. PhysicalNamingStrategy
Many organizations define rules around the naming of database objects (tables, columns, foreign-keys, etc). The idea of a PhysicalNamingStrategy is to help implement such naming rules without having to hard-code them into the mapping via explicit names.
While the purpose of an ImplicitNamingStrategy is to determine that an attribute named accountNumber
maps to a logical column name of accountNumber
when not explicitly specified, the purpose of a PhysicalNamingStrategy would be, for example, to say that the physical column name should instead be abbreviated acct_num
.
It is true that the resolution to acct_num could have been handled in an ImplicitNamingStrategy in this case. But the point is separation of concerns. The PhysicalNamingStrategy will be applied regardless of whether the attribute explicitly specified the column name or whether we determined that implicitly. The ImplicitNamingStrategy would only be applied if an explicit name was not given. So it depends on needs and intent. |
|
---|---|
The default implementation is to simply use the logical name as the physical name. However applications and integrations can define custom implementations of this PhysicalNamingStrategy contract. Here is an example PhysicalNamingStrategy for a fictitious company named Acme Corp whose naming standards are to:
prefer underscore-delimited words rather than camel-casing
replace certain words with standard abbreviations
Example 2. Example PhysicalNamingStrategy implementation
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.userguide.naming;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.apache.commons.lang3.StringUtils;
/**
* An example PhysicalNamingStrategy that implements database object naming standards
* for our fictitious company Acme Corp.
* <p/>
* In general Acme Corp prefers underscore-delimited words rather than camel casing.
* <p/>
* Additionally standards call for the replacement of certain words with abbreviations.
*
* @author Steve Ebersole
*/
public class AcmeCorpPhysicalNamingStrategy implements PhysicalNamingStrategy {
private static final Map<String,String> ABBREVIATIONS = buildAbbreviationMap();
@Override
public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment jdbcEnvironment) {
// Acme naming standards do not apply to catalog names
return name;
}
@Override
public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment jdbcEnvironment) {
// Acme naming standards do not apply to schema names
return null;
}
@Override
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) {
final List<String> parts = splitAndReplace( name.getText() );
return jdbcEnvironment.getIdentifierHelper().toIdentifier(
join( parts ),
name.isQuoted()
);
}
@Override
public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment jdbcEnvironment) {
final LinkedList<String> parts = splitAndReplace( name.getText() );
// Acme Corp says all sequences should end with _seq
if ( !"seq".equalsIgnoreCase( parts.getLast() ) ) {
parts.add( "seq" );
}
return jdbcEnvironment.getIdentifierHelper().toIdentifier(
join( parts ),
name.isQuoted()
);
}
@Override
public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment jdbcEnvironment) {
final List<String> parts = splitAndReplace( name.getText() );
return jdbcEnvironment.getIdentifierHelper().toIdentifier(
join( parts ),
name.isQuoted()
);
}
private static Map<String, String> buildAbbreviationMap() {
TreeMap<String,String> abbreviationMap = new TreeMap<> ( String.CASE_INSENSITIVE_ORDER );
abbreviationMap.put( "account", "acct" );
abbreviationMap.put( "number", "num" );
return abbreviationMap;
}
private LinkedList<String> splitAndReplace(String name) {
LinkedList<String> result = new LinkedList<>();
for ( String part : StringUtils.splitByCharacterTypeCamelCase( name ) ) {
if ( part == null || part.trim().isEmpty() ) {
// skip null and space
continue;
}
part = applyAbbreviationReplacement( part );
result.add( part.toLowerCase( Locale.ROOT ) );
}
return result;
}
private String applyAbbreviationReplacement(String word) {
if ( ABBREVIATIONS.containsKey( word ) ) {
return ABBREVIATIONS.get( word );
}
return word;
}
private String join(List<String> parts) {
boolean firstPass = true;
String separator = "";
StringBuilder joined = new StringBuilder();
for ( String part : parts ) {
joined.append( separator ).append( part );
if ( firstPass ) {
firstPass = false;
separator = "_";
}
}
return joined.toString();
}
}
There are multiple ways to specify the PhysicalNamingStrategy to use. First, applications can specify the implementation using the hibernate.physical_naming_strategy
configuration setting which accepts:
reference to a Class that implements the the
org.hibernate.boot.model.naming.PhysicalNamingStrategy
contractFQN of a class that implements the the
org.hibernate.boot.model.naming.PhysicalNamingStrategy
contract
Secondly applications and integrations can leverage org.hibernate.boot.MetadataBuilder#applyPhysicalNamingStrategy
. See
Bootstrap
for additional details on bootstrapping.
2.3. Basic Types
Basic value types usually map a single database column, to a single, non-aggregated Java type. Hibernate provides a number of built-in basic types, which follow the natural mappings recommended by the JDBC specifications.
Internally Hibernate uses a registry of basic types when it needs to resolve a specific org.hibernate.type.Type
.
2.3.1. Hibernate-provided BasicTypes
Table 1. Standard BasicTypes
Hibernate type (org.hibernate.type package) | JDBC type | Java type | BasicTypeRegistry key(s) |
---|---|---|---|
StringType | VARCHAR | java.lang.String | string, java.lang.String |
MaterializedClob | CLOB | java.lang.String | materialized_clob |
TextType | LONGVARCHAR | java.lang.String | text |
CharacterType | CHAR | char, java.lang.Character | char, java.lang.Character |
BooleanType | BIT | boolean, java.lang.Boolean | boolean, java.lang.Boolean |
NumericBooleanType | INTEGER, 0 is false, 1 is true | boolean, java.lang.Boolean | numeric_boolean |
YesNoType | CHAR, 'N'/'n' is false, 'Y'/'y' is true. The uppercase value is written to the database. | boolean, java.lang.Boolean | yes_no |
TrueFalseType | CHAR, 'F'/'f' is false, 'T'/'t' is true. The uppercase value is written to the database. | boolean, java.lang.Boolean | true_false |
ByteType | TINYINT | byte, java.lang.Byte | byte, java.lang.Byte |
ShortType | SMALLINT | short, java.lang.Short | short, java.lang.Short |
IntegerTypes | INTEGER | int, java.lang.Integer | int, java.lang.Integer |
LongType | BIGINT | long, java.lang.Long | long, java.lang.Long |
FloatType | FLOAT | float, java.lang.Float | float, java.lang.Float |
DoubleType | DOUBLE | double, java.lang.Double | double, java.lang.Double |
BigIntegerType | NUMERIC | java.math.BigInteger | big_integer, java.math.BigInteger |
BigDecimalType | NUMERIC | java.math.BigDecimal | big_decimal, java.math.bigDecimal |
TimestampType | TIMESTAMP | java.sql.Timestamp | timestamp, java.sql.Timestamp |
TimeType | TIME | java.sql.Time | time, java.sql.Time |
DateType | DATE | java.sql.Date | date, java.sql.Date |
CalendarType | TIMESTAMP | java.util.Calendar | calendar, java.util.Calendar |
CalendarDateType | DATE | java.util.Calendar | calendar_date |
CalendarTimeType | TIME | java.util.Calendar | calendar_time |
CurrencyType | java.util.Currency | VARCHAR | currency, java.util.Currency |
LocaleType | VARCHAR | java.util.Locale | locale, java.utility.locale |
TimeZoneType | VARCHAR, using the TimeZone ID | java.util.TimeZone | timezone, java.util.TimeZone |
UrlType | VARCHAR | java.net.URL | url, java.net.URL |
ClassType | VARCHAR (class FQN) | java.lang.Class | class, java.lang.Class |
BlobType | BLOB | java.sql.Blob | blog, java.sql.Blob |
ClobType | CLOB | java.sql.Clob | clob, java.sql.Clob |
BinaryType | VARBINARY | byte[] | binary, byte[] |
MaterializedBlobType | BLOB | byte[] | materized_blob |
ImageType | LONGVARBINARY | byte[] | image |
WrapperBinaryType | VARBINARY | java.lang.Byte[] | wrapper-binary, Byte[], java.lang.Byte[] |
CharArrayType | VARCHAR | char[] | characters, char[] |
CharacterArrayType | VARCHAR | java.lang.Character[] | wrapper-characters, Character[], java.lang.Character[] |
UUIDBinaryType | BINARY | java.util.UUID | uuid-binary, java.util.UUID |
UUIDCharType | CHAR, can also read VARCHAR | java.util.UUID | uuid-char |
PostgresUUIDType | PostgreSQL UUID, through Types#OTHER, which complies to the PostgreSQL JDBC driver definition | java.util.UUID | pg-uuid |
SerializableType | VARBINARY | implementors of java.lang.Serializable | Unlike the other value types, multiple instances of this type are registered. It is registered once under java.io.Serializable, and registered under the specific java.io.Serializable implementation class names. |
StringNVarcharType | NVARCHAR | java.lang.String | nstring |
NTextType | LONGNVARCHAR | java.lang.String | ntext |
NClobType | NCLOB | java.sql.NClob | nclob, java.sql.NClob |
MaterializedNClobType | NCLOB | java.lang.String | materialized_nclob |
PrimitiveCharacterArrayNClobType | NCHAR | char[] | N/A |
CharacterNCharType | NCHAR | java.lang.Character | ncharacter |
CharacterArrayNClobType | NCLOB | java.lang.Character[] | N/A |
Table 2. BasicTypes added by hibernate-java8
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 |
OffsetTimeType | TIMESTAMP | java.time.ZonedDateTime | ZonedDateTime, java.time.ZonedDateTime |
To use these hibernate-java8 types just add the hibernate-java8 dependency to your classpath and Hibernate will take care of the rest. See Mapping Date/Time Values for more about Java 8 Date/Time types. |
|
---|---|
These mappings are managed by a service inside Hibernate called the org.hibernate.type.BasicTypeRegistry
, which essentially maintains a map of org.hibernate.type.BasicType
(a org.hibernate.type.Type
specialization) instances keyed by a name. That is the purpose of the "BasicTypeRegistry key(s)" column in the previous tables.
2.3.2. The @Basic
annotation
Strictly speaking, a basic type is denoted with with the javax.persistence.Basic
annotation. Generally speaking the @Basic
annotation can be ignored, as it is assumed by default. Both of the following examples are ultimately the same.
Example 3. With @Basic
@Entity
public class Product {
@Id
@Basic
private Integer id;
@Basic
private String sku;
@Basic
private String name;
@Basic
private String description;
}
Example 4. Without @Basic
@Entity
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: | |
---|---|
The @Basic
annotation defines 2 attributes.
optional
- boolean (defaults to true)Defines whether this attribute allows nulls. JPA defines this as "a hint", which essentially means that it effect is specifically required. As long as the type is not primitive, Hibernate takes this to mean that the underlying column should be
NULLABLE
.fetch
- FetchType (defaults to EAGER)- Defines whether this attribute should be fetched eagerly or lazily. JPA says that EAGER is a requirement to the provider (Hibernate) that the value should be fetched when the owner is fetched, while LAZY is merely a hint that the value be fetched when the attribute is accessed. Hibernate ignores this setting for basic types unless you are using bytecode enhancement. See the
> for additional information on fetching and on bytecode enhancement.
2.3.3. The @Column
annotation
JPA defines rules for implicitly determining the name of tables and columns. For a detailed discussion of implicit naming see Naming.
For basic type attributes, the implicit naming rule is that the column name is the same as the attribute name. If that implicit naming rule does not meet your requirements, you can explicitly tell Hibernate (and other providers) the column name to use.
Example 5. Explicit column naming
@Entity
public class Product {
@Id
@Basic
private Integer id;
@Basic
private String sku;
@Basic
private String name;
@Basic
@Column( name = "NOTES" )
private String description;
}
Here we use @Column
to explicitly map the description
attribute to the NOTES
column, as opposed to the implicit column name description
.
The @Column
annotation defines other mapping information as well. See its javadocs for details.
2.3.4. BasicTypeRegistry
We said before that a Hibernate type is not a Java type, nor a SQL type, but that it understands both and performs the marshalling between them. But looking at the basic type mappings from the previous examples, how did Hibernate know to use its org.hibernate.type.StringType
for mapping for java.lang.String
attributes, or its org.hibernate.type.IntegerType
for mapping java.lang.Integer
attributes?
The answer lies in a service inside Hibernate called the org.hibernate.type.BasicTypeRegistry
, which essentially maintains a map of org.hibernate.type.BasicType
(a org.hibernate.type.Type
specialization) instances keyed by a name.
We will see later, in the Explicit BasicTypes section, that we can explicitly tell Hibernate which BasicType to use for a particular attribute. But first let’s explore how implicit resolution works and how applications can adjust implicit resolution.
A thorough discussion of the BasicTypeRegistry and all the different ways to contribute types to it is beyond the scope of this documentation. Please see Integrations Guide for complete details. |
|
---|---|
As an example, take a String attribute such as we saw before with Product#sku. Since there was no explicit type mapping, Hibernate looks to the BasicTypeRegistry
to find the registered mapping for java.lang.String
. This goes back to the "BasicTypeRegistry key(s)" column we saw in the tables at the start of this chapter.
As a baseline within BasicTypeRegistry
, Hibernate follows the recommended mappings of JDBC for Java types. JDBC recommends mapping Strings to VARCHAR, which is the exact mapping that StringType
handles. So that is the baseline mapping within BasicTypeRegistry
for Strings.
Applications can also extend (add new BasicType
registrations) or override (replace an exiting BasicType
registration) using one of the MetadataBuilder#applyBasicType
methods or the MetadataBuilder#applyTypes
method during bootstrap. For more details, see Custom BasicTypes section.
2.3.5. Explicit BasicTypes
Sometimes you want a particular attribute to be handled differently. Occasionally Hibernate will implicitly pick a BasicType
that you do not want (and for some reason you do not want to adjust the BasicTypeRegistry
).
In these cases you must explicitly tell Hibernate the BasicType
to use, via the org.hibernate.annotations.Type
annotation.
Example 6. Using @org.hibernate.annotations.Type
@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
implementationAny 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.lang.BigInteger
to VARCHAR
columns, or support completely new types.
There are two approaches to developing a custom BasicType. As a means of illustrating the different approaches, let’s consider a use case where we need to support a class called Fizzywig
from a third party library. Let’s assume that Fizzywig naturally stores as a VARCHAR.
The first approach is to directly implement the BasicType interface.
Example 7. Custom BasicType implementation
public class FizzywigType1 implements org.hibernate.type.BasicType {
public static final FizzywigType1 INSTANCE = new FizzywigType1();
@Override
public String[] getRegistrationKeys() {
return new String[]{Fizzywig.class.getName()};
}
@Override
public int[] sqlTypes( Mapping mapping ) {
return new int[]{java.sql.Types.VARCHAR};
}
@Override
public Class getReturnedClass() {
return Money.class;
}
@Override
public Object nullSafeGet(
ResultSet rs,
String[] names,
SessionImplementor session,
Object owner) throws SQLException {
return Fizzwig.fromString(
StringType.INSTANCE.get( rs, names[0], sesson )
);
}
@Override
public void nullSafeSet(
PreparedStatement st,
Object value,
int index,
boolean[] settable,
SessionImplementor session) throws SQLException {
final String dbValue = value == null
? null
: (( Fizzywig ) value).asString();
StringType.INSTANCE.nullSafeSet( st, value, index, settable, session );
}
...
}
MetadataSources metadataSources = ...;
metadataSources.getMetaDataBuilder().applyBasicType( FizzwigType1.INSTANCE );
...
The second approach is to implement the UserType interface.
Example 8. Custom UserType implementation
public class FizzywigType2 implements org.hibernate.usertype.UserType {
public static final String KEYS = new String[]{Fizzywig.class.getName()};
public static final FizzywigType1 INSTANCE = new FizzywigType1();
@Override
public int[] sqlTypes( Mapping mapping ) {
return new int[]{java.sql.Types.VARCHAR};
}
@Override
public Class getReturnedClass() {
return Fizzywig.class;
}
@Override
public Object nullSafeGet(
ResultSet rs,
String[] names,
SessionImplementor session,
Object owner) throws SQLException {
return Fizzwig.fromString(
StringType.INSTANCE.get( rs, names[0], sesson )
);
}
@Override
public void nullSafeSet(
PreparedStatement st,
Object value,
int index,
SessionImplementor session) throws SQLException {
final String dbValue = value == null
? null
: (( Fizzywig ) value).asString();
StringType.INSTANCE.nullSafeSet( st, value, index, session );
}
...
}
MetadataSources metadataSources = ...;
metadataSources.getMetaDataBuilder().applyBasicType( FizzwigType2.KEYS,FizzwigType2.INSTANCE )
...
For additional information on developing and registering custom types, see the Hibernate Integration Guide.
2.3.7. Mapping enums
Hibernate supports the mapping of Java enums as basic value types in a number of different ways.
2.3.8. @Enumerated
The original JPA-compliant way to map enums was via the @Enumerated
and @MapKeyEnumerated
for map keys annotations which works on the principle that the enum values are stored according to one of 2 strategies indicated by javax.persistence.EnumType
:
ORDINAL
- stored according to the enum value’s ordinal position within the enum class, as indicated by java.lang.Enum#ordinalSTRING
- stored according to the enum value’s name, as indicated by java.lang.Enum#name
Example 9. @Enumerated(ORDINAL)
example
@Entity
public class Person {
...
@Enumerated
public Gender gender;
public static enum Gender {
MALE,
FEMALE
}
}
In the ORDINAL example, the gender column is defined as an (nullable) INTEGER type and would hold:
NULL
- null0
- MALE1
- FEMALE
Example 10. @Enumerated(STRING)
example
@Entity
public class Person {
...
@Enumerated( STRING )
public Gender gender;
public static enum Gender {
MALE,
FEMALE
}
}
In the STRING example, the gender column is defined as an (nullable) VARCHAR type and would hold:
NULL
- nullMALE
- MALEFEMALE
- FEMALE
2.3.9. AttributeConverter
You can also map enums in a JPA compliant way using a JPA 2.1 AttributeConverter. Let’s revisit the Gender enum example, but instead we want to store the more standardized 'M'
and 'F'
codes.
Example 11. Enum mapping with AttributeConverter example
public enum Gender {
MALE('M'),
FEMALE('F');
private final char code;
private 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...
}
public char getCode() {
return code;
}
}
@Entity
public class Person {
...
@Basic
@Convert( converter = GenderConverter.class )
public Gender gender;
}
@Converter
public class GenderConverter implements AttributeConverter<Character, Gender> {
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
- null'M'
- MALE'F'
- FEMALE
For additional details on using AttributeConverters, see JPA 2.1 AttributeConverters section.
Note that JPA explicitly disallows the use of an AttributeConverter with an attribute marked as @Enumerated
. So if using the AttributeConverter approach, be sure to not mark the attribute as @Enumerated
.
2.3.10. Custom type
You can also map enums using a Hibernate custom type mapping. Let’s again revisit the Gender enum example, this time using a custom Type to store the more standardized 'M'
and 'F'
codes.
Example 12. Enum mapping with custom Type example
import org.hibernate.type.descriptor.java.CharacterTypeDescriptor;
public enum Gender {
MALE('M'),
FEMALE('F');
private final char code;
private 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...
}
public char getCode() {
return code;
}
}
public static class GenderJavaTypeDescriptor extends AbstractTypeDescriptor<Gender> {
public static final GenderJavaTypeDescriptor INSTANCE = new GenderJavaTypeDescriptor();
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 CharacterTypeDescriptor.INSTANCE.wrap( value, options );
}
}
@Entity
public class Person {
...
@Basic
@Type( type = GenderType.class )
public Gender gender;
}
@Converter
public class GenderType extends AbstractSingleColumnStandardBasicType<Gender> {
public static final GenderType INSTANCE = new GenderType();
private GenderType() {
super(
CharTypeDescriptor.INSTANCE,
GenderJavaTypeDescriptor.INSTANCE
);
}
public String getName() {
return "gender";
}
@Override
protected boolean registerUnderJavaType() {
return true;
}
}
Again, the gender column is defined as a CHAR type and would hold:
NULL
- null'M'
- MALE'F'
- FEMALE
For additional details on using custom types, see Custom BasicTypes section..
2.3.11. Mapping LOBs
Mapping LOBs (database Large Objects) come in 2 forms, those using the JDBC locator types and those materializing the LOB data.
JDBC LOB locators exist to allow efficient access to the LOB data. They allow the JDBC driver to stream parts of the LOB data as needed, potentially freeing up memory space. However they can be unnatural to deal with and have certain limitations. For example, a LOB locator is only portably valid during the duration of the transaction in which it was obtained.
The idea of materialized LOBs is to trade-off the potential efficiency (not all drivers handle LOB data efficiently) for a more natural programming paradigm using familiar Java types such as String or byte[], etc for these LOBs.
Materialized deals with the entire LOB contents in memory, whereas LOB locators (in theory) allow streaming parts of the LOB contents into memory as needed.
The JDBC LOB locator types include:
java.sql.Blob
java.sql.Clob
java.sql.NClob
Mapping materialized forms of these LOB values would use more familiar Java types such as String, char[], byte[], etc. The trade off for "more familiar" is usually performance.
For a first look, let’s assume we have a CLOB column that we would like to map (NCLOB character LOB data will be covered in Mapping Nationalized Character Data section.
Example 13. CLOB - SQL
create table product(
...
description CLOB not null,
...
)
Let’s first map this using the JDBC locator.
Example 14. CLOB - locator mapping
@Entity
public class Product {
...
@Lob
@Basic
public Clob description;
...
}
We could also map a materialized form.
Example 15. CLOB - materialized mapping
@Entity
public class Product {
...
@Lob
@Basic
public String description;
...
}
How JDBC deals with LOB data varies from driver to driver. Hibernate tries to handle all these variances for you. However some drivers do not allow Hibernate to always do that in an automatic fashion (looking directly at you PostgreSQL JDBC drivers). In such cases you may have to do some extra to get LOBs working. Such discussions are beyond the scope of this guide however. | |
---|---|
We might even want the materialized data as a char array (for some crazy reason).
Example 16. CLOB - materialized char[] mapping
@Entity
public class Product {
...
@Lob
@Basic
public char[] description;
...
}
We’d map BLOB data in a similar fashion.
Example 17. BLOB - SQL
create table step(
...
instruction BLOB not null,
...
)
Let’s first map this using the JDBC locator.
Example 18. BLOB - locator mapping
@Entity
public class Step {
...
@Lob
@Basic
public Blob instructions;
...
}
We could also map a materialized BLOB form.
Example 19. BLOB - materialized mapping
@Entity
public class Step {
...
@Lob
@Basic
public byte[] instructions;
...
}
2.3.12. 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
To map a specific attribute to a nationalized variant data type, Hibernate defines the @Nationalized
annotation.
Example 20. NVARCHAR mapping
@Entity
public class Product {
...
@Basic
@Nationalized
public String description;
...
}
Example 21. NCLOB (locator) mapping
@Entity
public class Product {
...
@Lob
@Basic
@Nationalized
public NClob description;
// Clob also works, because NClob
// extends Clob. The db type is
// still NCLOB either way and
// handled as such
}
Example 22. NCLOB (materialized) mapping
@Entity
public class Product {
...
@Lob
@Basic
@Nationalized
public String description;
}
If you application and database are entirely nationalized you may instead want to enable nationalized character data as the default. You can do this via the hibernate.use_nationalized_character_data
setting or by calling MetadataBuilder#enableGlobalNationalizedCharacterDataSupport
during bootstrap.
2.3.13. Mapping UUID Values
Hibernate also allows you to map UUID values, again in a number of ways.
The default UUID mapping is as binary because it represents more efficient storage. However many applications prefer the readability of character storage. To switch the default mapping, simply call MetadataBuilder.applyBasicType( UUIDCharType.INSTANCE, UUID.class.getName() ) |
|
---|---|
2.3.14. UUID as binary
As mentioned, the default mapping for UUID attributes. Maps the UUID to a byte[]
using java.util.UUID#getMostSignificantBits
and java.util.UUID#getLeastSignificantBits
and stores that as BINARY data.
Chosen as the default simply because it is generally more efficient from storage perspective.
2.3.15. 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.16. PostgeSQL-specific UUID
When using one of the PostgreSQL Dialects, this becomes the default UUID mapping | |
---|---|
Maps the UUID using PostgreSQL’s specific UUID data type. The PostgreSQL JDBC driver chooses to map its UUID type to the OTHER
code. Note that this can cause difficulty as the driver chooses to map many different data types to OTHER.
2.3.17. UUID as identifier
Hibernate supports using UUID values as identifiers, and they can even be generated on user’s behalf. For details, see the discussion of generators in Identifier generators.
2.3.18. Mapping Date/Time Values
Hibernate allows various Java Date/Time classes to be mapped as persistent domain model entity properties. The SQL standard defines three Date/Time types:
- DATE
Represents a calendar date by storing years, months and days. The JDBC equivalent is
java.sql.Date
TIME
Represents the time of a day and it stores hours, minutes and seconds. The JDBC equivalent is
java.sql.Time
TIMESTAMP
- It stores both a DATE and a TIME plus nanoseconds. The JDBC equivalent is
java.sql.Timestamp
To avoid dependencies on the java.sql package, it’s common to use the java.util or java.time Date/Time classes instead. |
|
---|---|
While the java.sql
classes define a direct association to the SQL Date/Time data types, the java.util
or java.time
properties need to explicitly mark the SQL type correlation with the @Temporal
annotation. This way, a java.util.Date
or a java.util.Calendar
cn be mapped to either an SQL DATE
, TIME
or TIMESTAMP
type.
Considering the following entity:
Example 23. java.util.Date
mapping
@Entity
public class DateEvent {
@Id
@GeneratedValue
private Long id;
@Temporal(TemporalType.DATE)
private Date timestamp;
public DateEvent() {}
public DateEvent(Date timestamp) {
this.timestamp = timestamp;
}
public Long getId() {
return id;
}
public Date getTimestamp() {
return timestamp;
}
}
When persisting such entity:
DateEvent dateEvent = new DateEvent(new Date());
entityManager.persist(dateEvent);
Hibernate generates the following INSERT statement:
INSERT INTO DateEvent
( timestamp, id )
VALUES ( '2015-12-29', 1 )
Only the year, month and the day field were saved into the the database.
If we change the @Temporal
type to TIME:
@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 the @Temporal
type is set to TIMESTAMP:
@Temporal(TemporalType.TIMESTAMP)
private Date timestamp;
Hibernate will include both the DATE, the TIME and the nanoseconds in the INSERT statement:
INSERT INTO DateEvent
( timestamp, id )
VALUES ( '2015-12-29 16:54:04.544', 1 )
Just like the java.util.Date , the java.util.Calendar requires the @Temporal annotation in order to know what JDBC data type to be chosen: DATE, TIME or TIMESTAMP. If the java.util.Date marks a point in time, the java.util.Calendar takes into consideration the default Time Zone. |
|
---|---|
Mapping Java 8 Date/Time Values
Java 8 came with a new Date/Time API, offering support for instant dates, intervals, local and zoned Date/Time immutable instances, bundled in the java.time
package. Hibernate added support for the new Date/Time API in a new module, which must be included with the following Maven dependency:
Example 24. hibernate-java8
Maven dependency
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-java8</artifactId>
<version>${hibernate.version}</version>
</dependency>
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 Java 8 Date/Time classes and the SQL types is implicit, there is not need to specify the @Temporal annotation. Setting it on the java.time classes throws the following exception: |
|
---|---|
2.3.19. JPA 2.1 AttributeConverters
Although Hibernate has long been offering custom types, as a JPA 2.1 provider, it also supports AttributeConverter
s as well.
With a custom AttributeConverter
, the application developer can map a given JDBC type to an entity basic type.
In the following example, the java.util.Period
is going to be mapped to a VARCHAR
database column.
Example 25. java.util.Period
custom AttributeConverter
@Converter
public class PeriodStringConverter implements AttributeConverter<Period, String> {
@Override
public String convertToDatabaseColumn(Period attribute) {
return attribute.toString();
}
@Override
public Period convertToEntityAttribute(String dbData) {
return Period.parse(dbData);
}
}
To make use of this custom converter, the @Convert
annotation must decorate the entity attribute.
Example 26. Entity using the custom AttributeConverter
@Entity
public class Event {
@Id
@GeneratedValue
private Long id;
@Convert(converter = PeriodStringConverter.class)
private Period span;
public Event() {}
public Event(Period span) {
this.span = span;
}
public Long getId() {
return id;
}
public Period getSpan() {
return span;
}
}
When persisting such entity, Hibernate will do the type conversion based on the AttributeConverter
logic:
Example 27. Persisting entity using the custom AttributeConverter
INSERT INTO Event
( span, id )
VALUES ( 'P1Y2M3D', 1 )
2.3.20. 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. Hibernate will use the correct quotation style for the SQL Dialect
. This is usually double quotes, but the SQL Server uses brackets and MySQL uses backticks.
Example 28. Quoting column names
@Entity @Table(name="`Line Item`")
class LineItem {
@Id
@Column(name="`Item Id`")
private Integer id;
@Column(name="`Item #`")
private Integer itemNumber
}
2.3.21. 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 afterwards to retrieve the generated values.
Properties marked as generated must additionally be non-insertable and non-updateable. Only @Version
and @Basic
types can be marked as generated.
never
(the default)the given property value is not generated within the database.
insert
the given property value is generated on insert, but is not regenerated on subsequent updates. Properties like creationTimestamp fall into this category.
always
- the property value is generated both on insert and on update.
To mark a property as generated, use The Hibernate specific @Generated
annotation.
2.3.22. 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 this:
Example 29. @ColumnTransformer
example
@Entity
class CreditCard {
@Id
private Integer id;
@Column(name="credit_card_num")
@ColumnTransformer(
read="decrypt(credit_card_num)",
write="encrypt(?)"
)
private String creditCardNumber;
}
You can use the plural form @ColumnTransformers if more than one columns need to define either of these rules. |
|
---|---|
If a property uses more than one column, you must use the forColumn
attribute to specify which column, the expressions are targeting.
Example 30. @ColumnTransformer
forColumn
attribute usage
@Entity
class User {
@Id
private Integer id;
@Type(type="com.acme.type.CreditCardType")
@Columns( {
@Column(name="credit_card_num"),
@Column(name="exp_date")
})
@ColumnTransformer(
forColumn="credit_card_num",
read="decrypt(credit_card_num)",
write="encrypt(?)"
)
private CreditCard creditCard;
}
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.
2.3.23. 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)
Example 31. @Formula
mapping usage
@Formula("obj_length * obj_height * obj_width")
private long objectVolume;
The SQL fragment can be as complex as you want and even include subselects. | |
---|---|
2.3.24. Any
There is one more type of property mapping. The @Any
mapping defines a polymorphic association to classes from multiple tables. This type of mapping requires more than one column. The first column contains the type of the associated entity. The remaining columns contain the identifier.
It is impossible to specify a foreign key constraint for this kind of association. This is not the usual way of mapping polymorphic associations and you should use this only in special cases (e.g. audit logs, user session data, etc). | |
---|---|
The @Any
annotation describes the column holding the metadata information. To link the value of the metadata information and an actual entity type, the @AnyDef
and @AnyDefs
annotations are used. The metaType
attribute allows the application to specify a custom type that maps database column values to persistent classes that have identifier properties of the type specified by idType
. You must specify the mapping from values of the metaType to class names.
Example 32. @Any
mapping usage
@Any(
metaColumn = @Column( name = "property_type" ),
fetch=FetchType.EAGER
)
@AnyMetaDef(
idType = "integer",
metaType = "string",
metaValues = {
@MetaValue( value = "S", targetEntity = StringProperty.class ),
@MetaValue( value = "I", targetEntity = IntegerProperty.class )
}
)
@JoinColumn( name = "property_id" )
private Property mainProperty;
Note that @AnyDef
can be mutualized and reused. It is recommended to place it as a package metadata in this case.
Example 33. @AnyMetaDef
mapping usage
//on a package
@AnyMetaDef( name="property"
idType = "integer",
metaType = "string",
metaValues = {
@MetaValue( value = "S", targetEntity = StringProperty.class ),
@MetaValue( value = "I", targetEntity = IntegerProperty.class )
}
)
package org.hibernate.test.annotations.any;
//in a class
@Any(
metaDef="property",
metaColumn = @Column( name = "property_type" ),
fetch=FetchType.EAGER
)
@JoinColumn( name = "property_id" )
private Property mainProperty;
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 Name class that is a composition of first-name and last-name, or an Address class that is a composition of street, city, postal code, etc.
To avoid any confusion with the annotation that marks a given embeddable type, the annotation will be further referred as @Embeddable . |
|
---|---|
Example 34. Simple embeddable type example
@Embeddable
public class Name {
private String firstName;
private String middleName;
private String lastName;
...
}
@Embeddable
public class Address {
private String line1;
private String line2;
@Embedded
private ZipCode zipCode;
...
@Embeddable
public static class Zip {
private String postalCode;
private String plus4;
...
}
}
An embeddable type is another form of value type, and its lifecycle is bound to a parent entity type, therefore inheriting the attribute access from its parent (for details on attribute access, see Access strategies).
Embeddable types can be made up of basic values as well as associations, with the caveat that, when used as collection elements, they cannot define collections themselves.
2.4.1. Component / Embedded
Most often, embeddable types are used to group multiple basic type mappings and reuse them across several entities.
Example 35. Simple Embeddedable
@Entity
public class Person {
@Id
private Integer id;
@Embedded
private Name name;
...
}
JPA defines two terms for working with an embeddable type: @Embeddable and @Embedded . @Embeddable is used to describe the mapping type itself (e.g. Name ). @Embedded is for referencing a given embeddable type (e.g. person.name ). |
|
---|---|
So, the embeddable type is represented by the Name
class and the parent makes use of it through the person.name
object composition.
Example 36. Person table
create table Person (
id integer not null,
firstName VARCHAR,
middleName VARCHAR,
lastName VARCHAR,
...
)
The composed values are mapped to the same table as the parent table. Composition is part of good OO data modeling (idiomatic Java). In fact, that table could also be mapped by the following entity type instead.
Example 37. Alternative to embeddable type composition
@Entity
public class Person {
@Id
private Integer id;
private String firstName;
private String middleName;
private String lastName;
...
}
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
Example 38. Multiple embeddable types
@Entity
public class Contact {
@Id
private Integer id;
@Embedded
private Name name;
@Embedded
private Address homeAddress;
@Embedded
private Address mailingAddress;
@Embedded
private Address workAddress;
...
}
Although from an object-oriented perspective, it’s much more convenient to work with embeddable types, this example doesn’t work as-is. When the same embeddable type is included multiple times in the same parent entity type, the JPA specification demands setting the associated column names explicitly.
This requirement is due to how object properties are mapped to database columns. By default, JPA expects a database column having the same name with its associated object property. When including multiple embeddables, the implicit name-based mapping rule doesn’t work anymore because multiple object properties could end-up being mapped to the same database column.
We have a few options to handle this issue.
2.4.3. JPA’s AttributeOverride
JPA defines the @AttributeOverride
annotation to handle this scenario.
Example 39. JPA’s AttributeOverride
@Entity
public class Contact {
@Id
private Integer id;
@Embedded
private Name name;
@Embedded
@AttributeOverrides(
@AttributeOverride(
name = "line1",
column = @Column( name = "home_address_line1" ),
),
@AttributeOverride(
name = "line2",
column = @Column( name = "home_address_line2" )
),
@AttributeOverride(
name = "zipCode.postalCode",
column = @Column( name = "home_address_postal_cd" )
),
@AttributeOverride(
name = "zipCode.plus4",
column = @Column( name = "home_address_postal_plus4" )
)
)
private Address homeAddress;
@Embedded
@AttributeOverrides(
@AttributeOverride(
name = "line1",
column = @Column( name = "mailing_address_line1" ),
),
@AttributeOverride(
name = "line2",
column = @Column( name = "mailing_address_line2" )
),
@AttributeOverride(
name = "zipCode.postalCode",
column = @Column( name = "mailing_address_postal_cd" )
),
@AttributeOverride(
name = "zipCode.plus4",
column = @Column( name = "mailing_address_postal_plus4" )
)
)
private Address mailingAddress;
@Embedded
@AttributeOverrides(
@AttributeOverride(
name = "line1",
column = @Column( name = "work_address_line1" ),
),
@AttributeOverride(
name = "line2",
column = @Column( name = "work_address_line2" )
),
@AttributeOverride(
name = "zipCode.postalCode",
column = @Column( name = "work_address_postal_cd" )
),
@AttributeOverride(
name = "zipCode.plus4",
column = @Column( name = "work_address_postal_plus4" )
)
)
private Address workAddress;
...
}
This way, the mapping conflict is resolved by setting up explicit name-based property-column type mappings.
2.4.4. ImplicitNamingStrategy
This is a Hibernate specific feature. Users concerned with JPA provider portability should instead prefer explicit column naming with @AttributeOverride . |
|
---|---|
Hibernate naming strategies are covered in detail in Naming. However, for the purposes of this discussion, Hibernate has the capability to interpret implicit column names in a way that is safe for use with multiple embeddable types.
Example 40. Enabling embeddable type safe implicit naming
MetadataSources sources = ...;
sources.addAnnotatedClass( Address.class );
sources.addAnnotatedClass( Name.class );
sources.addAnnotatedClass( Contact.class );
Metadata metadata = sources.getMetadataBuilder().applyImplicitNamingStrategy( ImplicitNamingStrategyComponentPathImpl.INSTANCE )
...
.build();
create table Contact(
id integer not null,
name_firstName VARCHAR,
name_middleName VARCHAR,
name_lastName VARCHAR,
homeAddress_line1 VARCHAR,
homeAddress_line2 VARCHAR,
homeAddress_zipCode_postalCode VARCHAR,
homeAddress_zipCode_plus4 VARCHAR,
mailingAddress_line1 VARCHAR,
mailingAddress_line2 VARCHAR,
mailingAddress_zipCode_postalCode VARCHAR,
mailingAddress_zipCode_plus4 VARCHAR,
workAddress_line1 VARCHAR,
workAddress_line2 VARCHAR,
workAddress_zipCode_postalCode VARCHAR,
workAddress_zipCode_plus4 VARCHAR,
...
)
Now the "path" to attributes are used in the implicit column naming. You could even develop your own to do special implicit naming.
2.4.5. Collections of embeddable types
Collections of embeddable types are specifically value collections (as embeddable types are a value type). Value collections are covered in detail in Collections of value types.
2.4.6. Embeddable types as Map key
Embeddable types can also be used as Map
keys. This topic is converted in detail in Map - key.
2.4.7. Embeddable types as identifiers
Embeddable types can also be used as entity type identifiers. This usage is covered in detail in
Embeddable types that are used as collection entries, map keys or entity type identifiers cannot include their own collection mappings. | |
---|---|
2.5. Entity types
The entity type describes the mapping between the actual persistable domain model object and a database table row. To avoid any confusion with the annotation that marks a given entity type, the annotation will be further referred as @Entity . |
|
---|---|
2.5.1. POJO Models
Section 2.1 The Entity Class of the JPA 2.1 specification defines its requirements for an entity class. Applications that wish to remain portable across JPA providers should adhere to these requirements.
The entity class must be annotated with the
javax.persistence.Entity
annotation (or be denoted as such in XML mapping)The entity class must have a public or protected no-argument constructor. It may define additional constructors as well.
The entity class must be a top-level class.
An enum or interface may not be designated as an entity.
The entity class must not be final. No methods or persistent instance variables of the entity class may be final.
If an entity instance is to be used remotely as a detached object, the entity class must implement the
Serializable
interface.Both abstract and concrete classes can be entities. Entities may extend non-entity classes as well as entity classes, and non-entity classes may extend entity classes.
The persistent state of an entity is represented by instance variables, which may correspond to JavaBean-style properties. An instance variable must be directly accessed only from within the methods of the entity by the entity instance itself. The state of the entity is available to clients only through the entity’s accessor methods (getter/setter methods) or other business methods.
Hibernate, however, is not as strict in its requirements. The differences from the list above include:
The entity class must have a no-argument constructor, which may be public, protected or package visibility. It may define additional constructors as well.
The entity class need not be a top-level class.
Technically Hibernate can persist final classes or classes with final persistent state accessor (getter/setter) methods. However, it is generally not a good idea as doing so will stop Hibernate from being able to generate proxies for lazy-loading the entity.
Hibernate does not restrict the application developer from exposing instance variables and reference them from outside the entity class itself. The validity of such a paradigm, however, is debatable at best.
Let’s look at each requirement in detail.
2.5.2. Prefer non-final classes
A central feature of Hibernate is the ability to load lazily certain entity instance variables (attributes) via runtime proxies. This feature depends upon the entity class being non-final or else implementing an interface that declares all the attribute getters/setters. You can still persist final classes that do not implement such an interface with Hibernate, but you will not be able to use proxies for fetching lazy associations, therefore limiting your options for performance tuning. For the very same reason, you should also avoid declaring persistent attribute getters and setters as final.
Starting in 5.0 Hibernate offers a more robust version of bytecode enhancement as another means for handling lazy loading. Hibernate had some bytecode re-writing capabilities prior to 5.0 but they were very rudimentary. See the |
|
---|---|
2.5.3. Implement a no-argument constructor
The entity class should have a no-argument constructor. Both Hibernate and JPA require this.
JPA requires that this constructor be defined as public or protected. Hibernate, for the most part, does not care about the constructor visibility, as long as the system SecurityManager allows overriding the visibility setting. That said, the constructor should be defined with at least package visibility if you wish to leverage runtime proxy generation.
2.5.4. Declare getters and setters for persistent attributes
The JPA specification requires this, otherwise the model would prevent accessing the entity persistent state fields directly from outside the entity itself.
Although Hibernate does not require it, it is recommended to follow the JavaBean conventions and define getters and setters for entity persistent attributes. Nevertheless, you can still tell Hibernate to directly access the entity fields.
Attributes (whether fields or getters/setters) need not be declared public. Hibernate can deal with attributes declared with public, protected, package or private visibility. Again, if wanting to use runtime proxy generation for lazy loading, the getter/setter should grant access to at least package visibility.
2.5.5. Provide identifier attribute(s)
Historically this was considered optional. However, not defining identifier attribute(s) on the entity should be considered a deprecated feature that will be removed in an upcoming release. | |
---|---|
The identifier attribute does not necessarily need to be mapped to the column(s) that physically define the primary key. However, it should map to column(s) that can uniquely identify each row.
We recommend that you declare consistently-named identifier attributes on persistent classes and that you use a nullable (i.e., non-primitive) type. | |
---|---|
The placement of the @Id
annotation marks the persistence state access strategy.
Example 41. Identifier
@Id
private Integer id;
Hibernate offers multiple identifier generation strategies, see the Identifier Generators chapter for more about this topic.
2.5.6. Mapping the entity
The main piece in mapping the entity is the javax.persistence.Entity
annotation. The @Entity
annotation defines just one attribute name
which is used to give a specific entity name for use in JPQL queries. By default, the entity name represents the unqualified name of the entity class itself.
Example 42. Simple @Entity
@Entity
public class Simple {
...
}
An entity models a database table. The identifier uniquely identifies each row in that table. By default, the name of the table is assumed to be the same as the name of the entity. To explicitly give the name of the table or to specify other information about the table, we would use the javax.persistence.Table
annotation.
Example 43. Simple @Entity
with @Table
@Entity
@Table( catalog = "CRM", schema = "purchasing", name = "t_simple" )
public class Simple {
...
}
2.5.7. Implementing equals()
and hashCode()
Much of the discussion in this section deals with the relation of an entity to a Hibernate Session, whether the entity is managed, transient or detached. If you are unfamiliar with these topics, they are explained in the Persistence Context chapter. | |
---|---|
Whether to implement equals()
and hashCode()
methods in your domain model, let alone how to implement them, is a surprisingly tricky discussion when it comes to ORM.
There is really just one absolute case: a class that acts as an identifier must implement equals/hashCode based on the id value(s). Generally this is pertinent for user-defined classes used as composite identifiers. Beyond this one very specific use case and few others we will discuss below, you may want to consider not implementing equals/hashCode altogether.
So what’s all the fuss? Normally, most Java objects provide a built-in equals()
and hashCode()
based on the object’s identity, so each new object will be different from all others. This is generally what you want in ordinary Java programming. Conceptually however this starts to break down when you start to think about the possibility of multiple instances of a class representing the same data.
This is, in fact, exactly the case when dealing with data coming from a database. Every time we load a specific Person
from the database we would naturally get a unique instance. Hibernate, however, works hard to make sure that does not happen within a given Session
. In fact Hibernate guarantees equivalence of persistent identity (database row) and Java identity inside a particular session scope. So if we ask a Hibernate Session
to load that specific Person multiple times we will actually get back the same instance:
Example 44. Scope of identity
Session session=...;
Person p1 = session.get( Person.class,1 );
Person p2 = session.get( Person.class,1 );
// this evaluates to true
assert p1==p2;
Consider another example using a persistent java.util.Set
:
Example 45. Set usage with Session-scoped identity
Session session=...;
Club club = session.get( Club.class,1 );
Person p1 = session.get( Person.class,1 );
Person p2 = session.get( Person.class,1 );
club.getMembers().add( p1 );
club.getMembers().add( p2 );
// this evaluates to true
assert club.getMembers.size()==1;
However, the semantic changes when we mix instances loaded from different Sessions:
Example 46. Mixed Sessions
Session session1=...;
Session session2=...;
Person p1 = session1.get( Person.class,1 );
Person p2 = session2.get( Person.class,1 );
// this evaluates to false
assert p1==p2;
Session session1=...;
Session session2=...;
Club club = session1.get( Club.class,1 );
Person p1 = session1.get( Person.class,1 );
Person p2 = session2.get( Person.class,1 );
club.getMembers().add( p1 );
club.getMembers().add( p2 );
// this evaluates to ... well it depends
assert club.getMembers.size()==1;
Specifically the outcome in this last example will depend on whether the Person
class implemented equals/hashCode, and, if so, how.
Consider yet another case:
Example 47. Sets with transient entities
Session session=...;
Club club = session.get( Club.class,1 );
Person p1 = new Person(...);
Person p2 = new Person(...);
club.getMembers().add( p1 );
club.getMembers().add( p2 );
// this evaluates to ... again, it depends
assert club.getMembers.size()==1;
In cases where you will be dealing with entities outside of a Session (whether they be transient or detached), especially in cases where you will be using them in Java collections, you should consider implementing equals/hashCode.
A common initial approach is to use the entity’s identifier attribute as the basis for equals/hashCode calculations:
Example 48. Naive equals/hashCode implementation
@Entity
public class Person {
@Id
@GeneratedValue
private Integer id;
...
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
@Override
public boolean equals() {
if ( this == o ) {
return true;
}
if (!( o instanceof Person ) ) {
return false;
}
if ( id == null ) {
return false;
}
final Person other = ( Person ) o;
return id.equals( other.id );
}
}
It turns out that this still breaks when adding transient instance of Person
to a set as we saw in the last example:
Example 49. Still trouble
Session session=...;
session.getTransaction().begin();
Club club = session.get( Club.class,1 );
Person p1 = new Person(...);
Person p2 = new Person(...);
club.getMembers().add( p1 );
club.getMembers().add( p2 );
session.getTransaction().commit();
// will actually resolve to false!
assert club.getMembers().contains( p1 );
The issue here is a conflict between the use of generated identifier, the contract of `Set`` and the equals/hashCode implementations. Set says that the equals/hashCode value for an object should not change while the object is part of the Set. But that is exactly what happened here because the equals/hasCode are based on the (generated) id, which was not set until the
session.getTransaction().commit()` call.
Note that this is just a concern when using generated identifiers. If you are using assigned identifiers this will not be a problem, assuming the identifier value is assigned prior to adding to the Set
.
Another option is to force the identifier to be generated and set prior to adding to the Set
:
Example 50. Forcing identifier generation
Session session=...;
session.getTransaction().begin();
Club club = session.get( Club.class,1 );
Person p1 = new Person(...);
Person p2 = new Person(...);
session.save( p1 );
session.save( p2 );
session.flush();
club.getMembers().add( p1 );
club.getMembers().add( p2 );
session.getTransaction().commit();
// will actually resolve to false!
assert club.getMembers().contains( p1 );
But this is often not feasible.
The final approach is to use a "better" equals/hashCode implementation, making use of a natural-id or business-key.
Example 51. Better equals/hashCode with natural-id
@Entity
public class Person {
@Id
@GeneratedValue
private Integer id;
@NaturalId
private String ssn;
protected Person() {
// ctor for ORM
}
public Person( String ssn ) {
// ctor for app
this.ssn = ssn;
}
...
@Override
public int hashCode() {
assert ssn != null;
return ssn.hashCode();
}
@Override
public boolean equals() {
if ( this == o ) {
return true;
}
if (!( o instanceof Person ) ) {
return false;
}
final Person other = ( Person ) o;
assert ssn != null;
assert other.ssn != null;
return ssn.equals( other.ssn );
}
}
As you can see the question of equals/hashCode is not trivial, nor is there a one-size-fits-all solution.
For details on mapping the identifier, see the
Identifiers
chapter.
2.5.8. 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
Example 52. Version
@Entity
public class Course {
@Id
private Integer id;
@Version
private Integer version;
...
}
@Entity
public class Thing {
@Id
private Integer id;
@Version
private Timestamp ts;
...
}
@Entity
public class Thing2 {
@Id
private Integer id;
@Version
private Instant ts;
...
}
Hibernate supports a form of optimistic locking that does not require a dedicated "version attribute". This is intended mainly 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 org.hibernate.annotations.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 presentVERSION
(the default)performs optimistic locking based on a
@Version
as described aboveALL
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.
2.5.9. Access strategies
As a JPA provider, Hibernate can introspect both the entity attributes (instance fields) or the accessors (instance properties). By default, the placement of the @Id
annotation gives the default access strategy. When placed on a field, Hibernate will assume field-based access. Place on the identifier getter, Hibernate will use property-based access.
Embeddable types inherit the access strategy from their parent entities.
Field-based access
Example 53. Field-based access
@Entity
public class Simple {
@Id
private Integer id;
public Integer getId() {
return id;
}
public void setId( Integer id ) {
this.id = id;
}
}
When using field-based access, adding other entity-level methods is much more flexible because Hibernate won’t consider those part of the persistence state. To exclude a field from being part of the entity persistent state, the field must be marked with the @Transient
annotation.
Another advantage of using field-based access is that some entity attributes can be hidden from outside the entity. An example of such attribute is the entity @Version field, which must not be manipulated by the data access layer. With field-based access, we can simply omit the the getter and the setter for this version field, and Hibernate can still leverage the optimistic concurrency control mechanism. |
|
---|---|
Property-based access
Example 54. Property-based access
@Entity
public class Simple {
private Integer id;
@Id
public Integer getId() {
return id;
}
public void setId( Integer id ) {
this.id = id;
}
}
When using property-based access, Hibernate uses the accessors for both reading and writing the entity state. Every other method that will be added to the entity (e.g. helper methods for synchronizing both ends of a bidirectional one-to-many association) will have to be marked with the @Transient
annotation.
Overriding the default access strategy
The default access strategy mechanism can be overridden with the JPA @Access
annotation. In the following example, the @Version
attribute is accessed by its field and not by its getter, like the rest of entity attributes.
Example 55. Overriding access strategy
@Entity
public class Simple {
private Integer id;
@Version
@Access( AccessType.FIELD )
private Integer version;
@Id
public Integer getId() {
return id;
}
public void setId( Integer id ) {
this.id = id;
}
}
Embeddable types and access strategy
Because embeddables are managed by their owning entities, the access strategy is therefore inherited from the entity too. This applies to both simple embeddable types as well as for collection of embeddables.
The embeddable types can overrule the default implicit access strategy (inherited from the owning entity). In the following example, the embeddable uses property-based access, no matter what access strategy the owning entity is choosing:
Example 56. Embeddable with exclusive access strategy
@Embeddable
@Access(AccessType.PROPERTY)
public static class Change {
private String path;
private String diff;
public Change() {}
@Column(name = "path", nullable = false)
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
@Column(name = "diff", nullable = false)
public String getDiff() {
return diff;
}
public void setDiff(String diff) {
this.diff = diff;
}
}
The owning entity can use field-based access, while the embeddable uses property-based access as it has chosen explicitly:
Example 57. Entity including a single embeddable type
@Entity
public class Patch {
@Id
private Long id;
@Embedded
private Change change;
}
This works also for collection of embeddable types:
Example 58. Entity including a collection of embeddable types
@Entity
public class Patch {
@Id
private Long id;
@ElementCollection
@CollectionTable(
name="patch_change",
joinColumns=@JoinColumn(name="patch_id")
)
@OrderColumn(name = "index_id")
private List<Change> changes = new ArrayList<>();
public List<Change> getChanges() {
return changes;
}
}
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.
Values for simple identifiers can be assigned, as we have seen in the examples above. The expectation for assigned identifier values is that the application assigns (sets them on the entity attribute) prior to calling save/persist.
Example 59. Simple assigned identifier
@Entity
public class MyEntity {
@Id
public Integer id;
...
}
Values for simple identifiers can be generated. To denote that an identifier attribute is generated, it is annotated with javax.persistence.GeneratedValue
Example 60. Simple generated identifier
@Entity
public class MyEntity {
@Id
@GeneratedValue
public Integer id;
...
}
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 - aggregated (EmbeddedId)), or defined using thejavax.persistence.IdClass
annotation (see Composite identifiers - non-aggregated (IdClass)).The primary key class must be public and must have a public no-arg constructor.
The primary key class must be serializable.
The primary key class must define equals and hashCode methods, consistent with equality for the underlying database types to which the primary key is mapped.
The restriction that a composite identifier has to be represented by a "primary key class" is only JPA specific. Hibernate does allow composite identifiers to be defined without a "primary key class", although that modeling technique is deprecated and therefore omitted from this discussion. | |
---|---|
The attributes making up the composition can be either basic, composite, ManyToOne. Note especially that collections and one-to-ones are never appropriate.
2.6.3. Composite identifiers - aggregated (EmbeddedId)
Modelling a composite identifier using an EmbeddedId simply means defining an embeddable to be a composition for the the one or more attributes making up the identifier, and then exposing an attribute of that embeddable type on the entity.
Example 61. Basic EmbeddedId
@Entity
public class Login {
@EmbeddedId
private PK pk;
@Embeddable
public static class PK implements Serializable {
private String system;
private String username;
...
}
...
}
As mentioned before, EmbeddedIds can even contain ManyToOne attributes.
Example 62. EmbeddedId with ManyToOne
@Entity
public class Login {
@EmbeddedId
private PK pk;
@Embeddable
public static class PK implements Serializable {
@ManyToOne
private System system;
private String username;
...
}
...
}
Hibernate supports directly modeling the ManyToOne in the PK class, whether EmbeddedId or IdClass. However that is not portably supported by the JPA specification. In JPA terms one would use "derived identifiers"; for details, see Derived Identifiers. | |
---|---|
2.6.4. Composite identifiers - non-aggregated (IdClass)
Modelling a composite identifier using an IdClass differs from using an EmbeddedId in that the entity defines each individual attribute making up the composition. The IdClass simply acts as a "shadow".
Example 63. Basic IdClass
@Entity
@IdClass( PK.class )
public class Login {
@Id
private String system;
@Id
private String username;
public static class PK implements Serializable {
private String system;
private String username;
...
}
...
}
Non-aggregated composite identifiers can also contain ManyToOne attributes as we saw with aggregated ones (still non-portably)
Example 64. IdClass with ManyToOne
@Entity
@IdClass( PK.class )
public class Login {
@Id
@ManyToOne
private System system;
@Id
private String username;
public static class PK implements Serializable {
private System system;
private String username;
...
}
...
}
With non-aggregated composite identifiers, Hibernate also supports "partial" generation of the composite values.
Example 65. IdClass with partial generation
@Entity
@IdClass( PK.class )
public class LogFile {
@Id
private String name;
@Id
private LocalDate date;
@Id
@GeneratedValue
private Integer uniqueStamp;
public static class PK implements Serializable {
private String name;
private LocalDate date;
private Integer uniqueStamp;
...
}
...
}
This feature exists because of a highly questionable interpretation of the JPA specification made by the SpecJ committee. Hibernate does not feel that JPA defines support for this, but added the feature simply to be usable in SpecJ benchmarks. Use of this feature may or may not be portable from a JPA perspective. | |
---|---|
2.6.5. Composite identifiers - associations
Hibernate allows defining a composite identifier out of entity associations. In the following example, the PersonAddress
entity identifier is formed of two @ManyToOne
associations.
Example 66. Composite identifiers with associations
@Entity
public class PersonAddress implements Serializable {
@Id
@ManyToOne
private Person person;
@Id
@ManyToOne()
private Address address;
public PersonAddress() {}
public PersonAddress(Person person, Address address) {
this.person = person;
this.address = address;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@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
public class Person {
@Id
@GeneratedValue
private Long id;
@NaturalId
private String registrationNumber;
public Person() {}
public Person(String registrationNumber) {
this.registrationNumber = registrationNumber;
}
public Long getId() {
return id;
}
@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
public class Address {
@Id
@GeneratedValue
private Long id;
private String street;
private String number;
private String postalCode;
public Address() {}
public Address(String street, String number, String postalCode) {
this.street = street;
this.number = number;
this.postalCode = postalCode;
}
public Long getId() {
return id;
}
public String getStreet() {
return street;
}
public String getNumber() {
return number;
}
public String getPostalCode() {
return postalCode;
}
@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);
}
}
Although the mapping is much simpler than using an @EmbeddedId
or an @IdClass
, there’s no separation between the entity instance and the actual identifier. To query this entity, an instance of the entity itself must be supplied to the persistence context.
Example 67. Composite identifiers with associations query
PersonAddress personAddress = entityManager.find(
PersonAddress.class,
new PersonAddress( person, address )
);
2.6.6. Generated identifier values
For discussion of generated values for non-identifier attributes, see Generated properties | |
---|---|
Hibernate supports identifier value generation across a number of different types. Remember that JPA portably defines identifier value generation just for integer types.
Identifier value generation is indicates using the javax.persistence.GeneratedValue
annotation. The most important piece of information here is the specified javax.persistence.GenerationType
which indicates how values will be generated.
The discussions below assume that the application is using Hibernate’s "new generator mappings" as indicated by the hibernate.id.new_generator_mappings setting or MetadataBuilder.enableNewIdentifierGeneratorSupport method during bootstrap. Starting with Hibernate 5, this is set to true by default. If applications set this to false the resolutions discussed here will be very different. The rest of the discussion here assumes this setting is enabled (true). |
|
---|---|
AUTO
(the default)Indicates that the persistence provider (Hibernate) should chose 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 identifier table.
2.6.7. Interpreting AUTO
How a persistence provider interprets the AUTO generation type is left up to the provider. Hibernate interprets it in the following order:
If the given name matches the name for a
javax.persistence.SequenceGenerator
annotation then Using sequences.If the given name matches the name for a
javax.persistence.TableGenerator
annotation then Using identifier table.If the given name matches the name for a
org.hibernate.annotations.GenericGenerator
annotation then Using @GenericGenerator.
The fallback is to consult with the pluggable org.hibernate.boot.model.IdGeneratorStrategyInterpreter
contract, which is covered in detail in the Hibernate Integrations Guide. The default behavior is to look at the java type of the identifier attribute:
for UUID Using UUID generation
Otherwise Using sequences
2.6.8. Using sequences
For implementing database sequence-based identifier value generation Hibernate makes use of its org.hibernate.id.enhanced.SequenceStyleGenerator
id generator. It is important to note that SequenceStyleGenerator is capable of working against databases that do not support sequences by switching to a table as the underlying backing. This gives Hibernate a huge degree of portability across databases while still maintaining consistent id generation behavior (versus say choosing between sequence and IDENTITY). This backing storage is completely transparent to the user.
The preferred (and portable) way to configure this generator is using the JPA-defined javax.persistence.SequenceGenerator annotation.
The simplest form is to simply request sequence generation; Hibernate will use a single, implicitly-named sequence (hibernate_sequence
) for all such unnamed definitions.
Example 68. Unnamed sequence
@Entity
public class MyEntity {
@Id
@GeneratedValue( generation = SEQUENCE )
public Integer id;
...
}
Or a specifically named sequence can be requested
Example 69. Named sequence
@Entity
public class MyEntity {
@Id
@GeneratedValue( generation = SEQUENCE, name = "my_sequence" )
public Integer id;
...
}
Use javax.persistence.SequenceGenerator to specify additional configuration.
Example 70. Configured sequence
@Entity
public class MyEntity {
@Id
@GeneratedValue( generation = SEQUENCE, name = "my_sequence" )
@SequenceGenerator( name = "my_sequence", schema = "globals", allocationSize = 30 )
public Integer id;
...
}
2.6.9. Using IDENTITY columns
For implementing identifier value generation based on IDENTITY columns, Hibernate makes use of its org.hibernate.id.IdentityGenerator
id generator which expects the identifier to generated by INSERT into the table. IdentityGenerator understands 3 different ways that the INSERT-generated value might be retrieved:
If Hibernate believes the JDBC environment supports
java.sql.Statement#getGeneratedKeys
, then that approach will be used for extracting the IDENTITY generated keys.Otherwise, if
Dialect#supportsInsertSelectIdentity
reports true, Hibernate will use the Dialect specific INSERT+SELECT statement syntax.Otherwise, Hibernate will expect that the database supports some form of asking for the most recently inserted IDENTITY value via a separate SQL command as indicated by
Dialect#getIdentitySelectString
It is important to realize that this imposes a runtime behavior where the entity row must be physically inserted prior to the identifier value being known. This can mess up extended persistence contexts (conversations). Because of the runtime imposition/inconsistency Hibernate suggest other forms of identifier value generation be used. | |
---|---|
There is yet another important runtime impact of choosing IDENTITY generation: Hibernate will not be able to JDBC batching for inserts of the entities that use IDENTITY generation. The importance of this depends on the application’s specific use cases. If the application is not usually creating many new instances of a given type of entity that uses IDENTITY generation, then this is not an important impact since batching would not have been helpful anyway. | |
---|---|
2.6.10. Using identifier table
Hibernate achieves table-based identifier generation based on its org.hibernate.id.enhanced.TableGenerator
id generator which defines a table capable of holding multiple named value segments for any number of entities.
Example 71. Table generator table structure
create table hibernate_sequences(
sequence_name VARCHAR NOT NULL,
next_val INTEGER NOT NULL
)
The basic idea is that a given table-generator table (hibernate_sequences
for example) can hold multiple segments of identifier generation values.
Example 72. Unnamed table generator
@Entity
public class MyEntity {
@Id
@GeneratedValue( generation = TABLE )
public Integer id;
...
}
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.
2.6.11. Using UUID generation
As mentioned above, Hibernate supports UUID identifier value generation. This is supported through its org.hibernate.id.UUIDGenerator
id generator.
UUIDGenerator
supports pluggable strategies for exactly how the UUID is generated. These strategies are defined by the org.hibernate.id.UUIDGenerationStrategy
contract. The default strategy is a version 4 (random) strategy according to IETF RFC 4122. Hibernate does ship with an alternative strategy which is a RFC 4122 version 1 (time-based) strategy (using ip address rather than mac address).
Example 73. Implicitly using the random UUID strategy
@Entity
public class MyEntity {
@Id
@GeneratedValue
public UUID id;
...
}
To specify an alternative generation strategy, we’d have to define some configuration via @GenericGenerator
. Here we choose the RFC 4122 version 1 compliant strategy named org.hibernate.id.uuid.CustomVersionOneStrategy
Example 74. Implicitly using the random UUID strategy
@Entity
public class MyEntity {
@Id
@GeneratedValue( generator = "uuid" )
@GenericGenerator(
name = "uuid",
strategy = "org.hibernate.id.UUIDGenerator",
parameters = {
@Parameter(
name = "uuid_gen_strategy_class",
value = "org.hibernate.id.uuid.CustomVersionOneStrategy"
)
}
)
public UUID id;
...
}
2.6.12. 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.
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-that-1 increment size.
For example, consider a brand new sequence defined as
create sequence m_sequence start with 1 increment by 20
. This sequence essentially defines a "pool" of 20 usable id values each and every time we ask it for its next-value. The pooled-lo optimizer interprets the next-value as the low end of that pool.So when we first ask it for next-value, we’d get 1. We then assume that the valid pool would be the values from 1-20 inclusive.
The next call to the sequence would result in 21, which would define 21-40 as the valid range. And so on. The "lo" part of the name indicates that the value from the database table/sequence is interpreted as the pool lo(w) end.
pooled
Just like pooled-lo, except that here the value from the table/sequence is interpreted as the high end of the value pool.
hilo; legacy-hilo
Define a custom algorithm for generating pools of values based on a single value from a table or sequence.
These optimizers are not recommended for use. They are maintained (and mentioned) here simply for use by legacy applications that used these strategies previously.
Applications can also implement and use their own optimizer strategies, as defined by the org.hibernate.id.enhanced.Optimizer
contract.
2.6.14. Derived Identifiers
JPA 2.0 added support for derived identifiers which allow an entity to borrow the identifier from a many-to-one or one-to-one association.
Example 75. Derived identifier with @MapsId
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
@NaturalId
private String registrationNumber;
public Person() {}
public Person(String registrationNumber) {
this.registrationNumber = registrationNumber;
}
public Long getId() {
return id;
}
public String getRegistrationNumber() {
return registrationNumber;
}
}
@Entity
public class PersonDetails {
@Id
private Long id;
private String nickName;
@ManyToOne
@MapsId
private Person person;
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
In the example above, the PersonDetails
entity uses the id
column for both the entity identifier and for the many-to-one association to the Person
entity. The value of the PersonDetails
entity identifier is "derived" from the the identifier of its parent Person
entity. The @MapsId
annotation can also reference columns from an @EmbeddedId
identifier as well.
The previous example can also be mapped using @PrimaryKeyJoinColumn
.
Example 76. Derived identifier @PrimaryKeyJoinColumn
@Entity
public class PersonDetails {
@Id
private Long id;
private String nickName;
@ManyToOne
@PrimaryKeyJoinColumn
private Person person;
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
this.id = person.getId();
}
}
Unlike @MapsId , the application developer is responsible of ensuring that the identifier and the many-to-one (or one-to-one) association are in sync. |
|
---|---|
2.7. Associations
Associations describe how two or more entities form a relationship based on a database joining semantics.
2.7.1. @ManyToOne
@ManyToOne
is the most common association, having a direct equivalent in the relational database as well (e.g. foreign key), and so it establishes a relationship between a child entity and a parent.
Example 77. @ManyToOne
association
@Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue
private Long id;
public Person() {
}
}
@Entity(name = "Phone")
public static class Phone {
@Id
@GeneratedValue
private Long id;
private String number;
@ManyToOne
@JoinColumn(name = "person_id",
foreignKey = @ForeignKey(name = "PERSON_ID_FK")
)
private Person person;
public Phone() {
}
public Phone(String number) {
this.number = number;
}
public Long getId() {
return id;
}
public String getNumber() {
return number;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
CREATE TABLE Person (
id BIGINT NOT NULL ,
PRIMARY KEY ( id )
)
CREATE TABLE Phone (
id BIGINT NOT NULL ,
number VARCHAR(255) ,
person_id BIGINT ,
PRIMARY KEY ( id )
)
ALTER TABLE Phone
ADD CONSTRAINT PERSON_ID_FK
FOREIGN KEY (person_id) REFERENCES Person
Each entity has a lifecycle of its own. Once the @ManyToOne
association is set, Hibernate will set the associated database foreign key column.
Example 78. @ManyToOne
association lifecycle
Person person = new Person();
entityManager.persist( person );
Phone phone = new Phone( "123-456-7890" );
phone.setPerson( person );
entityManager.persist( phone );
entityManager.flush();
phone.setPerson( null );
INSERT INTO Person ( id )
VALUES ( 1 )
INSERT INTO Phone ( number, person_id, id )
VALUES ( '123-456-7890', 1, 2 )
UPDATE Phone
SET number = '123-456-7890',
person_id = NULL
WHERE id = 2
2.7.2. @OneToMany
The @OneToMany
association links a parent entity with one or more child entities. If the @OneToMany
doesn’t have a mirroring @ManyToOne
association on the child side, the @OneToMany
association is unidirectional. If there is a @ManyToOne
association on the child side, the @OneToMany
association is bidirectional and the application developer can navigate this relationship from both ends.
Unidirectional @OneToMany
When using a unidirectional @OneToMany
association, Hibernate resorts to using a link table between the two joining entities.
Example 79. Unidirectional @OneToMany
association
@Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue
private Long id;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<Phone> phones = new ArrayList<>();
public Person() {
}
public List<Phone> getPhones() {
return phones;
}
}
@Entity(name = "Phone")
public static class Phone {
@Id
@GeneratedValue
private Long id;
private String number;
public Phone() {
}
public Phone(String number) {
this.number = number;
}
public Long getId() {
return id;
}
public String getNumber() {
return number;
}
}
CREATE TABLE Person (
id BIGINT NOT NULL ,
PRIMARY KEY ( id )
)
CREATE TABLE Person_Phone (
Person_id BIGINT NOT NULL ,
phones_id BIGINT NOT NULL
)
CREATE TABLE Phone (
id BIGINT NOT NULL ,
number VARCHAR(255) ,
PRIMARY KEY ( id )
)
ALTER TABLE Person_Phone
ADD CONSTRAINT UK_9uhc5itwc9h5gcng944pcaslf
UNIQUE (phones_id)
ALTER TABLE Person_Phone
ADD CONSTRAINT FKr38us2n8g5p9rj0b494sd3391
FOREIGN KEY (phones_id) REFERENCES Phone
ALTER TABLE Person_Phone
ADD CONSTRAINT FK2ex4e4p7w1cj310kg2woisjl2
FOREIGN KEY (Person_id) REFERENCES Person
The @OneToMany association is by definition a parent association, even if it’s a unidirectional or a bidirectional one. Only the parent side of an association makes sense to cascade its entity state transitions to children. |
|
---|---|
Example 80. Cascading @OneToMany
association
Person person = new Person();
Phone phone1 = new Phone( "123-456-7890" );
Phone phone2 = new Phone( "321-654-0987" );
person.getPhones().add( phone1 );
person.getPhones().add( phone2 );
entityManager.persist( person );
entityManager.flush();
person.getPhones().remove( phone1 );
INSERT INTO Person
( id )
VALUES ( 1 )
INSERT INTO Phone
( number, id )
VALUES ( '123 - 456 - 7890', 2 )
INSERT INTO Phone
( number, id )
VALUES ( '321 - 654 - 0987', 3 )
INSERT INTO Person_Phone
( Person_id, phones_id )
VALUES ( 1, 2 )
INSERT INTO Person_Phone
( Person_id, phones_id )
VALUES ( 1, 3 )
DELETE FROM Person_Phone
WHERE Person_id = 1
INSERT INTO Person_Phone
( Person_id, phones_id )
VALUES ( 1, 3 )
DELETE FROM Phone
WHERE id = 2
When persisting the Person
entity, the cascade will propagate the persist operation to the underlying Phone
children as well. Upon removing a Phone
from the phones collection, the association row is deleted from the link table, and the orphanRemoval
attribute will trigger a Phone
removal as well.
The unidirectional associations are not very efficient when it comes to removing child entities. In this particular example, upon flushing the persistence context, Hibernate deletes all database child entries and reinserts the ones that are still found in the in-memory persistence context. | |
---|---|
Bidirectional @OneToMany
The bidirectional @OneToMany
association also requires a @ManyToOne
association on the child side. Although the Domain Model exposes two sides to navigate this association, behind the scenes, the relational database has only one foreign key for this relationship.
Every bidirectional association must have one owning side only (the child side), the other one being referred to as the inverse (or the mappedBy
) side.
Example 81. @OneToMany
association mappedBy the @ManyToOne
side
@Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue
private Long id;
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Phone> phones = new ArrayList<>();
public Person() {
}
public Person(Long id) {
this.id = id;
}
public List<Phone> getPhones() {
return phones;
}
public void addPhone(Phone phone) {
phones.add( phone );
phone.setPerson( this );
}
public void removePhone(Phone phone) {
phones.remove( phone );
phone.setPerson( null );
}
}
@Entity(name = "Phone")
public static class Phone {
@Id
@GeneratedValue
private Long id;
@NaturalId
@Column(unique = true)
private String number;
@ManyToOne
private Person person;
public Phone() {
}
public Phone(String number) {
this.number = number;
}
public Long getId() {
return id;
}
public String getNumber() {
return number;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
Phone phone = (Phone) o;
return Objects.equals( number, phone.number );
}
@Override
public int hashCode() {
return Objects.hash( number );
}
}
CREATE TABLE Person (
id BIGINT NOT NULL ,
PRIMARY KEY ( id )
)
CREATE TABLE Phone (
id BIGINT NOT NULL ,
number VARCHAR(255) ,
person_id BIGINT ,
PRIMARY KEY ( id )
)
ALTER TABLE Phone
ADD CONSTRAINT UK_l329ab0g4c1t78onljnxmbnp6
UNIQUE (number)
ALTER TABLE Phone
ADD CONSTRAINT FKmw13yfsjypiiq0i1osdkaeqpg
FOREIGN KEY (person_id) REFERENCES Person
Whenever a bidirectional association is formed, the application developer must make sure both sides are in-sync at all times. The addPhone() and removePhone() are utilities methods that synchronize both ends whenever a child element is added or removed. |
|
---|---|
Because the Phone
class has a @NaturalId
column (the phone number being unique), the equals()
and the hashCode()
can make use of this property, and so the removePhone()
logic is reduced to the remove()
Java Collection
method.
Example 82. Bidirectional @OneToMany
with an owner @ManyToOne
side lifecycle
Person person = new Person();
Phone phone1 = new Phone( "123-456-7890" );
Phone phone2 = new Phone( "321-654-0987" );
person.addPhone( phone1 );
person.addPhone( phone2 );
entityManager.persist( person );
entityManager.flush();
person.removePhone( phone1 );
INSERT INTO Phone
( number, person_id, id )
VALUES ( '123-456-7890', NULL, 2 )
INSERT INTO Phone
( number, person_id, id )
VALUES ( '321-654-0987', NULL, 3 )
DELETE FROM Phone
WHERE id = 2
Unlike the unidirectional @OneToMany
, the bidirectional association is much more efficient when managing the collection persistence state. Every element removal only requires a single update (in which the foreign key column is set to NULL
), and, if the child entity lifecycle is bound to its owning parent so that the child cannot exist without its parent, then we can annotate the association with the orphan-removal
attribute and disassociating the child will trigger a delete statement on the actual child table row as well.
2.7.3. @OneToOne
The @OneToOne
association can either be unidirectional or bidirectional. A unidirectional association follows the relational database foreign key semantics, the client-side owning the relationship. A bidirectional association features a mappedBy
@OneToOne
parent side too.
Unidirectional @OneToOne
Example 83. Unidirectional @OneToOne
@Entity(name = "Phone")
public static class Phone {
@Id
@GeneratedValue
private Long id;
private String number;
@OneToOne
@JoinColumn(name = "details_id")
private PhoneDetails details;
public Phone() {
}
public Phone(String number) {
this.number = number;
}
public Long getId() {
return id;
}
public String getNumber() {
return number;
}
public PhoneDetails getDetails() {
return details;
}
public void setDetails(PhoneDetails details) {
this.details = details;
}
}
@Entity(name = "PhoneDetails")
public static class PhoneDetails {
@Id
@GeneratedValue
private Long id;
private String provider;
private String technology;
public PhoneDetails() {
}
public PhoneDetails(String provider, String technology) {
this.provider = provider;
this.technology = technology;
}
public String getProvider() {
return provider;
}
public String getTechnology() {
return technology;
}
public void setTechnology(String technology) {
this.technology = technology;
}
}
CREATE TABLE Phone (
id BIGINT NOT NULL ,
number VARCHAR(255) ,
details_id BIGINT ,
PRIMARY KEY ( id )
)
CREATE TABLE PhoneDetails (
id BIGINT NOT NULL ,
provider VARCHAR(255) ,
technology VARCHAR(255) ,
PRIMARY KEY ( id )
)
ALTER TABLE Phone
ADD CONSTRAINT FKnoj7cj83ppfqbnvqqa5kolub7
FOREIGN KEY (details_id) REFERENCES PhoneDetails
From a relational database point of view, the underlying schema is identical to the unidirectional @ManyToOne
association, as the client-side controls the relationship based on the foreign key column.
But then, it’s unusual to consider the Phone
as a client-side and the PhoneDetails
as the parent-side because the details cannot exist without an actual phone. A much more natural mapping would be if the Phone
was the parent-side, therefore pushing the foreign key into the PhoneDetails
table. This mapping requires a bidirectional @OneToOne
association as you can see in the following example:
Bidirectional @OneToOne
Example 84. Bidirectional @OneToOne
@Entity(name = "Phone")
public static class Phone {
@Id
@GeneratedValue
private Long id;
private String number;
@OneToOne(mappedBy = "phone", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private PhoneDetails details;
public Phone() {
}
public Phone(String number) {
this.number = number;
}
public Long getId() {
return id;
}
public String getNumber() {
return number;
}
public PhoneDetails getDetails() {
return details;
}
public void addDetails(PhoneDetails details) {
details.setPhone( this );
this.details = details;
}
public void removeDetails() {
if ( details != null ) {
details.setPhone( null );
this.details = null;
}
}
}
@Entity(name = "PhoneDetails")
public static class PhoneDetails {
@Id
@GeneratedValue
private Long id;
private String provider;
private String technology;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "phone_id")
private Phone phone;
public PhoneDetails() {
}
public PhoneDetails(String provider, String technology) {
this.provider = provider;
this.technology = technology;
}
public String getProvider() {
return provider;
}
public String getTechnology() {
return technology;
}
public void setTechnology(String technology) {
this.technology = technology;
}
public Phone getPhone() {
return phone;
}
public void setPhone(Phone phone) {
this.phone = phone;
}
}
CREATE TABLE Phone (
id BIGINT NOT NULL ,
number VARCHAR(255) ,
PRIMARY KEY ( id )
)
CREATE TABLE PhoneDetails (
id BIGINT NOT NULL ,
provider VARCHAR(255) ,
technology VARCHAR(255) ,
phone_id BIGINT ,
PRIMARY KEY ( id )
)
ALTER TABLE PhoneDetails
ADD CONSTRAINT FKeotuev8ja8v0sdh29dynqj05p
FOREIGN KEY (phone_id) REFERENCES Phone
This time, the PhoneDetails
owns the association, and, like any bidirectional association, the parent-side can propagate its lifecycle to the child-side through cascading.
Example 85. Bidirectional @OneToOne
lifecycle
Phone phone = new Phone( "123-456-7890" );
PhoneDetails details = new PhoneDetails( "T-Mobile", "GSM" );
phone.addDetails( details );
entityManager.persist( phone );
INSERT INTO Phone ( number, id )
VALUES ( '123 - 456 - 7890', 1 )
INSERT INTO PhoneDetails ( phone_id, provider, technology, id )
VALUES ( 1, 'T - Mobile, GSM', 2 )
When using a bidirectional @OneToOne
association, Hibernate enforces the unique constraint upon fetching the child-side. If there are more than one children associated to the same parent, Hibernate will throw a constraint violation exception. Continuing the previous example, when adding another PhoneDetails
, Hibernate validates the uniqueness constraint when reloading the Phone
object.
Example 86. Bidirectional @OneToOne
unique constraint
PhoneDetails otherDetails = new PhoneDetails( "T-Mobile", "CDMA" );
otherDetails.setPhone( phone );
entityManager.persist( otherDetails );
entityManager.flush();
entityManager.clear();
//throws javax.persistence.PersistenceException: org.hibernate.HibernateException: More than one row with the given identifier was found: 1
phone = entityManager.find( Phone.class, phone.getId() );
2.7.4. @ManyToMany
The @ManyToMany
association requires a link table that joins two entities. Like the @OneToMany
association, @ManyToMany
can be a either unidirectional or bidirectional.
Unidirectional @ManyToMany
Example 87. Unidirectional @ManyToMany
@Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue
private Long id;
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List<Address> addresses = new ArrayList<>();
public Person() {
}
public List<Address> getAddresses() {
return addresses;
}
}
@Entity(name = "Address")
public static class Address {
@Id
@GeneratedValue
private Long id;
private String street;
private String number;
public Address() {
}
public Address(String street, String number) {
this.street = street;
this.number = number;
}
public Long getId() {
return id;
}
public String getStreet() {
return street;
}
public String getNumber() {
return number;
}
}
CREATE TABLE Address (
id BIGINT NOT NULL ,
number VARCHAR(255) ,
street VARCHAR(255) ,
PRIMARY KEY ( id )
)
CREATE TABLE Person (
id BIGINT NOT NULL ,
PRIMARY KEY ( id )
)
CREATE TABLE Person_Address (
Person_id BIGINT NOT NULL ,
addresses_id BIGINT NOT NULL
)
ALTER TABLE Person_Address
ADD CONSTRAINT FKm7j0bnabh2yr0pe99il1d066u
FOREIGN KEY (addresses_id) REFERENCES Address
ALTER TABLE Person_Address
ADD CONSTRAINT FKba7rc9qe2vh44u93u0p2auwti
FOREIGN KEY (Person_id) REFERENCES Person
Just like with unidirectional @OneToMany
associations, the link table is controlled by the owning side.
When an entity is removed from the @ManyToMany
collection, Hibernate simply deletes the joining record in the link table. Unfortunately, this operation requires removing all entries associated to a given parent and recreating the ones that are listed in the current running persistent context.
Example 88. Unidirectional @ManyToMany
lifecycle
Person person1 = new Person();
Person person2 = new Person();
Address address1 = new Address( "12th Avenue", "12A" );
Address address2 = new Address( "18th Avenue", "18B" );
person1.getAddresses().add( address1 );
person1.getAddresses().add( address2 );
person2.getAddresses().add( address1 );
entityManager.persist( person1 );
entityManager.persist( person2 );
entityManager.flush();
person1.getAddresses().remove( address1 );
INSERT INTO Person ( id )
VALUES ( 1 )
INSERT INTO Address ( number, street, id )
VALUES ( '12A', '12th Avenue', 2 )
INSERT INTO Address ( number, street, id )
VALUES ( '18B', '18th Avenue', 3 )
INSERT INTO Person ( id )
VALUES ( 4 )
INSERT INTO Person_Address ( Person_id, addresses_id )
VALUES ( 1, 2 )
INSERT INTO Person_Address ( Person_id, addresses_id )
VALUES ( 1, 3 )
INSERT INTO Person_Address ( Person_id, addresses_id )
VALUES ( 4, 2 )
DELETE FROM Person_Address
WHERE Person_id = 1
INSERT INTO Person_Address ( Person_id, addresses_id )
VALUES ( 1, 3 )
For @ManyToMany associations, the REMOVE entity state transition doesn’t make sense to be cascaded because it will propagate beyond the link table. Since the other side might be referenced by other entities on the parent-side, the automatic removal might end up in a ConstraintViolationException . |
|
---|---|
By simply removing the parent-side, Hibernate can safely remove the associated link records as you can see in the following example:
Example 89. Unidirectional @ManyToMany
entity removal
Person person1 = entityManager.find( Person.class, personId );
entityManager.remove( person1 );
DELETE FROM Person_Address
WHERE Person_id = 1
DELETE FROM Person
WHERE id = 1
Bidirectional @ManyToMany
A bidirectional @ManyToMany
association has an owning and a mappedBy
side. To preserve synchronicity between both sides, it’s good practice to provide helper methods for adding or removing child entities.
Example 90. Bidirectional @ManyToMany
@Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue
private Long id;
@NaturalId
private String registrationNumber;
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List<Address> addresses = new ArrayList<>();
public Person() {
}
public Person(String registrationNumber) {
this.registrationNumber = registrationNumber;
}
public List<Address> getAddresses() {
return addresses;
}
public void addAddress(Address address) {
addresses.add( address );
address.getOwners().add( this );
}
public void removeAddress(Address address) {
addresses.remove( address );
address.getOwners().remove( this );
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
Person person = (Person) o;
return Objects.equals( registrationNumber, person.registrationNumber );
}
@Override
public int hashCode() {
return Objects.hash( registrationNumber );
}
}
@Entity(name = "Address")
public static class Address {
@Id
@GeneratedValue
private Long id;
private String street;
private String number;
private String postalCode;
@ManyToMany(mappedBy = "addresses")
private List<Person> owners = new ArrayList<>();
public Address() {
}
public Address(String street, String number, String postalCode) {
this.street = street;
this.number = number;
this.postalCode = postalCode;
}
public Long getId() {
return id;
}
public String getStreet() {
return street;
}
public String getNumber() {
return number;
}
public String getPostalCode() {
return postalCode;
}
public List<Person> getOwners() {
return owners;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
Address address = (Address) o;
return Objects.equals( street, address.street ) &&
Objects.equals( number, address.number ) &&
Objects.equals( postalCode, address.postalCode );
}
@Override
public int hashCode() {
return Objects.hash( street, number, postalCode );
}
}
CREATE TABLE Address (
id BIGINT NOT NULL ,
number VARCHAR(255) ,
postalCode VARCHAR(255) ,
street VARCHAR(255) ,
PRIMARY KEY ( id )
)
CREATE TABLE Person (
id BIGINT NOT NULL ,
registrationNumber VARCHAR(255) ,
PRIMARY KEY ( id )
)
CREATE TABLE Person_Address (
owners_id BIGINT NOT NULL ,
addresses_id BIGINT NOT NULL
)
ALTER TABLE Person
ADD CONSTRAINT UK_23enodonj49jm8uwec4i7y37f
UNIQUE (registrationNumber)
ALTER TABLE Person_Address
ADD CONSTRAINT FKm7j0bnabh2yr0pe99il1d066u
FOREIGN KEY (addresses_id) REFERENCES Address
ALTER TABLE Person_Address
ADD CONSTRAINT FKbn86l24gmxdv2vmekayqcsgup
FOREIGN KEY (owners_id) REFERENCES Person
With the helper methods in place, the synchronicity management can be simplified, as you can see in the following example:
Example 91. Bidirectional @ManyToMany
lifecycle
Person person1 = new Person( "ABC-123" );
Person person2 = new Person( "DEF-456" );
Address address1 = new Address( "12th Avenue", "12A", "4005A" );
Address address2 = new Address( "18th Avenue", "18B", "4007B" );
person1.addAddress( address1 );
person1.addAddress( address2 );
person2.addAddress( address1 );
entityManager.persist( person1 );
entityManager.persist( person2 );
entityManager.flush();
person1.removeAddress( address1 );
INSERT INTO Person ( registrationNumber, id )
VALUES ( 'ABC-123', 1 )
INSERT INTO Address ( number, postalCode, street, id )
VALUES ( '12A', '4005A', '12th Avenue', 2 )
INSERT INTO Address ( number, postalCode, street, id )
VALUES ( '18B', '4007B', '18th Avenue', 3 )
INSERT INTO Person ( registrationNumber, id )
VALUES ( 'DEF-456', 4 )
INSERT INTO Person_Address ( owners_id, addresses_id )
VALUES ( 1, 2 )
INSERT INTO Person_Address ( owners_id, addresses_id )
VALUES ( 1, 3 )
INSERT INTO Person_Address ( owners_id, addresses_id )
VALUES ( 4, 2 )
DELETE FROM Person_Address
WHERE owners_id = 1
INSERT INTO Person_Address ( owners_id, addresses_id )
VALUES ( 1, 3 )
If a bidirectional @OneToMany
association performs better when removing or changing the order of child elements, the @ManyToMany
relationship cannot benefit from such an optimization because the foreign key side is not in control. To overcome this limitation, the 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.
Example 92. Bidirectional many-to-many with link entity
@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<>();
public Person() {
}
public Person(String registrationNumber) {
this.registrationNumber = registrationNumber;
}
public Long getId() {
return id;
}
public List<PersonAddress> getAddresses() {
return addresses;
}
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;
public PersonAddress() {
}
public PersonAddress(Person person, Address address) {
this.person = person;
this.address = address;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@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;
private String number;
private String postalCode;
@OneToMany(mappedBy = "address", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PersonAddress> owners = new ArrayList<>();
public Address() {
}
public Address(String street, String number, String postalCode) {
this.street = street;
this.number = number;
this.postalCode = postalCode;
}
public Long getId() {
return id;
}
public String getStreet() {
return street;
}
public String getNumber() {
return number;
}
public String getPostalCode() {
return postalCode;
}
public List<PersonAddress> getOwners() {
return owners;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
Address address = (Address) o;
return Objects.equals( street, address.street ) &&
Objects.equals( number, address.number ) &&
Objects.equals( postalCode, address.postalCode );
}
@Override
public int hashCode() {
return Objects.hash( street, number, postalCode );
}
}
CREATE TABLE Address (
id BIGINT NOT NULL ,
number VARCHAR(255) ,
postalCode VARCHAR(255) ,
street VARCHAR(255) ,
PRIMARY KEY ( id )
)
CREATE TABLE Person (
id BIGINT NOT NULL ,
registrationNumber VARCHAR(255) ,
PRIMARY KEY ( id )
)
CREATE TABLE 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 amappedBy
@OneToMany
side, while the PersonAddress
owns the person
and the address
@ManyToOne
associations. Because this mapping is formed out of two bidirectional associations, the helper methods are even more relevant.
The aforementioned example uses a Hibernate specific mapping for the link entity since JPA doesn’t allow building a composite identifier out of multiple @ManyToOne associations. For more details, see the Composite identifiers - associations section. |
|
---|---|
The entity state transitions are better managed than in the previous bidirectional @ManyToMany
case.
Example 93. Bidirectional many-to-many with link entity lifecycle
Person person1 = new Person( "ABC-123" );
Person person2 = new Person( "DEF-456" );
Address address1 = new Address( "12th Avenue", "12A", "4005A" );
Address address2 = new Address( "18th Avenue", "18B", "4007B" );
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.8. Collections
Naturally Hibernate also allows to persist collections. These persistent collections can contain almost any other Hibernate type, including: basic types, custom types, components and references to other entities. In this context, the distinction between value and reference semantics is very important. An object in a collection might be handled with value semantics (its life cycle being fully depends on the collection owner), or it might be a reference to another entity with its own life cycle. In the latter case, only the link between the two objects is considered to be a state held by the collection.
The owner of the collection is always an entity, even if the collection is defined by an embeddable type. Collections form one/many-to-many associations between types, so there can be:
value type collections
embeddable type collections
entity collections
Hibernate uses its own collection implementations which are enriched with lazy-loading, caching or state change detection semantics. For this reason, persistent collections must be declared as an interface type. The actual interface might be java.util.Collection
, java.util.List
, java.util.Set
, java.util.Map
, java.util.SortedSet
, java.util.SortedMap
or even other object types (meaning you will have to write an implementation of org.hibernate.usertype.UserCollectionType
).
As the following example demonstrates, it’s important to use the interface type and not the collection implementation, as declared in the entity mapping.
Example 94. Hibernate uses its own collection implementations
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@ElementCollection
private List<String> phones = new ArrayList<>();
public List<String> getPhones() {
return phones;
}
}
Person person = entityManager.find( Person.class, 1L );
//Throws java.lang.ClassCastException: org.hibernate.collection.internal.PersistentBag cannot be cast to java.util.ArrayList
ArrayList<String> phones = (ArrayList<String>) person.getPhones();
It is important that collections be defined using the appropriate Java Collections Framework interface rather than a specific implementation. From a theoretical perspective, this just follows good design principles. From a practical perspective, Hibernate (like other persistence providers) will use their own collection implementations which conform to the Java Collections Framework interfaces. | |
---|---|
The persistent collections injected by Hibernate behave like ArrayList
, HashSet
, TreeSet
, HashMap
or TreeMap
, depending on the interface type.
2.8.1. Collections as a value type
Value and embeddable type collections have a similar behavior as simple value types because they are automatically persisted when referenced by a persistent object and automatically deleted when unreferenced. If a collection is passed from one persistent object to another, its elements might be moved from one table to another.
Two entities cannot share a reference to the same collection instance. Collection-valued properties do not support null value semantics because Hibernate does not distinguish between a null collection reference and an empty collection. | |
---|---|
2.8.2. Collections of value types
Collections of value type include basic and embeddable types. Collections cannot be nested, and, when used in collections, embeddable types are not allowed to define other collections.
For collections of value types, JPA 2.0 defines the @ElementCollection
annotation. The lifecycle of the value-type collection is entirely controlled by its owning entity.
Considering the previous example mapping, when clearing the phone collection, Hibernate deletes all the associated phones. When adding a new element to the value type collection, Hibernate issues a new insert statement.
Example 95. Value type collection lifecycle
person.getPhones().clear();
person.getPhones().add( "123-456-7890" );
person.getPhones().add( "456-000-1234" );
DELETE FROM Person_phones WHERE Person_id = 1
INSERT INTO Person_phones ( Person_id, phones )
VALUES ( 1, '123-456-7890' )
INSERT INTO Person_phones (Person_id, phones)
VALUES ( 1, '456-000-1234' )
If removing all elements or adding new ones is rather straightforward, removing a certain entry actually requires reconstructing the whole collection from scratch.
Example 96. Removing collection elements
person.getPhones().remove( 0 );
DELETE FROM Person_phones WHERE Person_id = 1
INSERT INTO Person_phones ( Person_id, phones )
VALUES ( 1, '456-000-1234' )
Depending on the number of elements, this behavior might not be efficient, if many elements need to be deleted and reinserted back into the database table. A workaround is to use an @OrderColumn
, which, although not as efficient as when using the actual link table primary key, might improve the efficiency of the remove operations.
Example 97. Removing collection elements using the order column
@ElementCollection
@OrderColumn(name = "order_id")
private List<String> phones = new ArrayList<>();
person.getPhones().remove( 0 );
DELETE FROM Person_phones
WHERE Person_id = 1
AND order_id = 1
UPDATE Person_phones
SET phones = '456-000-1234'
WHERE Person_id = 1
AND order_id = 0
The @OrderColumn column works best when removing from the tail of the collection, as it only requires a single delete statement. Removing from the head or the middle of the collection requires deleting the extra elements and updating the remaining ones to preserve element order. |
|
---|---|
Embeddable type collections behave the same way as value type collections. Adding embeddables to the collection triggers the associated insert statements and removing elements from the collection will generate delete statements.
Example 98. Embeddable type collections
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@ElementCollection
private List<Phone> phones = new ArrayList<>();
public List<Phone> getPhones() {
return phones;
}
}
@Embeddable
public static class Phone {
private String type;
private String number;
public Phone() {
}
public Phone(String type, String number) {
this.type = type;
this.number = number;
}
public String getType() {
return type;
}
public String getNumber() {
return number;
}
}
person.getPhones().add( new Phone( "landline", "028-234-9876" ) );
person.getPhones().add( new Phone( "mobile", "072-122-9876" ) );
INSERT INTO Person_phones ( Person_id, number, type )
VALUES ( 1, '028-234-9876', 'landline' )
INSERT INTO Person_phones ( Person_id, number, type )
VALUES ( 1, '072-122-9876', 'mobile' )
2.8.3. Collections of entities
If value type collections can only form a one-to-many association between an owner entity and multiple basic or embeddable types, entity collections can represent both @OneToMany and @ManyToMany associations.
From a relational database perspective, associations are defined by the foreign key side (the child-side). With value type collections, only the entity can control the association (the parent-side), but for a collection of entities, both sides of the association are managed by the persistence context.
For ths reason, entity collections can be devised into two main categories: unidirectional and bidirectional associations. Unidirectional associations are very similar to value type collections, since only the parent side controls this relationship. Bidirectional associations are more tricky, since, even if sides need to be in-sync at all times, only one side is responsible for managing the association. A bidirectional association has an owning side and an inverse (mappedBy) side.
Another way of categorizing entity collections is by the underlying collection type, and so we can have:
bags
indexed lists
sets
sorted sets
maps
sorted maps
arrays
In the following sections, we will go through all these collection types and discuss both unidirectional and bidirectional associations.
2.8.4. Bags
Bags are unordered lists and we can have unidirectional bags or bidirectional ones.
Unidirectional bags
The unidirectional bag is mapped using a single @OneToMany
annotation on the parent side of the association. Behind the scenes, Hibernate requires an association table to manage the parent-child relationship, as we can see in the following example:
Example 99. Unidirectional bag
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@OneToMany(cascade = CascadeType.ALL)
private List<Phone> phones = new ArrayList<>();
public Person() {
}
public Person(Long id) {
this.id = id;
}
public List<Phone> getPhones() {
return phones;
}
}
@Entity(name = "Phone")
public static class Phone {
@Id
private Long id;
private String type;
private String number;
public Phone() {
}
public Phone(Long id, String type, String number) {
this.id = id;
this.type = type;
this.number = number;
}
public Long getId() {
return id;
}
public String getType() {
return type;
}
public String getNumber() {
return number;
}
}
CREATE TABLE Person (
id BIGINT NOT NULL ,
PRIMARY KEY ( id )
)
CREATE TABLE Person_Phone (
Person_id BIGINT NOT NULL ,
phones_id BIGINT NOT NULL
)
CREATE TABLE Phone (
id BIGINT NOT NULL ,
number VARCHAR(255) ,
type VARCHAR(255) ,
PRIMARY KEY ( id )
)
ALTER TABLE Person_Phone
ADD CONSTRAINT UK_9uhc5itwc9h5gcng944pcaslf
UNIQUE (phones_id)
ALTER TABLE Person_Phone
ADD CONSTRAINT FKr38us2n8g5p9rj0b494sd3391
FOREIGN KEY (phones_id) REFERENCES Phone
ALTER TABLE Person_Phone
ADD CONSTRAINT FK2ex4e4p7w1cj310kg2woisjl2
FOREIGN KEY (Person_id) REFERENCES Person
Because both the parent and the child sides are entities, the persistence context manages each entity separately. Cascades can propagate an entity state transition from a parent entity to its children. | |
---|---|
By marking the parent side with the CascadeType.ALL
attribute, the unidirectional association lifecycle becomes very similar to that of a value type collection.
Example 100. Unidirectional bag lifecycle
Person person = new Person( 1L );
person.getPhones().add( new Phone( 1L, "landline", "028-234-9876" ) );
person.getPhones().add( new Phone( 2L, "mobile", "072-122-9876" ) );
entityManager.persist( person );
INSERT INTO Person ( id )
VALUES ( 1 )
INSERT INTO Phone ( number, type, id )
VALUES ( '028-234-9876', 'landline', 1 )
INSERT INTO Phone ( number, type, id )
VALUES ( '072-122-9876', 'mobile', 2 )
INSERT INTO Person_Phone ( Person_id, phones_id )
VALUES ( 1, 1 )
INSERT INTO Person_Phone ( Person_id, phones_id )
VALUES ( 1, 2 )
In the example above, once the parent entity is persisted, the child entities are going to be persisted as well.
Just like value type collections, unidirectional bags are not as efficient when it comes to modifying the collection structure (removing or reshuffling elements). Because the parent-side cannot uniquely identify each individual child, Hibernate might delete all child table rows associate to the parent entity and re-add them according to the current collection state. | |
---|---|
Bidirectional bags
The bidirectional bag is the most common type of entity collection. The @ManyToOne
side is the owning side of the bidirectional bag association, while the @OneToMany
is the inverse side, being marked with the mappedBy
attribute.
Example 101. Bidirectional bag
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
private List<Phone> phones = new ArrayList<>();
public Person() {
}
public Person(Long id) {
this.id = id;
}
public List<Phone> getPhones() {
return phones;
}
public void addPhone(Phone phone) {
phones.add( phone );
phone.setPerson( this );
}
public void removePhone(Phone phone) {
phones.remove( phone );
phone.setPerson( null );
}
}
@Entity(name = "Phone")
public static class Phone {
@Id
private Long id;
private String type;
@Column(unique = true)
@NaturalId
private String number;
@ManyToOne
private Person person;
public Phone() {
}
public Phone(Long id, String type, String number) {
this.id = id;
this.type = type;
this.number = number;
}
public Long getId() {
return id;
}
public String getType() {
return type;
}
public String getNumber() {
return number;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
Phone phone = (Phone) o;
return Objects.equals( number, phone.number );
}
@Override
public int hashCode() {
return Objects.hash( number );
}
}
CREATE TABLE Person (
id BIGINT NOT NULL, PRIMARY KEY (id)
)
CREATE TABLE Phone (
id BIGINT NOT NULL,
number VARCHAR(255),
type VARCHAR(255),
person_id BIGINT,
PRIMARY KEY (id)
)
ALTER TABLE Phone
ADD CONSTRAINT UK_l329ab0g4c1t78onljnxmbnp6
UNIQUE (number)
ALTER TABLE Phone
ADD CONSTRAINT FKmw13yfsjypiiq0i1osdkaeqpg
FOREIGN KEy (person_id) REFERENCES Person
Example 102. Bidirectional bag lifecycle
person.addPhone( new Phone( 1L, "landline", "028-234-9876" ) );
person.addPhone( new Phone( 2L, "mobile", "072-122-9876" ) );
entityManager.flush();
person.removePhone( person.getPhones().get( 0 ) );
INSERT INTO Phone (number, person_id, type, id)
VALUES ( '028-234-9876', 1, 'landline', 1 )
INSERT INTO Phone (number, person_id, type, id)
VALUES ( '072-122-9876', 1, 'mobile', 2 )
UPDATE Phone
SET person_id = NULL, type = 'landline' where id = 1
Example 103. Bidirectional bag with orphan removal
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Phone> phones = new ArrayList<>();
DELETE FROM Phone WHERE id = 1
When rerunning the previous example, the child will get removed because the parent-side propagates the removal upon disassociating the child entity reference.
2.8.5. Ordered Lists
Although they use the List
interface on the Java side, bags don’t retain element order. To preserve the collection element order, there are two possibilities:
@OrderBy
the collection is ordered upon retrieval using a child entity property
@OrderColumn
- the collection uses a dedicated order column in the collection link table
Unidirectional ordered lists
When using the @OrderBy
annotation, the mapping looks as follows:
Example 104. Unidirectional @OrderBy
list
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@OneToMany(cascade = CascadeType.ALL)
@OrderColumn(name = "order_id")
private List<Phone> phones = new ArrayList<>();
public Person() {
}
public Person(Long id) {
this.id = id;
}
public List<Phone> getPhones() {
return phones;
}
}
@Entity(name = "Phone")
public static class Phone {
@Id
private Long id;
private String type;
private String number;
public Phone() {
}
public Phone(Long id, String type, String number) {
this.id = id;
this.type = type;
this.number = number;
}
public Long getId() {
return id;
}
public String getType() {
return type;
}
public String getNumber() {
return number;
}
}
The database mapping is the same as with the Unidirectional bags example, so it won’t be repeated. Upon fetching the collection, Hibernate generates the following select statement:
Example 105. Unidirectional @OrderBy
list select statement
SELECT
phones0_.Person_id AS Person_i1_1_0_,
phones0_.phones_id AS phones_i2_1_0_,
unidirecti1_.id AS id1_2_1_,
unidirecti1_.number AS number2_2_1_,
unidirecti1_.type AS type3_2_1_
FROM
Person_Phone phones0_
INNER JOIN
Phone unidirecti1_ ON phones0_.phones_id=unidirecti1_.id
WHERE
phones0_.Person_id = 1
ORDER BY
unidirecti1_.number
The child table column is used to order the list elements.
The @OrderBy annotation can take multiple entity properties, and each property can take an ordering direction too (e.g. @OrderBy("name ASC, type DESC") ). |
|
---|---|
Another ordering option is to use the @OrderColumn
annotation:
Example 106. Unidirectional @OrderColumn
list
@OneToMany(cascade = CascadeType.ALL)
@OrderBy("number")
private List<Phone> phones = new ArrayList<>();
CREATE TABLE Person_Phone (
Person_id BIGINT NOT NULL ,
phones_id BIGINT NOT NULL ,
order_id INTEGER NOT NULL ,
PRIMARY KEY ( Person_id, order_id )
)
This time, the link table takes the order_id
column and uses it to materialize the collection element order. When fetching the list, the following select query is executed:
Example 107. Unidirectional @OrderColumn
list select statement
select
phones0_.Person_id as Person_i1_1_0_,
phones0_.phones_id as phones_i2_1_0_,
phones0_.order_id as order_id3_0_,
unidirecti1_.id as id1_2_1_,
unidirecti1_.number as number2_2_1_,
unidirecti1_.type as type3_2_1_
from
Person_Phone phones0_
inner join
Phone unidirecti1_
on phones0_.phones_id=unidirecti1_.id
where
phones0_.Person_id = 1
With the order_id
column in place, Hibernate can order the list in-memory after it’s being fetched from the database.
Bidirectional ordered lists
The mapping is similar with the Bidirectional bags example, just that the parent side is going to be annotated with either @OrderBy
or @OrderColumn
.
Example 108. Bidirectional @OrderBy
list
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
@OrderBy("number")
private List<Phone> phones = new ArrayList<>();
Just like with the unidirectional @OrderBy
list, the number
column is used to order the statement on the SQL level.
When using the @OrderColumn
annotation, the order_id
column is going to be embedded in the child table:
Example 109. Bidirectional @OrderColumn
list
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
@OrderColumn(name = "order_id")
private List<Phone> phones = new ArrayList<>();
CREATE TABLE Phone (
id BIGINT NOT NULL ,
number VARCHAR(255) ,
type VARCHAR(255) ,
person_id BIGINT ,
order_id INTEGER ,
PRIMARY KEY ( id )
)
When fetching the collection, Hibernate will use the fetched ordered columns to sort the elements according to the @OrderColumn
mapping.
2.8.6. Sets
Sets are collections that don’t allow duplicate entries and Hibernate supports both the unordered Set
and the natural-ordering SortedSet
.
Unidirectional sets
The unidirectional set uses a link table to hold the parent-child associations and the entity mapping looks as follows:
Example 110. Unidirectional set
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@OneToMany(cascade = CascadeType.ALL)
private Set<Phone> phones = new HashSet<>();
public Person() {
}
public Person(Long id) {
this.id = id;
}
public Set<Phone> getPhones() {
return phones;
}
}
@Entity(name = "Phone")
public static class Phone {
@Id
private Long id;
private String type;
@NaturalId
private String number;
public Phone() {
}
public Phone(Long id, String type, String number) {
this.id = id;
this.type = type;
this.number = number;
}
public Long getId() {
return id;
}
public String getType() {
return type;
}
public String getNumber() {
return number;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
Phone phone = (Phone) o;
return Objects.equals( number, phone.number );
}
@Override
public int hashCode() {
return Objects.hash( number );
}
}
The unidirectional set lifecycle is similar to that of the Unidirectional bags, so it can be omitted. The only difference is that Set
doesn’t allow duplicates, but this constraint is enforced by the Java object contract rather then the database mapping.
When using sets, it’s very important to supply proper equals/hashCode implementations for child entities. In the absence of a custom equals/hashCode implementation logic, Hibernate will use the default Java reference-based object equality which might render unexpected results when mixing detached and managed object instances. | |
---|---|
Bidirectional sets
Just like bidirectional bags, the bidirectional set doesn’t use a link table, and the child table has a foreign key referencing the parent table primary key. The lifecycle is just like with bidirectional bags except for the duplicates which are filtered out.
Example 111. Bidirectional set
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
private Set<Phone> phones = new HashSet<>();
public Person() {
}
public Person(Long id) {
this.id = id;
}
public Set<Phone> getPhones() {
return phones;
}
public void addPhone(Phone phone) {
phones.add( phone );
phone.setPerson( this );
}
public void removePhone(Phone phone) {
phones.remove( phone );
phone.setPerson( null );
}
}
@Entity(name = "Phone")
public static class Phone {
@Id
private Long id;
private String type;
@Column(unique = true)
@NaturalId
private String number;
@ManyToOne
private Person person;
public Phone() {
}
public Phone(Long id, String type, String number) {
this.id = id;
this.type = type;
this.number = number;
}
public Long getId() {
return id;
}
public String getType() {
return type;
}
public String getNumber() {
return number;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
Phone phone = (Phone) o;
return Objects.equals( number, phone.number );
}
@Override
public int hashCode() {
return Objects.hash( number );
}
}
2.8.7. Sorted sets
For sorted sets, the entity mapping must use the SortedSet
interface instead. According to the SortedSet
contract, all elements must implement the comparable interface and therefore provide the sorting logic.
Unidirectional sorted sets
A SortedSet
that relies on the natural sorting order given by the child element Comparable
implementation logic must be annotated with the @SortNatural
Hibernate annotation.
Example 112. Unidirectional natural sorted set
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@OneToMany(cascade = CascadeType.ALL)
@SortNatural
private SortedSet<Phone> phones = new TreeSet<>();
public Person() {
}
public Person(Long id) {
this.id = id;
}
public Set<Phone> getPhones() {
return phones;
}
}
@Entity(name = "Phone")
public static class Phone implements Comparable<Phone> {
@Id
private Long id;
private String type;
@NaturalId
private String number;
public Phone() {
}
public Phone(Long id, String type, String number) {
this.id = id;
this.type = type;
this.number = number;
}
public Long getId() {
return id;
}
public String getType() {
return type;
}
public String getNumber() {
return number;
}
@Override
public int compareTo(Phone o) {
return number.compareTo( o.getNumber() );
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
Phone phone = (Phone) o;
return Objects.equals( number, phone.number );
}
@Override
public int hashCode() {
return Objects.hash( number );
}
}
The lifecycle and the database mapping are identical to the Unidirectional bags, so they are intentionally omitted.
To provide a custom sorting logic, Hibernate also provides a @SortComparator
annotation:
Example 113. Unidirectional custom comparator sorted set
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@OneToMany(cascade = CascadeType.ALL)
@SortComparator(ReverseComparator.class)
private SortedSet<Phone> phones = new TreeSet<>();
public Person() {
}
public Person(Long id) {
this.id = id;
}
public Set<Phone> getPhones() {
return phones;
}
}
public static class ReverseComparator implements Comparator<Phone> {
@Override
public int compare(Phone o1, Phone o2) {
return o2.compareTo( o1 );
}
}
@Entity(name = "Phone")
public static class Phone implements Comparable<Phone> {
@Id
private Long id;
private String type;
@NaturalId
private String number;
public Phone() {
}
public Phone(Long id, String type, String number) {
this.id = id;
this.type = type;
this.number = number;
}
public Long getId() {
return id;
}
public String getType() {
return type;
}
public String getNumber() {
return number;
}
@Override
public int compareTo(Phone o) {
return number.compareTo( o.getNumber() );
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
Phone phone = (Phone) o;
return Objects.equals( number, phone.number );
}
@Override
public int hashCode() {
return Objects.hash( number );
}
}
Bidirectional sorted sets
The @SortNatural
and @SortComparator
work the same for bidirectional sorted sets too:
Example 114. Bidirectional natural sorted set
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
@SortNatural
private SortedSet<Phone> phones = new TreeSet<>();
@OneToMany(cascade = CascadeType.ALL)
@SortComparator(ReverseComparator.class)
private SortedSet<Phone> phones = new TreeSet<>();
2.8.8. Maps
A java.util.Map
is ternary association because it required a parent entity a map key and a value. An entity can either be a map key or a map value, depending on the mapping. Hibernate allows using the following map keys:
MapKeyColumn
for value type maps, the map key is a column in the link table that defines the grouping logic
MapKey
the map key is either the primary key or another property of the entity stored as a map entry value
MapKeyEnumerated
the map key is an
Enum
of the target child entityMapKeyTemporal
the map key is a
Date
or aCalendar
of the target child entityMapKeyJoinColumn
- the map key is a an entity mapped as an association in the child entity that’s stored as a map entry key
Value type maps
A map of value type must use the @ElementCollection
annotation, just like value type lists, bags or sets.
Example 115. Value type map with an entity as a map key
public enum PhoneType {
LAND_LINE,
MOBILE
}
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@Temporal(TemporalType.TIMESTAMP)
@ElementCollection
@CollectionTable(name = "phone_register")
@Column(name = "since")
@MapKeyJoinColumn(name = "phone_id", referencedColumnName = "id")
private Map<Phone, Date> phoneRegister = new HashMap<>();
public Person() {
}
public Person(Long id) {
this.id = id;
}
public Map<Phone, Date> getPhoneRegister() {
return phoneRegister;
}
}
@Embeddable
public static class Phone {
private PhoneType type;
private String number;
public Phone() {
}
public Phone(PhoneType type, String number) {
this.type = type;
this.number = number;
}
public PhoneType getType() {
return type;
}
public String getNumber() {
return number;
}
}
CREATE TABLE Person (
id BIGINT NOT NULL ,
PRIMARY KEY ( id )
)
CREATE TABLE phone_register (
Person_id BIGINT NOT NULL ,
since TIMESTAMP ,
number VARCHAR(255) NOT NULL ,
type INTEGER NOT NULL ,
PRIMARY KEY ( Person_id, number, type )
)
ALTER TABLE phone_register
ADD CONSTRAINT FKrmcsa34hr68of2rq8qf526mlk
FOREIGN KEY (Person_id) REFERENCES Person
Adding entries to the map generates the following SQL statements:
Example 116. Adding value type map entries
person.getPhoneRegister().put(
new Phone( PhoneType.LAND_LINE, "028-234-9876" ), new Date()
);
person.getPhoneRegister().put(
new Phone( PhoneType.MOBILE, "072-122-9876" ), new Date()
);
INSERT INTO phone_register (Person_id, number, type, since)
VALUES (1, '072-122-9876', 1, '2015-12-15 17:16:45.311')
INSERT INTO phone_register (Person_id, number, type, since)
VALUES (1, '028-234-9876', 0, '2015-12-15 17:16:45.311')
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.
Example 117. Unidirectional Map
public enum PhoneType {
LAND_LINE,
MOBILE
}
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinTable(
name = "phone_register",
joinColumns = @JoinColumn(name = "phone_id"),
inverseJoinColumns = @JoinColumn(name = "person_id"))
@MapKey(name = "since")
@MapKeyTemporal(TemporalType.TIMESTAMP)
private Map<Date, Phone> phoneRegister = new HashMap<>();
public Person() {
}
public Person(Long id) {
this.id = id;
}
public Map<Date, Phone> getPhoneRegister() {
return phoneRegister;
}
public void addPhone(Phone phone) {
phoneRegister.put( phone.getSince(), phone );
}
}
@Entity(name = "Phone")
public static class Phone {
@Id
@GeneratedValue
private Long id;
private PhoneType type;
private String number;
private Date since;
public Phone() {
}
public Phone(PhoneType type, String number, Date since) {
this.type = type;
this.number = number;
this.since = since;
}
public PhoneType getType() {
return type;
}
public String getNumber() {
return number;
}
public Date getSince() {
return since;
}
}
CREATE TABLE Person (
id BIGINT NOT NULL ,
PRIMARY KEY ( id )
)
CREATE TABLE Phone (
id BIGINT NOT NULL ,
number VARCHAR(255) ,
since TIMESTAMP ,
type INTEGER ,
PRIMARY KEY ( id )
)
CREATE TABLE phone_register (
phone_id BIGINT NOT NULL ,
person_id BIGINT NOT NULL ,
PRIMARY KEY ( phone_id, person_id )
)
ALTER TABLE phone_register
ADD CONSTRAINT FKc3jajlx41lw6clbygbw8wm65w
FOREIGN KEY (person_id) REFERENCES Phone
ALTER TABLE phone_register
ADD CONSTRAINT FK6npoomh1rp660o1b55py9ndw4
FOREIGN KEY (phone_id) REFERENCES Person
Bidirectional maps
Like most bidirectional associations, this relationship is owned by the child-side while the parent is the inverse side abd can propagate its own state transitions to the child entities. In the following example, you can see that @MapKeyEnumerated
was used so that the Phone
enumeration becomes the map key.
Example 118. Bidirectional Map
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true)
@MapKey(name = "type")
@MapKeyEnumerated
private Map<PhoneType, Phone> phoneRegister = new HashMap<>();
public Person() {
}
public Person(Long id) {
this.id = id;
}
public Map<PhoneType, Phone> getPhoneRegister() {
return phoneRegister;
}
public void addPhone(Phone phone) {
phone.setPerson( this );
phoneRegister.put( phone.getType(), phone );
}
}
@Entity(name = "Phone")
public static class Phone {
@Id
@GeneratedValue
private Long id;
private PhoneType type;
private String number;
private Date since;
@ManyToOne
private Person person;
public Phone() {
}
public Phone(PhoneType type, String number, Date since) {
this.type = type;
this.number = number;
this.since = since;
}
public PhoneType getType() {
return type;
}
public String getNumber() {
return number;
}
public Date getSince() {
return since;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
CREATE TABLE Person (
id BIGINT NOT NULL ,
PRIMARY KEY ( id )
)
CREATE TABLE Phone (
id BIGINT NOT NULL ,
number VARCHAR(255) ,
since TIMESTAMP ,
type INTEGER ,
person_id BIGINT ,
PRIMARY KEY ( id )
)
ALTER TABLE Phone
ADD CONSTRAINT FKmw13yfsjypiiq0i1osdkaeqpg
FOREIGN KEY (person_id) REFERENCES Person
2.8.9. Arrays
When it comes to arrays, there is quite a difference between Java arrays and relational database array types (e.g. VARRAY, ARRAY). First, not all database systems implement the SQL-99 ARRAY type, and, for this reason, Hibernate doesn’t support native database array types. Second, Java arrays are relevant for basic types only, since storing multiple embeddables or entities should always be done using the Java Collection API.
2.8.10. Arrays as binary
By default, Hibernate will choose a BINARY type, as supported by the current Dialect
.
Example 119. Binary arrays
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
private String[] phones;
public Person() {
}
public Person(Long id) {
this.id = id;
}
public String[] getPhones() {
return phones;
}
public void setPhones(String[] phones) {
this.phones = phones;
}
}
CREATE TABLE Person (
id BIGINT NOT NULL ,
phones VARBINARY(255) ,
PRIMARY KEY ( id )
)
2.8.11. Collections as basic value type
Notice how all the previous examples explicitly mark the collection attribute as either ElementCollection
, OneToMany
or ManyToMany
. Collections not marked as such require a custom Hibernate Type
and the collection elements must be stored in a single database column.
This is sometimes beneficial. Consider a use-case such as a VARCHAR
column that represents a delimited list/set of Strings.
Example 120. Comma delimited collection
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
@Type(type = "comma_delimited_strings")
private List<String> phones = new ArrayList<>();
public List<String> getPhones() {
return phones;
}
}
public class CommaDelimitedStringsJavaTypeDescriptor extends AbstractTypeDescriptor<List> {
public static final String DELIMITER = ",";
public CommaDelimitedStringsJavaTypeDescriptor() {
super(
List.class,
new MutableMutabilityPlan<List>() {
@Override
protected List deepCopyNotNull(List value) {
return new ArrayList( value );
}
}
);
}
@Override
public String toString(List value) {
return ( (List<String>) value ).stream().collect( Collectors.joining( DELIMITER ) );
}
@Override
public List fromString(String string) {
List<String> values = new ArrayList<>();
Collections.addAll( values, string.split( DELIMITER ) );
return values;
}
@Override
public <X> X unwrap(List value, Class<X> type, WrapperOptions options) {
return (X) toString( value );
}
@Override
public <X> List wrap(X value, WrapperOptions options) {
return fromString( (String) value );
}
}
public class CommaDelimitedStringsType extends AbstractSingleColumnStandardBasicType<List> {
public CommaDelimitedStringsType() {
super(
VarcharTypeDescriptor.INSTANCE,
new CommaDelimitedStringsJavaTypeDescriptor()
);
}
@Override
public String getName() {
return "comma_delimited_strings";
}
}
The developer can use the comma-delimited collection like any other collection we’ve discussed so far and Hibernate will take care of the type transformation part. The collection itself behaves like any other basic value type, as its lifecycle is bound to its owner entity.
Example 121. Comma delimited collection lifecycle
person.phones.add( "027-123-4567" );
person.phones.add( "028-234-9876" );
session.flush();
person.getPhones().remove( 0 );
INSERT INTO Person ( phones, id )
VALUES ( '027-123-4567,028-234-9876', 1 )
UPDATE Person
SET phones = '028-234-9876'
WHERE id = 1
See the Hibernate Integrations Guide for more details on developing custom value type mappings.
2.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.
Example 122. Natural id using single basic attribute
@Entity
public class Company {
@Id
private Integer id;
@NaturalId
private String taxIdentifier;
...
}
Example 123. Natural id using single embedded attribute
@Entity
public class PostalCarrier {
@Id
private Integer id;
@NaturalId
@Embedded
private PostalCode postalCode;
...
}
@Embeddable
public class PostalCode {
...
}
Example 124. Natural id using multiple persistent attributes
@Entity
public class Course {
@Id
private Integer id;
@NaturalId
@ManyToOne
private Department department;
@NaturalId
private String code;
...
}
2.9.2. Natural Id API
As stated before, Hibernate provides an API for loading entities by their associate natural id. This is represented by the org.hibernate.NaturalIdLoadAccess
contract obtained via Session#byNaturalId.
If the entity does not define a natural id, trying to load an entity by its natural id will thrown an exception. | |
---|---|
Example 125. Using NaturalIdLoadAccess
Session session = ...;
Company company = session.byNaturalId( Company.class )
.using( "taxIdentifier","abc-123-xyz" )
.load();
PostalCarrier carrier = session.byNaturalId( PostalCarrier.class )
.using( "postalCode",new PostalCode(... ) )
.load();
Department department = ...;
Course course = session.byNaturalId( Course.class )
.using( "department",department )
.using( "code","101" )
.load();
NaturalIdLoadAccess offers 2 distinct methods for obtaining the entity:
load()
obtains a reference to the entity, making sure that the entity state is initialized
getReference()
- obtains a reference to the entity. The state may or may not be initialized. If the entity is already associated with the current running Session, that reference (loaded or not) is returned. If the entity is not loaded in the current Session and the entity supports proxy generation, an uninitialized proxy is generated and returned, otherwise the entity is loaded from the database and returned.
NaturalIdLoadAccess
allows loading an entity by natural id and at the same time apply a pessimistic lock. For additional details on locking, see the Locking chapter.
We will discuss the last method available on NaturalIdLoadAccess ( setSynchronizationEnabled()
) in Natural Id - Mutability and Caching.
Because the Company
and PostalCarrier
entities define "simple" natural ids, we can load them as follows:
Example 126. Using SimpleNaturalIdLoadAccess
Session session = ...;
Company company = session.bySimpleNaturalId( Company.class )
.load( "abc-123-xyz" );
PostalCarrier carrier = session.bySimpleNaturalId( PostalCarrier.class )
.load( new PostalCode(... ) );
Here we see the use of the org.hibernate.SimpleNaturalIdLoadAccess
contract, obtained via Session#bySimpleNaturalId().
SimpleNaturalIdLoadAccessis similar to
NaturalIdLoadAccessexcept 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 value of that natural id attribute directly to the
load()and
getReference()` methods.
If the entity does not define a natural id, or if the natural id is not of a "simple" type, an exception will be thrown there. | |
---|---|
2.9.3. Natural Id - Mutability and Caching
A natural id may be mutable or immutable. By default the @NaturalId
annotation marks an immutable natural id attribute. An immutable natural id is expected to never change its value. If the value(s) of the natural id attribute(s) change, @NaturalId(mutable=true)
should be used instead.
Example 127. Mutable natural id
@Entity
public class Person {
@Id
private Integer id;
@NaturalId( mutable = true )
private String ssn;
...
}
Within the Session, Hibernate maintains a mapping from natural id values to entity identifiers (PK) values. If natural ids values changed, it is possible for this mapping to become out of date until a flush occurs. To work around this condition, Hibernate will attempt to discover any such pending changes and adjust them when the load()
or getReference()
methods are executed. To be clear: this is only pertinent for mutable natural ids.
This discovery and adjustment have a performance impact. If an application is certain that none of its mutable natural ids already associated with the Session have changed, it can disable that checking by calling setSynchronizationEnabled(false) (the default is true). This will force Hibernate to circumvent the checking of mutable natural ids. |
|
---|---|
Example 128. Mutable natural id synchronization use-case
Session session=...;
Person person = session.bySimpleNaturalId( Person.class ).load( "123-45-6789" );
person.setSsn( "987-65-4321" );
...
// returns null!
person = session.bySimpleNaturalId( Person.class )
.setSynchronizationEnabled( false )
.load( "987-65-4321" );
// returns correctly!
person = session.bySimpleNaturalId( Person.class )
.setSynchronizationEnabled( true )
.load( "987-65-4321" );
Not only can this NaturalId-to-PK resolution be cached in the Session, but we can also have it cached in the second-level cache if second level caching is enabled.
Example 129. Natural id caching
@Entity
@NaturalIdCache
public class Company {
@Id
private Integer id;
@NaturalId
private String taxIdentifier;
...
}
2.10. Dynamic Model
JPA only acknowledges the entity model mapping, so if you are concerned about JPA provider portability it’s best to stick to the strict POJO model. On the other hand, Hibernate can work with both POJO entities as well as with dynamic entity models. | |
---|---|
2.10.1. Dynamic mapping models
Persistent entities do not necessarily have to be represented as POJO/JavaBean classes. Hibernate also supports dynamic models (using Map
s of Map
s at runtime). With this approach, you do not write persistent classes, only mapping files.
| | The mapping of dynamic models is beyond the scope of this chapter. We will discuss using such models with Hibernate, in the
next chapter
. | | --- | --- |
A given entity has just one entity mode within a given SessionFactory. This is a change from previous versions which allowed to define multiple entity modes for an entity and to select which to load. Entity modes can now be mixed within a domain model; a dynamic entity might reference a POJO entity, and vice versa.
Example 130. Working with Dynamic Domain Models
Session s = openSession();
Transaction tx = s.beginTransaction();
// Create a customer entity
Map<String, String>david = new HashMap<>();
david.put( "name","David" );
// Create an organization entity
Map<String, String>foobar = new HashMap<>();
foobar.put( "name","Foobar Inc." );
// Link both
david.put( "organization",foobar );
// Save both
s.save( "Customer",david );
s.save( "Organization",foobar );
tx.commit();
s.close();
The main advantage of dynamic models is quick turnaround time for prototyping without the need for entity class implementation. The main down-fall is that you lose compile-time type checking and will likely deal with many exceptions at runtime. However, as a result of the Hibernate mapping, the database schema can easily be normalized and sound, allowing to add a proper domain model implementation on top later on. | |
---|---|
2.11. Inheritance
Although relational database systems don’t provide support for inheritance, Hibernate provides several strategies to leverage this object-oriented trait onto domain model entities:
- MappedSuperclass
Inheritance is implemented in domain model only without reflecting it in the database schema. See MappedSuperclass.
Single table
The domain model class hierarchy is materialized into a single table which contains entities belonging to different class types. See Single table.
Joined table
The base class and all the subclasses have their own database tables and fetching a subclass entity requires a join with the parent table as well. See Joined table.
Table per class
- Each subclass has its own table containing both the subclass and the base class properties. See Table per class.
2.11.1. MappedSuperclass
In the following domain model class hierarchy, a 'DebitAccount' and a 'CreditAccount' share the same 'Account' base class.
When using MappedSuperclass
, the inheritance is visible in the domain model only and ach database table contains both the base class and the subclass properties.
Example 131. @MappedSuperclass
inheritance
@MappedSuperclass
public static class Account {
@Id
private Long id;
private String owner;
private BigDecimal balance;
private BigDecimal interestRate;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public BigDecimal getBalance() {
return balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
public BigDecimal getInterestRate() {
return interestRate;
}
public void setInterestRate(BigDecimal interestRate) {
this.interestRate = interestRate;
}
}
@Entity(name = "DebitAccount")
public static class DebitAccount extends Account {
private BigDecimal overdraftFee;
public BigDecimal getOverdraftFee() {
return overdraftFee;
}
public void setOverdraftFee(BigDecimal overdraftFee) {
this.overdraftFee = overdraftFee;
}
}
@Entity(name = "CreditAccount")
public static class CreditAccount extends Account {
private BigDecimal creditLimit;
public BigDecimal getCreditLimit() {
return creditLimit;
}
public void setCreditLimit(BigDecimal creditLimit) {
this.creditLimit = creditLimit;
}
}
CREATE TABLE DebitAccount (
id BIGINT NOT NULL ,
balance NUMERIC(19, 2) ,
interestRate NUMERIC(19, 2) ,
owner VARCHAR(255) ,
overdraftFee NUMERIC(19, 2) ,
PRIMARY KEY ( id )
)
CREATE TABLE CreditAccount (
id BIGINT NOT NULL ,
balance NUMERIC(19, 2) ,
interestRate NUMERIC(19, 2) ,
owner VARCHAR(255) ,
creditLimit NUMERIC(19, 2) ,
PRIMARY KEY ( id )
)
Because the @MappedSuperclass inheritance model is not mirrored at database level, it’s not possible to use polymorphic queries (fetching subclasses by their base class). |
|
---|---|
2.11.2. Single table
The single table inheritance strategy maps all subclasses to only one database table. Each subclass declares its own persistent properties. Version and id properties are assumed to be inherited from the root class.
When omitting an explicit inheritance strategy (e.g. @Inheritance ), JPA will choose the SINGLE_TABLE strategy by default. |
|
---|---|
Example 132. Single Table inheritance
@Entity(name = "Account")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public static class Account {
@Id
private Long id;
private String owner;
private BigDecimal balance;
private BigDecimal interestRate;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public BigDecimal getBalance() {
return balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
public BigDecimal getInterestRate() {
return interestRate;
}
public void setInterestRate(BigDecimal interestRate) {
this.interestRate = interestRate;
}
}
@Entity(name = "DebitAccount")
public static class DebitAccount extends Account {
private BigDecimal overdraftFee;
public BigDecimal getOverdraftFee() {
return overdraftFee;
}
public void setOverdraftFee(BigDecimal overdraftFee) {
this.overdraftFee = overdraftFee;
}
}
@Entity(name = "CreditAccount")
public static class CreditAccount extends Account {
private BigDecimal creditLimit;
public BigDecimal getCreditLimit() {
return creditLimit;
}
public void setCreditLimit(BigDecimal creditLimit) {
this.creditLimit = creditLimit;
}
}
CREATE TABLE Account (
DTYPE VARCHAR(31) NOT NULL ,
id BIGINT NOT NULL ,
balance NUMERIC(19, 2) ,
interestRate NUMERIC(19, 2) ,
owner VARCHAR(255) ,
overdraftFee NUMERIC(19, 2) ,
creditLimit NUMERIC(19, 2) ,
PRIMARY KEY ( id )
)
Each subclass in a hierarchy must define a unique discriminator value, which is used to differentiate between rows belonging to separate subclass types. If this is not specified, the DTYPE
column is used as a discriminator, storing the associated subclass name.
Example 133. Single Table inheritance discriminator column
DebitAccount debitAccount = new DebitAccount();
debitAccount.setId( 1L );
debitAccount.setOwner( "John Doe" );
debitAccount.setBalance( BigDecimal.valueOf( 100 ) );
debitAccount.setInterestRate( BigDecimal.valueOf( 1.5d ) );
debitAccount.setOverdraftFee( BigDecimal.valueOf( 25 ) );
CreditAccount creditAccount = new CreditAccount();
creditAccount.setId( 2L );
creditAccount.setOwner( "John Doe" );
creditAccount.setBalance( BigDecimal.valueOf( 1000 ) );
creditAccount.setInterestRate( BigDecimal.valueOf( 1.9d ) );
creditAccount.setCreditLimit( BigDecimal.valueOf( 5000 ) );
entityManager.persist( debitAccount );
entityManager.persist( creditAccount );
INSERT INTO Account (balance, interestRate, owner, overdraftFee, DTYPE, id)
VALUES (100, 1.5, 'John Doe', 25, 'DebitAccount', 1)
INSERT INTO Account (balance, interestRate, owner, creditLimit, DTYPE, id)
VALUES (1000, 1.9, 'John Doe', 5000, 'CreditAccount', 2)
Discriminator
The discriminator column contains marker values that tell the persistence layer what subclass to instantiate for a particular row. Hibernate Core supports the following restricted set of types as discriminator column: String
, char
, int
, byte
, short
, boolean
(including yes_no
, true_false
).
Use the @DiscriminatorColumn
to define the discriminator column as well as the discriminator type.
The enum DiscriminatorType used in javax.persistence.DiscriminatorColumn only contains the values STRING , CHAR and INTEGER which means that not all Hibernate supported types are available via the @DiscriminatorColumn annotation. You can also use @DiscriminatorFormula to express in SQL a virtual discriminator column. This is particularly useful when the discriminator value can be extracted from one or more columns of the table. Both @DiscriminatorColumn and @DiscriminatorFormula are to be set on the root entity (once per persisted hierarchy). |
|
---|---|
There used to be @org.hibernate.annotations.ForceDiscriminator annotation which was deprecated in version 3.6 and later removed. Use @DiscriminatorOptions instead. |
|
---|---|
Assuming a legacy database schema where the discriminator is based on inspecting a certain column, we can take advantage of the Hibernate specific @DiscriminatorFormula
annotation and map the inheritance model as follows:
Example 134. Single Table discriminator formula
@Entity(name = "Account")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorFormula(
"case when debitKey is not null " +
"then 'Debit' " +
"else ( " +
" case when creditKey is not null " +
" then 'Credit' " +
" else 'Unknown' " +
" end ) " +
"end "
)
public static class Account {
@Id
private Long id;
private String owner;
private BigDecimal balance;
private BigDecimal interestRate;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public BigDecimal getBalance() {
return balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
public BigDecimal getInterestRate() {
return interestRate;
}
public void setInterestRate(BigDecimal interestRate) {
this.interestRate = interestRate;
}
}
@Entity(name = "DebitAccount")
@DiscriminatorValue(value = "Debit")
public static class DebitAccount extends Account {
private String debitKey;
private BigDecimal overdraftFee;
private DebitAccount() {
}
public DebitAccount(String debitKey) {
this.debitKey = debitKey;
}
public String getDebitKey() {
return debitKey;
}
public BigDecimal getOverdraftFee() {
return overdraftFee;
}
public void setOverdraftFee(BigDecimal overdraftFee) {
this.overdraftFee = overdraftFee;
}
}
@Entity(name = "CreditAccount")
@DiscriminatorValue(value = "Credit")
public static class CreditAccount extends Account {
private String creditKey;
private BigDecimal creditLimit;
private CreditAccount() {
}
public CreditAccount(String creditKey) {
this.creditKey = creditKey;
}
public String getCreditKey() {
return creditKey;
}
public BigDecimal getCreditLimit() {
return creditLimit;
}
public void setCreditLimit(BigDecimal creditLimit) {
this.creditLimit = creditLimit;
}
}
CREATE TABLE Account (
id int8 NOT NULL ,
balance NUMERIC(19, 2) ,
interestRate NUMERIC(19, 2) ,
owner VARCHAR(255) ,
debitKey VARCHAR(255) ,
overdraftFee NUMERIC(19, 2) ,
creditKey VARCHAR(255) ,
creditLimit NUMERIC(19, 2) ,
PRIMARY KEY ( id )
)
The @DiscriminatorFormula
defines a custom SQL clause that can be used to identify a certain subclass type. The @DiscriminatorValue
defines the mapping between the result of the @DiscriminatorFormula
and the inheritance subclass type.
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 into the data access layer. | |
---|---|
When using polymorphic queries, only a single table is required to be scanned to fetch all associated subclass instances.
Example 135. Single Table polymorphic query
List<Account> accounts = entityManager
.createQuery( "select a from Account a" )
.getResultList();
SELECT singletabl0_.id AS id2_0_ ,
singletabl0_.balance AS balance3_0_ ,
singletabl0_.interestRate AS interest4_0_ ,
singletabl0_.owner AS owner5_0_ ,
singletabl0_.overdraftFee AS overdraf6_0_ ,
singletabl0_.creditLimit AS creditLi7_0_ ,
singletabl0_.DTYPE AS DTYPE1_0_
FROM Account singletabl0_
2.11.3. Joined table
Each subclass can also be mapped to its own table. This is also called table-per-subclass mapping strategy. An inherited state is retrieved by joining with the table of the superclass.
A discriminator column is not required for this mapping strategy. Each subclass must, however, declare a table column holding the object identifier.
Example 136. Join Table
@Entity(name = "Account")
@Inheritance(strategy = InheritanceType.JOINED)
public static class Account {
@Id
private Long id;
private String owner;
private BigDecimal balance;
private BigDecimal interestRate;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public BigDecimal getBalance() {
return balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
public BigDecimal getInterestRate() {
return interestRate;
}
public void setInterestRate(BigDecimal interestRate) {
this.interestRate = interestRate;
}
}
@Entity(name = "DebitAccount")
public static class DebitAccount extends Account {
private BigDecimal overdraftFee;
public BigDecimal getOverdraftFee() {
return overdraftFee;
}
public void setOverdraftFee(BigDecimal overdraftFee) {
this.overdraftFee = overdraftFee;
}
}
@Entity(name = "CreditAccount")
public static class CreditAccount extends Account {
private BigDecimal creditLimit;
public BigDecimal getCreditLimit() {
return creditLimit;
}
public void setCreditLimit(BigDecimal creditLimit) {
this.creditLimit = creditLimit;
}
}
CREATE TABLE Account (
id BIGINT NOT NULL ,
balance NUMERIC(19, 2) ,
interestRate NUMERIC(19, 2) ,
owner VARCHAR(255) ,
PRIMARY KEY ( id )
)
CREATE TABLE CreditAccount (
creditLimit NUMERIC(19, 2) ,
id BIGINT NOT NULL ,
PRIMARY KEY ( id )
)
CREATE TABLE DebitAccount (
overdraftFee NUMERIC(19, 2) ,
id BIGINT NOT NULL ,
PRIMARY KEY ( id )
)
ALTER TABLE CreditAccount
ADD CONSTRAINT FKihw8h3j1k0w31cnyu7jcl7n7n
FOREIGN KEY (id) REFERENCES Account
ALTER TABLE DebitAccount
ADD CONSTRAINT FKia914478noepymc468kiaivqm
FOREIGN KEY (id) REFERENCES Account
The primary key of this table is also a foreign key to the superclass table and described by the @PrimaryKeyJoinColumns . |
|
---|---|
Example 137. Join Table with @PrimaryKeyJoinColumn
@Entity(name = "Account")
@Inheritance(strategy = InheritanceType.JOINED)
public static class Account {
@Id
private Long id;
private String owner;
private BigDecimal balance;
private BigDecimal interestRate;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public BigDecimal getBalance() {
return balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
public BigDecimal getInterestRate() {
return interestRate;
}
public void setInterestRate(BigDecimal interestRate) {
this.interestRate = interestRate;
}
}
@Entity(name = "DebitAccount")
@PrimaryKeyJoinColumn(name = "account_id")
public static class DebitAccount extends Account {
private BigDecimal overdraftFee;
public BigDecimal getOverdraftFee() {
return overdraftFee;
}
public void setOverdraftFee(BigDecimal overdraftFee) {
this.overdraftFee = overdraftFee;
}
}
@Entity(name = "CreditAccount")
@PrimaryKeyJoinColumn(name = "account_id")
public static class CreditAccount extends Account {
private BigDecimal creditLimit;
public BigDecimal getCreditLimit() {
return creditLimit;
}
public void setCreditLimit(BigDecimal creditLimit) {
this.creditLimit = creditLimit;
}
}
CREATE TABLE CreditAccount (
creditLimit NUMERIC(19, 2) ,
account_id BIGINT NOT NULL ,
PRIMARY KEY ( account_id )
)
CREATE TABLE DebitAccount (
overdraftFee NUMERIC(19, 2) ,
account_id BIGINT NOT NULL ,
PRIMARY KEY ( account_id )
)
ALTER TABLE CreditAccount
ADD CONSTRAINT FK8ulmk1wgs5x7igo370jt0q005
FOREIGN KEY (account_id) REFERENCES Account
ALTER TABLE DebitAccount
ADD CONSTRAINT FK7wjufa570onoidv4omkkru06j
FOREIGN KEY (account_id) REFERENCES Account
When using polymorphic queries, the base class table must be joined with all subclass tables to fetch every associated subclass instance.
Example 138. Join Table polymorphic query
List<Account> accounts = entityManager
.createQuery( "select a from Account a" )
.getResultList();
SELECT jointablet0_.id AS id1_0_ ,
jointablet0_.balance AS balance2_0_ ,
jointablet0_.interestRate AS interest3_0_ ,
jointablet0_.owner AS owner4_0_ ,
jointablet0_1_.overdraftFee AS overdraf1_2_ ,
jointablet0_2_.creditLimit AS creditLi1_1_ ,
CASE WHEN jointablet0_1_.id IS NOT NULL THEN 1
WHEN jointablet0_2_.id IS NOT NULL THEN 2
WHEN jointablet0_.id IS NOT NULL THEN 0
END AS clazz_
FROM Account jointablet0_
LEFT OUTER JOIN DebitAccount jointablet0_1_ ON jointablet0_.id = jointablet0_1_.id
LEFT OUTER JOIN CreditAccount jointablet0_2_ ON jointablet0_.id = jointablet0_2_.id
Polymorphic queries can create cartesian products, so caution is advised. | |
---|---|
2.11.4. Table per class
A third option is to map only the concrete classes of an inheritance hierarchy to tables. This is called the table-per-concrete-class strategy. Each table defines all persistent states of the class, including the inherited state.
In Hibernate, it is not necessary to explicitly map such inheritance hierarchies. You can map each class as a separate entity root. However, if you wish use polymorphic associations (e.g. an association to the superclass of your hierarchy), you need to use the union subclass mapping.
Example 139. Table per class
@Entity(name = "Account")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public static class Account {
@Id
private Long id;
private String owner;
private BigDecimal balance;
private BigDecimal interestRate;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public BigDecimal getBalance() {
return balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
public BigDecimal getInterestRate() {
return interestRate;
}
public void setInterestRate(BigDecimal interestRate) {
this.interestRate = interestRate;
}
}
@Entity(name = "DebitAccount")
public static class DebitAccount extends Account {
private BigDecimal overdraftFee;
public BigDecimal getOverdraftFee() {
return overdraftFee;
}
public void setOverdraftFee(BigDecimal overdraftFee) {
this.overdraftFee = overdraftFee;
}
}
@Entity(name = "CreditAccount")
public static class CreditAccount extends Account {
private BigDecimal creditLimit;
public BigDecimal getCreditLimit() {
return creditLimit;
}
public void setCreditLimit(BigDecimal creditLimit) {
this.creditLimit = creditLimit;
}
}
CREATE TABLE Account (
id BIGINT NOT NULL ,
balance NUMERIC(19, 2) ,
interestRate NUMERIC(19, 2) ,
owner VARCHAR(255) ,
PRIMARY KEY ( id )
)
CREATE TABLE CreditAccount (
id BIGINT NOT NULL ,
balance NUMERIC(19, 2) ,
interestRate NUMERIC(19, 2) ,
owner VARCHAR(255) ,
creditLimit NUMERIC(19, 2) ,
PRIMARY KEY ( id )
)
CREATE TABLE DebitAccount (
id BIGINT NOT NULL ,
balance NUMERIC(19, 2) ,
interestRate NUMERIC(19, 2) ,
owner VARCHAR(255) ,
overdraftFee NUMERIC(19, 2) ,
PRIMARY KEY ( id )
)
When using polymorphic queries, a UNION is required to fetch the the base class table along with all subclass tables as well.
Example 140. Table per class polymorphic query
List<Account> accounts = entityManager
.createQuery( "select a from Account a" )
.getResultList();
SELECT tablepercl0_.id AS id1_0_ ,
tablepercl0_.balance AS balance2_0_ ,
tablepercl0_.interestRate AS interest3_0_ ,
tablepercl0_.owner AS owner4_0_ ,
tablepercl0_.overdraftFee AS overdraf1_2_ ,
tablepercl0_.creditLimit AS creditLi1_1_ ,
tablepercl0_.clazz_ AS clazz_
FROM (
SELECT id ,
balance ,
interestRate ,
owner ,
CAST(NULL AS INT) AS overdraftFee ,
CAST(NULL AS INT) AS creditLimit ,
0 AS clazz_
FROM Account
UNION ALL
SELECT id ,
balance ,
interestRate ,
owner ,
overdraftFee ,
CAST(NULL AS INT) AS creditLimit ,
1 AS clazz_
FROM DebitAccount
UNION ALL
SELECT id ,
balance ,
interestRate ,
owner ,
CAST(NULL AS INT) AS overdraftFee ,
creditLimit ,
2 AS clazz_
FROM CreditAccount
) tablepercl0_
| | Polymorphic queries require multiple UNION queries, so be aware of the performance implications of a large class hierarchy. | | --- | --- |