Hibernate.orgCommunity Documentation
5.1.0.Final
Copyright © 2011 Red Hat, Inc.
2016-02-10
Table of Contents
@Basic
annotation@Column
annotationList of Tables
List of Examples
@Basic
@Basic
The goal of this document is to discuss how to map an application's domain model to a relational database.
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 document will focus on JPA mapping where possible. For Hibernate mapping features not supported by JPA we will prefer Hibernate extension annotations, again where possible.
Before diving into the actual topics of how to map different categories of things in your domain mode it helps to understand what those categories are. Hibernate and JPA both express these categorizations.
Hibernate understands both the Java and JDBC representations of application data. The ability to read and write
the this data to/from 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 datatype. It provides information about both of these as well as understanding marshalling between.
When you encounter the term type in discussions of Hibernate, it may refer to the Java type, the JDBC type, or the Hibernate type, depending on context.
To help understand these categorizations, lets look at a simple table and domain model that we wish to map.
Example 1.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:
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;
Entities are application-specific classes which correlate to rows in a 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 Chapter 2, Entity.
Why do we spend so much time categorizing the various types of types? What is the significance of the distinction?
The main categorization was between entity types and value types. To review we said that entities, by nature of their unique identifier, exist independently of other objects whereas values do not. An application cannot "delete" the Contact website; instead, the website is removed when the Contact itself is deleted (obviously you can update the website of that website to null to make it "go away", but even there the access is done through the website).
Nor can you define an association to that Contact website. You can define an association to Contact based on its website (assuming website is unique), but that is totally different.
equality
TBC...
Table of Contents
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 really care if you expose direct access to your instance variables and use them from outside the entity itself. The validity of such a paradigm, however, is debatable at best.
Let's look at each requirement in detail.
This is a requirement for JPA. It is more of a recommendation for Hibernate.
A central feature of Hibernate is the ability to lazy load an entity's data 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 classes that are declared final and that do not implement such an interface with Hibernate; you just will not be able to use proxies for lazy association fetching which will ultimately limit your options for performance tuning.
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.
You should also avoid declaring persistent attribute getters and setters as final for the reasons already mentioned. And of course making the instance variable hold the entiy's persistent state would just simply not make any sense.
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 note care about the visibility as long as the system's SecurityManager allows overriding the visibility. That said, the constructor should be defined with at least package visibility if you wish to leverage runtime proxy generation.
Standard, portable JPA essentially requires this. Otherwise your model would violate the requirement quoted above in regards to accessing the entity persistent state fields directly from outside the entity itself.
Although Hibernate does not require it, it is recommended to follow JavaBean conventions by defining getters and setters for you entities persistent attributes. You can still tell Hibernate to directly access the entity's 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 visibility for the getter/setter should be at least package visibility.
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 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 the entity a specific name for use in JPQL queries; by default the name is the
unqualified name of the entity class.
Example 2.1. Simple @Entity
@Entity public class Simple { @Id private Integer id; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } }
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 2.2. Simple @Entity with @Table
@Entity @Table( catalog="CRM", schema="purchasing", name="t_simple" ) public class Simple { @Id private Integer id; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } }
For details on mapping the identifier, see Chapter 6, Identifiers
JPA defines support for optimistic locking based on either a version (sequential numeric) or timestamp
strategy. To enable this style of optimistic locking simply add the
javax.persistence.Version
to the persistent attribute that defines the
optimistic locking value. According to JPA, the valid types for these attributes are limited to:
int, or Integer
short, or Short
long, or Long
java.sql.Timestamp
Example 2.3. Version
@Entity public class Course { @Id private Integer id; @Version private Integer version; ... }
@Entity public class Thing { @Id private Integer id; @Version 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 present.
VERSION
(the default) - performs optimistic locking based on a @Version
as described above.
ALL
- Perform optimistic locking based on *all* fields as part of an
expanded WHERE clause restriction for the UPDATE/DELETE SQL statement.
DIRTY
- Perform optimistic locking based on *dirty* fields as part of
an expanded WHERE clause restriction for the UPDATE/DELETE SQL statement
* dynamic models (hbm.xml) * Map mode * proxy solutions (hibernate-core/src/test/java/org/hibernate/test/dynamicentity/tuplizer2)
Table of Contents
@Basic
annotation@Column
annotationBasic value types usually map a single database value, or column, to a single, non-aggregated Java type. Hibernate provides a number of built-in basic types, which follow the natural mappings recommended in the JDBC specifications.
Internally Hibernate uses a registry of basic types when it needs to resolve the specific
org.hibernate.type.Type
to use in certain situations.
Table 3.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 |
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 3.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 jar to your classpath; Hibernate will take care of the rest. See Section 3.11, “Mapping Date/Time Values”
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. We will revisit this detail later.
Strictly speaking, a basic type is denoted with the javax.persistence.Basic
annotation. Generally speaking the @Basic
annotation can be ignored. Both of the
following examples are ultimately the same.
Example 3.1. With @Basic
@Entity public class Product { @Id @Basic private Integer id; @Basic private String sku; @Basic private String name; @Basic private String description; }
Example 3.2. 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:
* JPA's "support" for Serializable types is to directly serialize their state to the database.
If provider portability is a concern, you should stick to just these basic types. Note that JPA
2.1 did add the notion of an javax.persistence.AttributeConverter
to help alleviate some of these concerns; see Section 3.12, “JPA 2.1 AttributeConverters”
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 affect 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 but that
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 Hibernate User Guide for additional information on
fetching and on bytecode enhancement.
JPA defines rules for implicitly determining the name of tables and columns. For a detailed discussion of implicit naming see ???.
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 3.3. 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.
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 (Section 3.5, “Explicit BasicTypes”) 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
Section 3.6, “Custom 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 3.4. 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 Section 3.9, “Mapping Nationalized Character Data”
Additionally the description is to be handled as a LOB. Again, for better ways to indicate LOBs see Section 3.8, “Mapping LOBs”.
The org.hibernate.annotations.Type#type
attribute can name any of the following:
FQN of any org.hibernate.type.Type
implementation
Any key registered with BasicTypeRegistry
The name of any known "type definitions"
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 2 approaches to developing a custom BasicType. As a means of illustrating the different approaches, lets consider a use case where we need to support a class called Fizzywig from a third party library. Lets assume that Fizzywig naturally stores as a VARCHAR.
The first approach is to directly implement the BasicType interface.
Example 3.5. 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 3.6. 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.
Hibernate supports the mapping of Java enums as basic value types in a number of different ways.
The original JPA-compliant way to map enums was via the @Enumerated
and @MapKeyEnumerated
for map keys annotations which works on the principle that
the enum values are stored according to one of 2 strategies indicated by
javax.persistence.EnumType
:
ORDINAL
- stored according to the enum value's ordinal position within
the enum class, as indicated by java.lang.Enum#ordinal
STRING
- stored according to the enum value's name, as indicated by
java.lang.Enum#name
Example 3.7. @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
- null
0
- MALE
1
- FEMALE
Example 3.8. @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
- null
MALE
- MALE
FEMALE
- FEMALE
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 3.9. Enum mapping with AttributeConverter example
@Entity public class Person { ... @Basic @Convert( converter=GenderConverter.class ) public Gender gender; } public enum Gender { MALE( 'M' ), FEMALE( 'F' ); private final char code; private Gender(char code) { this.code = code; } public char getCode() { return code; } public static Gender fromCode(char code) { if ( code == 'M' || code == 'm' ) { return MALE; } if ( code == 'F' || code == 'f' ) { return FEMALE; } throw ... } } @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 Section 3.12, “JPA 2.1 AttributeConverters”.
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
.
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 3.10. Enum mapping with custom Type example
import org.hibernate.type.descriptor.java.CharacterTypeDescriptor; @Entity public class Person { ... @Basic @Type( type = GenderType.class ) public Gender gender; } public enum Gender { MALE( 'M' ), FEMALE( 'F' ); private final char code; private Gender(char code) { this.code = code; } public char getCode() { return code; } public static Gender fromCode(char code) { if ( code == 'M' || code == 'm' ) { return MALE; } if ( code == 'F' || code == 'f' ) { return FEMALE; } throw ... } } @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; } } 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 ); } }
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 Section 3.6, “Custom BasicTypes”.
Mapping LOBs (database Large OBjects) come in 2 forms, those using the JDBC locator types and those materializing the LOB data.
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 lets assume we have a CLOB column that we would like to map (NCLOB character LOB data will be covered in Section 3.9, “Mapping Nationalized Character Data”).
Let's first map this using the JDBC locator.
Example 3.12. CLOB - locator mapping
@Entity public class Product { ... @Lob @Basic public Clob description; ... }
We could also map a materialized form.
Example 3.13. 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 3.14. CLOB - materialized char[] mapping
@Entity public class Product { ... @Lob @Basic public char[] description; ... }
We'd map BLOB data in a similar fashion.
Let's first map this using the JDBC locator.
Example 3.16. BLOB - locator mapping
@Entity public class Step { ... @Lob @Basic public Blob instructions; ... }
We could also map a materialized BLOB form.
Example 3.17. BLOB - materialized mapping
@Entity public class Step { ... @Lob @Basic public byte[] instructions; ... }
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 datatype, Hibernate defines the
@Nationalized
annotation.
Example 3.18. NVARCHAR mapping
@Entity public class Product { ... @Basic @Nationalized public String description; ... }
Example 3.19. 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 3.20. 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.
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() )
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.
Maps the UUID to a String using java.util.UUID#toString and java.util.UUID#fromString and stores that as CHAR or VARCHAR data.
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 choses 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.
Hibernate supports using UUID values as identifiers. They can even be generated! For details see the discussion of generators in Section 6.3, “Generated identifier values”
Table of Contents
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.
Example 4.1. Simple composition 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; ... } }
A composition is another form of value type. The lifecycle of a composition is defined by the thing that contains it.
A composition inherits the attribute access of its parent. For details on attribute access, see ???.
Compositions can be made up of basic values as well as associations, with the caveat that compositions which are used as collection elements cannot themselves define collections.
This is the form of composition you will see most often. Here an entity or another composition is the container.
Example 4.2. Simple Embedded
@Entity public class Person { @Id private Integer id; @Embedded private Name name; ... }
Notice that JPA defines 2 terms for composition: Embeddable and Embedded. Embeddable is used to describe the composition class itself (Name). Embedded is used to describe a usage of that composition (Person.name).
The composition here is the Name type related to Person.name.
Example 4.3. 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 instead.
Example 4.4. Alternative to composition
@Entity public class Person { @Id private Integer id; private String firstName; private String middleName; private String lastName; ... }
The composition form is certainly more OO. And that becomes more evident as we work with multiple compositions.
Example 4.5. Multiple compositions
@Entity public class Contact { @Id private Integer id; @Embedded private Name name; @Embedded private Address homeAddress; @Embedded private Address mailingAddress; @Embedded private Address workAddress; ... }
It is certainly more convenient to work with the compositions. However, an interesting thing happens in this particular example. By default, this mapping actually will not work as-is. The problem is in how JPA defines implicit naming rules for columns that are part of a composition, which say that all of the Address compositions would map to the same implicit column names.
This occurs any time we have multiple compositions based on the same embeddable in a given parent. We have a few options to handle this issue.
The JPA-defined way to handle this situation is through the use of its AttributeOverride annotation.
Example 4.6. 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; ... }
Now, essentially there are no implicit column names in the Address compositions. We have explicitly named them.
This is a Hibernate specific feature. Users concerned with JPA provider portability should instead prefer explicit column naming with AttributeOverride as per Section 4.2.1, “JPA's AttributeOverride”
Hibernate naming strategies are covered in detail in ???. 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 compositions.
Example 4.7. Enabling composition-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();
Now the "path" to attributes are used in the implicit column naming.
Example 4.8. Enabling composition-safe implicit naming
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, ... )
You could even develop your own to do special implicit naming.
Collections of compositions are specifically value collections (as compositions are a value type). Value collections are covered in detail in Section 5.2, “Collections of value types”.
The one thing to add to the discussion of value collections in regards to compositions is that the composition cannot, in turn, define collections.
Compositions can also be used as the key values for Maps. Mapping Maps and their keys is convered in detail in ???.
Again, compositions used as a Map key cannot, in turn, define collections.
Compositions can also be used as entity identifiers. This usage is covered in detail in ???
Again, compositions used as an entity identifier cannot, in turn, define collections.
Table of Contents
discussions of what it means for them to be value types - lifecycle, opt-locking
Collections have the usual behavior of value types. They are automatically persisted when referenced by a persistent object and are 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 attributes do not support null value semantics; Hibernate does not distinguish between a null collection reference and an empty collection.
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 (really all persistence providers) will use their own collection implementations which conform to the Java Collections Framework interfaces.
collection of values - elements can be of any value type except for collections (in fact even compositions as the element cannot contain collections) * basics * compositions
Notice how all the previous examples explicitly mark the collection attribute as either ElementCollection, OneToMany or ManyToMany. Collections not marked as such, or collections explicitly maked with @Basic are treated as JPA basic values. Meaning there value is stored into a single column in the containing table.
This is sometimes beneficial. Consider a use-case such as a VARCHAR column that represents a delimited list or set of Strings.
Example 5.1. Delimited set of tags
@Entity public static class Post { @Id public Integer id; @Basic @Type( type = "delimited_strings" ) Set<String> tags; } public static class DelimitedStringsType extends AbstractSingleColumnStandardBasicType<Set> { public DelimitedStringsType() { super( VarcharTypeDescriptor.INSTANCE, new DelimitedStringsJavaTypeDescriptor() ); } @Override public String getName() { return "delimited_strings"; } } public static class DelimitedStringsJavaTypeDescriptor extends AbstractTypeDescriptor<Set> { public DelimitedStringsJavaTypeDescriptor() { super( Set.class, new MutableMutabilityPlan<Set>() { @Override protected Set deepCopyNotNull(Set value) { Set<String> copy = new HashSet<String>(); copy.addAll( value ); return copy; } } ); } @Override public String toString(Set value) { return null; } @Override public Set fromString(String string) { return null; } @Override public <X> X unwrap(Set value, Class<X> type, WrapperOptions options) { return null; } @Override public <X> Set wrap(X value, WrapperOptions options) { return null; } }
See the Hibernate Integrations Guide for more details on developing custom value type mappings. Without the special type mapping above the "set of tags" would have simply been marshalled using serialization.
Table of Contents
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 Chapter 7, Natural Ids.
Technically the identifier does not have to map to the column(s) physically defined as the entity table's 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).
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 6.1. 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 6.2. 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 Section 6.3, “Generated identifier values”.
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 Section 6.2.1, “Composite identifiers - aggregated (EmbeddedId)”) or defined using the
javax.persistence.IdClass
annotation (see
Section 6.2.2, “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 key is mapped.
The restriction that a composite identifier has to be represented by a "primary key class" is a JPA restriction. Hibernate does allow composite identifiers to be defined without a "primary key class", but use of that modeling technique is deprecated and not discussed here.
The attributes making up the composition can be either basic, composite, ManyToOne. Note especially that collections and one-to-ones are never appropriate.
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 6.3. Basic EmbeddedId
@Entity public class Login { @Embeddable public static class PK implements Serializable { private String system; private String username; ... } @EmbeddedId private PK pk; ... }
As mentioned before, EmbeddedIds can even contain ManyToOne attributes.
Example 6.4. EmbeddedId with ManyToOne
@Entity public class Login { @Embeddable public static class PK implements Serializable { @ManyToOne private System system; private String username; ... } @EmbeddedId private PK pk; ... }
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 Section 6.4, “Derived Identifiers”.
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 6.5. Basic IdClass
@Entity @IdClass(PK.class) public class Login { public static class PK implements Serializable { private String system; private String username; ... } @Id private String system; @Id private String username; ... }
Non-aggregated composite identifiers can also contain ManyToOne attributes as we saw with aggregated ones (still non-portably)
Example 6.6. IdClass with ManyToOne
@Entity @IdClass(PK.class) public class Login { public static class PK implements Serializable { private System system; private String username; ... } @Id @ManyToOne private System system; @Id private String username; ... }
With non-aggregated composite identifiers, Hibernate also supports "partial" generation of the composite values.
Example 6.7. IdClass with partial generation
@Entity @IdClass(PK.class) public class LogFile { public static class PK implements Serializable { private String name; private LocalDate date; private Integer uniqueStamp; ... } @Id private String name; @Id private LocalDate date; @Id @GeneratedValue 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.
For discussion of generated values for non-identifier attributes, see ???
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.
This is set to true by default, however 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).
GenerationTypes
AUTO
(the default) - Indicates that the persistence provider (Hibernate) should
chose an appropriate generation strategy. See Section 6.3.1, “Interpreting AUTO”.
IDENTITY
- Indicates that database IDENTITY columns will be used for
primary key value generation. See Section 6.3.3, “Using IDENTITY columns”.
SEQUENCE
- Indicates that database sequence should be used for obtaining
primary key values. See Section 6.3.2, “Using sequences”.
TABLE
- Indicates that a database table should be used for obtaining
primary key values. See Section 6.3.4, “Using identifier table”.
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 -> Section 6.3.2, “Using sequences”.
If the given name matches the name for a javax.persistence.TableGenerator
annotation -> Section 6.3.4, “Using identifier table”.
If the given name matches the name for a org.hibernate.annotations.GenericGenerator
annotation -> Section 6.3.6, “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:
If it is UUID -> Section 6.3.5, “Using UUID generation”
Otherwise -> Section 6.3.2, “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 6.8. Unnamed sequence
@Entity public class MyEntity { @Id @GeneratedValue(generation=SEQUENCE) public Integer id; ... }
Or a specifically named sequence can be requested
Example 6.9. 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 6.10. Configured sequence
@Entity public class MyEntity { @Id @GeneratedValue(generation=SEQUENCE, name="my_sequence") @SequenceGenerator( name = "my_sequence", schema = "globals", allocationSize = 30 ) public Integer id; ... }
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.
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 6.11. 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 6.12. 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.
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 6.13. 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 6.14. 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; ... }
@GenericGenerator allows integration of any Hibernate org.hibernate.id.IdentifierGenerator
implementation, including any of the specific ones discussed here and any custom ones.
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, everytime 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.
No optimization is performed. We communicate with the database each and every time an identifier value is needed from the generator.
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 my_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.
Just like pooled-lo, except that here the value from the table/sequence is interpreted as the high end of the value pool.
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.
Natural ids represent unique identifiers that naturally exist within your domain model. Even if a natural id does not make a good primary key, it still is useful to tell Hibernate about it. As we will see later, Hibernate provides a dedicated, efficient API for loading and entity by its natural-id much like it offers for loading by identifier (PK).
Natural ids are defined in terms of one or more persistent attributes.
Example 7.1. Natural id using single basic attribute
@Entity public class Company { @Id private Integer id; @NaturalId private String taxIdentifier; ... }
Example 7.2. Natural id using single embedded attribute
@Entity public class PostalCarrier { @Id private Integer id; @NaturalId @Embedded private PostalCode postalCode; ... } @Embeddable public class PostalCode { ... }
Example 7.3. Natural id using multiple persistent attributes
@Entity public class Course { @Id private Integer id; @NaturalId @ManyToOne private Department department; @NaturalId private String code; ... }
As stated before, Hibernate provides an API for loading entities by natural id. This is represented by the
org.hibernate.NaturalIdLoadAccess
contract obtained via
Session#byNaturalId
. If the entity does not define a natural id, an exception
will be thrown there.
Example 7.4. 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 associated with the Session already,
that reference (loaded or not) is returned; else if the entity supports proxy
generation, an uninitialized proxy is generated and returned; otherwise
the entity is loaded from the database and returned.
NaturalIdLoadAccess also allows to request locking for the load. We might use that to load an entity by natural id and at the same time apply a pessimistic lock. For additional details on locking, see the Hibernate User Guide.
We will discuss the last method available on NaturalIdLoadAccess
(setSynchronizationEnabled
) in Section 7.3, “Natural Id - Mutability and Caching”.
Because the Company and PostalCarrier entities define "simple" natural ids, we also allow simplified access to load them based on the natural ids.
Example 7.5. 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
. SimpleNaturalIdLoadAccess is similar
to NaturalIdLoadAccess except that it does not define the using
method. Instead,
because these "simple" natural ids are defined based on just one attribute we can directly pass the
corresponding 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 it does define is not simple, an exception will be thrown there.
A natural id may be mutable or immutable. By default @NaturalId
marks
an immutable natural id. An immutable natural id is expected to never change values.
If the values of the natural id attribute(s) can change, @NaturalId(mutable=true)
should be used instead.
Example 7.6. 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 pk values. If natural ids
values have 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 for them
when the load
or getReference
method is 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 7.7. 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 7.8. Natural id caching
@Entity @NaturalIdCache public class Company { @Id private Integer id; @NaturalId private String taxIdentifier; ... }