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.

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

VARCHAR

java.util.Currency

currency, java.util.Currency

LocaleType

VARCHAR

java.util.Locale

locale, java.utility.locale

TimeZoneType

VARCHAR, using the TimeZone ID

java.util.TimeZone

timezone, java.util.TimeZone

UrlType

VARCHAR

java.net.URL

url, java.net.URL

ClassType

VARCHAR (class FQN)

java.lang.Class

class, java.lang.Class

BlobType

BLOB

java.sql.Blob

blob, java.sql.Blob

ClobType

CLOB

java.sql.Clob

clob, java.sql.Clob

BinaryType

VARBINARY

byte[]

binary, byte[]

MaterializedBlobType

BLOB

byte[]

materialized_blob

ImageType

LONGVARBINARY

byte[]

image

WrapperBinaryType

VARBINARY

java.lang.Byte[]

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

CharArrayType

VARCHAR

char[]

characters, char[]

CharacterArrayType

VARCHAR

java.lang.Character[]

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

UUIDBinaryType

BINARY

java.util.UUID

uuid-binary, java.util.UUID

UUIDCharType

CHAR, can also read VARCHAR

java.util.UUID

uuid-char

PostgresUUIDType

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

java.util.UUID

pg-uuid

SerializableType

VARBINARY

implementors of java.lang.Serializable

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

StringNVarcharType

NVARCHAR

java.lang.String

nstring

NTextType

LONGNVARCHAR

java.lang.String

ntext

NClobType

NCLOB

java.sql.NClob

nclob, java.sql.NClob

MaterializedNClobType

NCLOB

java.lang.String

materialized_nclob

PrimitiveCharacterArrayNClobType

NCHAR

char[]

N/A

CharacterNCharType

NCHAR

java.lang.Character

ncharacter

CharacterArrayNClobType

NCLOB

java.lang.Character[]

N/A

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

DurationType

BIGINT

java.time.Duration

Duration, java.time.Duration

InstantType

TIMESTAMP

java.time.Instant

Instant, java.time.Instant

LocalDateTimeType

TIMESTAMP

java.time.LocalDateTime

LocalDateTime, java.time.LocalDateTime

LocalDateType

DATE

java.time.LocalDate

LocalDate, java.time.LocalDate

LocalTimeType

TIME

java.time.LocalTime

LocalTime, java.time.LocalTime

OffsetDateTimeType

TIMESTAMP

java.time.OffsetDateTime

OffsetDateTime, java.time.OffsetDateTime

OffsetTimeType

TIME

java.time.OffsetTime

OffsetTime, java.time.OffsetTime

ZonedDateTimeType

TIMESTAMP

java.time.ZonedDateTime

ZonedDateTime, java.time.ZonedDateTime

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

JTSGeometryType

depends on the dialect

com.vividsolutions.jts.geom.Geometry

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

GeolatteGeometryType

depends on the dialect

org.geolatte.geom.Geometry

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

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

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

The @Basic annotation

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

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

    @Id
    @Basic
    private Integer id;

    @Basic
    private String sku;

    @Basic
    private String name;

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

    @Id
    private Integer id;

    private String sku;

    private String name;

    private String description;
}

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

  • Java primitive types (boolean, int, etc)

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

  • java.lang.String

  • java.math.BigInteger

  • java.math.BigDecimal

  • java.util.Date

  • java.util.Calendar

  • java.sql.Date

  • java.sql.Time

  • java.sql.Timestamp

  • byte[] or Byte[]

  • char[] or Character[]

  • enums

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

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

The @Basic annotation defines 2 attributes.

optional - boolean (defaults to true)

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

fetch - FetchType (defaults to EAGER)

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

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 3. Explicit column naming
@Entity(name = "Product")
public class Product {

    @Id
    private Integer id;

    private String sku;

    private String name;

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

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

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

BasicTypeRegistry

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

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

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

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

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

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

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

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 4. Using @org.hibernate.annotations.Type
@Entity(name = "Product")
public class Product {

    @Id
    private Integer id;

    private String sku;

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

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

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

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

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

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

  • Any key registered with BasicTypeRegistry

  • The name of any known type definitions

Custom BasicTypes

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

There are two approaches to developing a custom type:

  • implementing a BasicType and registering it

  • implementing a UserType which doesn’t require type registration

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

Implementing a BasicType

The first approach is to directly implement the BasicType interface.

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

First, we need to extend the AbstractSingleColumnStandardBasicType like this:

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

    public static final BitSetType INSTANCE = new BitSetType();

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

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

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

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

}

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

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

    private static final String DELIMITER = ",";

    public static final BitSetTypeDescriptor INSTANCE = new BitSetTypeDescriptor();

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

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

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

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

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

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

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

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

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

or using the MetadataBuilder

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

MetadataSources sources = new MetadataSources( standardRegistry );

MetadataBuilder metadataBuilder = sources.getMetadataBuilder();

metadataBuilder.applyBasicType( BitSetType.INSTANCE );

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

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

    @Id
    private Integer id;

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

    public Integer getId() {
        return id;
    }

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

    public BitSet getBitSet() {
        return bitSet;
    }

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

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

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

    @Id
    private Integer id;

    private BitSet bitSet;

    public Integer getId() {
        return id;
    }

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

    public BitSet getBitSet() {
        return bitSet;
    }

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

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

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

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

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

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

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

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

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

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

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

Implementing a UserType

The second approach is to implement the UserType interface.

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

	public static final BitSetUserType INSTANCE = new BitSetUserType();

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

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

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

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

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

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

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

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

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

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

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

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

The entity mapping looks as follows:

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

    @Id
    private Integer id;

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

    public Integer getId() {
        return id;
    }

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

    public BitSet getBitSet() {
        return bitSet;
    }

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

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

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

or using the MetadataBuilder

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

MetadataSources sources = new MetadataSources( standardRegistry );

MetadataBuilder metadataBuilder = sources.getMetadataBuilder();

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

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

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

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

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

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

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

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

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

Mapping enums

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

@Enumerated

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

ORDINAL

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

STRING

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

Assuming the following enumeration:

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

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

NULL

For null values

0

For the LAND_LINE enum

1

For the MOBILE enum

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

    @Id
    private Long id;

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

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

    //Getters and setters are omitted for brevity

}

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

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

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

NULL

For null values

LAND_LINE

For the LAND_LINE enum

MOBILE

For the MOBILE enum

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

    @Id
    private Long id;

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

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

    //Getters and setters are omitted for brevity

}

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

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

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

Example 21. Enum with custom constructor
public enum Gender {

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

    private final char code;

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

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

    public char getCode() {
        return code;
    }
}

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

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

    @Id
    private Long id;

    private String name;

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

    //Getters and setters are omitted for brevity

}

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

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

        return value.getCode();
    }

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

        return Gender.fromCode( value );
    }
}

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

NULL

For null values

'M'

For the MALE enum

'F'

For the FEMALE enum

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

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

Mapping an AttributeConverter using HBM mappings

When using HBM mappings, you can still make use of the JPA AttributeConverter because Hibernate supports such mapping via the type attribute as demonstrated by the following example.

Let’s consider we have an application-specific Money type:

Example 23. Application-specific Money type
public class Money {

    private long cents;

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

    public long getCents() {
        return cents;
    }

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

Now, we want to use the Money type when mapping the Account entity:

Example 24. Account entity using the Money type
public class Account {

    private Long id;

    private String owner;

    private Money balance;

    //Getters and setters are omitted for brevity
}

Since Hibernate has no knowledge how to persist the Money type, we could use a JPA AttributeConverter to transform the Money type as a Long. For this purpose, we are going to use the following MoneyConverter utility:

Example 25. MoneyConverter implementing the JPA AttributeConverter interface
public class MoneyConverter
        implements AttributeConverter<Money, Long> {

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

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

To map the MoneyConverter using HBM configuration files you need to use the converted:: prefix in the type attribute of the property element.

Example 26. HBM mapping for AttributeConverter
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="org.hibernate.userguide.mapping.converter.hbm">
    <class name="Account" table="account" >
        <id name="id"/>

        <property name="owner"/>

        <property name="balance"
            type="converted::org.hibernate.userguide.mapping.converter.hbm.MoneyConverter"/>

    </class>
</hibernate-mapping>
Custom type

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

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

    @Id
    private Long id;

    private String name;

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

    //Getters and setters are omitted for brevity

}

public class GenderType extends AbstractSingleColumnStandardBasicType<Gender> {

    public static final GenderType INSTANCE = new GenderType();

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

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

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

public class GenderJavaTypeDescriptor extends AbstractTypeDescriptor<Gender> {

    public static final GenderJavaTypeDescriptor INSTANCE =
        new GenderJavaTypeDescriptor();

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

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

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

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

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

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

NULL

For null values

'M'

For the MALE enum

'F'

For the FEMALE enum

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

Mapping LOBs

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

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

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

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

The JDBC LOB locator types include:

  • java.sql.Blob

  • java.sql.Clob

  • java.sql.NClob

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

Mapping CLOB

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

Considering we have the following database table:

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

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

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

    @Id
    private Integer id;

    private String name;

    @Lob
    private Clob warranty;

    //Getters and setters are omitted for brevity

}

To persist such an entity, you have to create a Clob using the ClobProxy Hibernate utility:

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

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

product.setWarranty( ClobProxy.generateProxy( warranty ) );

entityManager.persist( product );

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

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

try (Reader reader = product.getWarranty().getCharacterStream()) {
    assertEquals( "My product warranty", toString( reader ) );
}

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

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

    @Id
    private Integer id;

    private String name;

    @Lob
    private String warranty;

    //Getters and setters are omitted for brevity

}

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

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

We might even want the materialized data as a char array (although this might not be a very good idea).

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

    @Id
    private Integer id;

    private String name;

    @Lob
    private char[] warranty;

    //Getters and setters are omitted for brevity

}
Mapping BLOB

BLOB data is mapped in a similar fashion.

Considering we have the following database table:

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

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

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

    @Id
    private Integer id;

    private String name;

    @Lob
    private Blob image;

    //Getters and setters are omitted for brevity

}

To persist such an entity, you have to create a Blob using the BlobProxy Hibernate utility:

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

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

product.setImage( BlobProxy.generateProxy( image ) );

entityManager.persist( product );

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

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

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

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

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

    @Id
    private Integer id;

    private String name;

    @Lob
    private byte[] image;

    //Getters and setters are omitted for brevity

}

Mapping Nationalized Character Data

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

  • NCHAR

  • NVARCHAR

  • LONGNVARCHAR

  • NCLOB

Considering we have the following database table:

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

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

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

    @Id
    private Integer id;

    private String name;

    @Nationalized
    private String warranty;

    //Getters and setters are omitted for brevity

}

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

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

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

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

    @Id
    private Integer id;

    private String name;

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

    //Getters and setters are omitted for brevity

}

To persist such an entity, you have to create a NClob using the NClobProxy Hibernate utility:

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

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

product.setWarranty( NClobProxy.generateProxy( warranty ) );

entityManager.persist( product );

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

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

try (Reader reader = product.getWarranty().getCharacterStream()) {
    assertEquals( "My product warranty", toString( reader ) );
}

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

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

    @Id
    private Integer id;

    private String name;

    @Lob
    @Nationalized
    private String warranty;

    //Getters and setters are omitted for brevity

}

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

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

    @Id
    private Integer id;

    private String name;

    @Lob
    @Nationalized
    private char[] warranty;

    //Getters and setters are omitted for brevity

}

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

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() ).

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.

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.

PostgreSQL-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.

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.

Mapping Date/Time Values

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

DATE

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

TIME

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

TIMESTAMP

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

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

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

Considering the following entity:

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

    @Id
    @GeneratedValue
    private Long id;

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

    //Getters and setters are omitted for brevity

}

When persisting such entity:

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

Hibernate generates the following INSERT statement:

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

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

If we change the @Temporal type to TIME:

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

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

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

When the @Temporal type is set to TIMESTAMP:

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

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

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

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

Mapping Java 8 Date/Time Values

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

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

DATE

java.time.LocalDate

TIME

java.time.LocalTime, java.time.OffsetTime

TIMESTAMP

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

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

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

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

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

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

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

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

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

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

JPA 2.1 AttributeConverters

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

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

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

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

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

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

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

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

    @Id
    @GeneratedValue
    private Long id;

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

    //Getters and setters are omitted for brevity

}

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

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

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

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

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

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

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

JPA 2.1 AttributeConverter Mutability Plan

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

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

Immutable types

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

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

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

    @Id
    @GeneratedValue
    private Long id;

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

    //Getters and setters are omitted for brevity

}

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

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

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

public static class Money {

    private long cents;

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

    public long getCents() {
        return cents;
    }

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

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

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

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

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

    @Id
    private Long id;

    private String owner;

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

    //Getters and setters are omitted for brevity

}

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

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

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

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

SQL quoted identifiers

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

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

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

    @Id
    private Long id;

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

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

    //Getters and setters are omitted for brevity

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

    @Id
    private Long id;

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

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

    //Getters and setters are omitted for brevity

}

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

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

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

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

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

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

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

    @Id
    private Long id;

    private String name;

    private String number;

    //Getters and setters are omitted for brevity

}

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

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

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

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

Generated properties

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

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

NEVER (the default)

the given property value is not generated within the database.

INSERT

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

ALWAYS

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

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

@Generated annotation

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

Considering the following entity:

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

    @Id
    private Long id;

    private String firstName;

    private String lastName;

    private String middleName1;

    private String middleName2;

    private String middleName3;

    private String middleName4;

    private String middleName5;

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

}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Considering the following entity:

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

    public static final CurrentUser INSTANCE = new CurrentUser();

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

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

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

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

public static class LoggedUserGenerator implements ValueGenerator<String> {

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

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

    @Id
    private Long id;

    private String firstName;

    private String lastName;

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

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

}

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

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

doInJPA( this::entityManagerFactory, entityManager -> {

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

    entityManager.persist( person );
} );

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

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

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

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

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

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

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

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

The supported property types are:

  • java.util.Date

  • java.util.Calendar

  • java.sql.Date

  • java.sql.Time

  • java.sql.Timestamp

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

    @Id
    @GeneratedValue
    private Long id;

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

    public Event() {}

    public Long getId() {
        return id;
    }

    public Date getTimestamp() {
        return timestamp;
    }
}

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

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

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

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

The supported property types are:

  • java.util.Date

  • java.util.Calendar

  • java.sql.Date

  • java.sql.Time

  • java.sql.Timestamp

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

    @Id
    @GeneratedValue
    private Long id;

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

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

    private Long cents;

    //Getters and setters are omitted for brevity

}

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

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

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

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

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

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

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

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

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

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

Database-generated values

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

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

    @Id
    @GeneratedValue
    private Long id;

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

    public Event() {}

    public Long getId() {
        return id;
    }

    public Date getTimestamp() {
        return timestamp;
    }
}

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

public static class FunctionCreationValueGeneration
        implements AnnotationValueGeneration<FunctionCreationTimestamp> {

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

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

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

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

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

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

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

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

In-memory-generated values

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

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

    @Id
    @GeneratedValue
    private Long id;

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

    public Event() {}

    public Long getId() {
        return id;
    }

    public Date getTimestamp() {
        return timestamp;
    }
}

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

public static class FunctionCreationValueGeneration
        implements AnnotationValueGeneration<FunctionCreationTimestamp> {

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

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

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

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

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

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

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

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

Column transformers: read and write expressions

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

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

    @Id
    private Long id;

    @NaturalId
    private String username;

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

    private int accessLevel;

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

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

    //Getters and setters omitted for brevity
}

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

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

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

    @Id
    private Long id;

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

    //Getters and setters omitted for brevity

}

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

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

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

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

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

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

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

@Formula

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

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

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

    @Id
    private Long id;

    private Double credit;

    private Double rate;

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

    //Getters and setters omitted for brevity

}

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

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

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

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

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

@Where

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

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

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

    @Id
    private Long id;

    private String name;

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

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

    //Getters and setters omitted for brevity

}

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

    @Id
    private Long id;

    @ManyToOne
    private Client client;

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

    private Double amount;

    private Double rate;

    private boolean active;

    //Getters and setters omitted for brevity

}

If the database contains the following entities:

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

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

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

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

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

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

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

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

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

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

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

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

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

@WhereJoinTable

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

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

    @Id
    private Long id;

    private String title;

    private String author;

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

    //Getters and setters omitted for brevity

}

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

    @Id
    private Long id;

    private String name;

    //Getters and setters omitted for brevity

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

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

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

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

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

alter table Book_Reader
    add created_on timestamp
    default current_timestamp

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

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

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

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

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

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

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

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

@Filter

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

Now, considering we have the following Account entity:

Example 83. @Filter mapping entity-level usage
@Entity(name = "Account")
@FilterDef(
    name="activeAccount",
    parameters = @ParamDef(
        name="active",
        type="boolean"
    )
)
@Filter(
    name="activeAccount",
    condition="active_status = :active"
)
public static class Account {

    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Client client;

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

    private Double amount;

    private Double rate;

    @Column(name = "active_status")
    private boolean active;

    //Getters and setters omitted for brevity
}

Notice that the active property is mapped to the active_status column.

This mapping was done to show you that the @Filter condition uses a SQL condition, and not a JPQL filtering criteria.

As already explained, we can also apply the @Filter annotation for collections as illustrated by the Client entity:

Example 84. @Filter mapping collection-level usage
@Entity(name = "Client")
public static class Client {

    @Id
    private Long id;

    private String name;

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

    //Getters and setters omitted for brevity

    public void addAccount(Account account) {
        account.setClient( this );
        this.accounts.add( account );
    }
}

If we persist a Client with three associated Account entities, Hibernate will execute the following SQL statements:

Example 85. Persisting and fetching entities with a @Filter mapping
Client client = new Client()
.setId( 1L )
.setName( "John Doe" );

client.addAccount(
    new Account()
    .setId( 1L )
    .setType( AccountType.CREDIT )
    .setAmount( 5000d )
    .setRate( 1.25 / 100 )
    .setActive( true )
);

client.addAccount(
    new Account()
    .setId( 2L )
    .setType( AccountType.DEBIT )
    .setAmount( 0d )
    .setRate( 1.05 / 100 )
    .setActive( false )
);

client.addAccount(
    new Account()
    .setType( AccountType.DEBIT )
    .setId( 3L )
    .setAmount( 250d )
    .setRate( 1.05 / 100 )
    .setActive( true )
);

entityManager.persist( client );
INSERT INTO Client (name, id)
VALUES ('John Doe', 1)

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

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

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

By default, without explicitly enabling the filter, Hibernate is going to fetch all Account entities.

Example 86. Query entities mapped without activating the @Filter
List<Account> accounts = entityManager.createQuery(
    "select a from Account a", Account.class)
.getResultList();

assertEquals( 3, accounts.size());
SELECT
    a.id as id1_0_,
    a.active_status as active2_0_,
    a.amount as amount3_0_,
    a.client_id as client_i6_0_,
    a.rate as rate4_0_,
    a.account_type as account_5_0_
FROM
    Account a

If the filter is enabled and the filter parameter value is provided, then Hibernate is going to apply the filtering criteria to the associated Account entities.

Example 87. Query entities mapped with @Filter
entityManager
    .unwrap( Session.class )
    .enableFilter( "activeAccount" )
    .setParameter( "active", true);

List<Account> accounts = entityManager.createQuery(
    "select a from Account a", Account.class)
.getResultList();

assertEquals( 2, accounts.size());
SELECT
    a.id as id1_0_,
    a.active_status as active2_0_,
    a.amount as amount3_0_,
    a.client_id as client_i6_0_,
    a.rate as rate4_0_,
    a.account_type as account_5_0_
FROM
    Account a
WHERE
    a.active_status = true

Filters apply to entity queries, but not to direct fetching.

Therefore, in the following example, the filter is not taken into consideration when fetching an entity from the Persistence Context.

Fetching entities mapped with @Filter
entityManager
    .unwrap( Session.class )
    .enableFilter( "activeAccount" )
    .setParameter( "active", true);

Account account = entityManager.find( Account.class, 2L );

assertFalse( account.isActive() );
SELECT
    a.id as id1_0_0_,
    a.active_status as active2_0_0_,
    a.amount as amount3_0_0_,
    a.client_id as client_i6_0_0_,
    a.rate as rate4_0_0_,
    a.account_type as account_5_0_0_,
    c.id as id1_1_1_,
    c.name as name2_1_1_
FROM
    Account a
WHERE
    a.id = 2

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

Just like with entity queries, collections can be filtered as well, but only if the filter is explicitly enabled on the currently running Hibernate Session.

Example 88. Traversing collections without activating the @Filter
Client client = entityManager.find( Client.class, 1L );

assertEquals( 3, client.getAccounts().size() );
SELECT
    c.id as id1_1_0_,
    c.name as name2_1_0_
FROM
    Client c
WHERE
    c.id = 1

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

When activating the @Filter and fetching the accounts collections, Hibernate is going to apply the filter condition to the associated collection entries.

Example 89. Traversing collections mapped with @Filter
entityManager
    .unwrap( Session.class )
    .enableFilter( "activeAccount" )
    .setParameter( "active", true);

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

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

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

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

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

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

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

@FilterJoinTable

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

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

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

    @Id
    private Long id;

    private String name;

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

    //Getters and setters omitted for brevity

    public void addAccount(Account account) {
        this.accounts.add( account );
    }
}

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

    @Id
    private Long id;

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

    private Double amount;

    private Double rate;

    //Getters and setters omitted for brevity
}

The firstAccounts filter will allow us to get only the Account entities that have the order_id (which tells the position of every entry inside the accounts collection) less than a given number (e.g. maxOrderId).

Let’s assume our database contains the following entities:

Example 91. Persisting and fetching entities with a @FilterJoinTable mapping
Client client = new Client()
.setId( 1L )
.setName( "John Doe" );

client.addAccount(
    new Account()
    .setId( 1L )
    .setType( AccountType.CREDIT )
    .setAmount( 5000d )
    .setRate( 1.25 / 100 )
);

client.addAccount(
    new Account()
    .setId( 2L )
    .setType( AccountType.DEBIT )
    .setAmount( 0d )
    .setRate( 1.05 / 100 )
);

client.addAccount(
    new Account()
    .setType( AccountType.DEBIT )
    .setId( 3L )
    .setAmount( 250d )
    .setRate( 1.05 / 100 )
);

entityManager.persist( client );
INSERT INTO Client (name, id)
VALUES ('John Doe', 1)

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

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

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

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

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

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

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

The collections can be filtered only if the associated filter is enabled on the currently running Hibernate Session.

Example 92. Traversing collections mapped with @FilterJoinTable without enabling the filter
Client client = entityManager.find( Client.class, 1L );

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

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

If we enable the filter and set the maxOrderId to 1, when fetching the accounts collections, Hibernate is going to apply the @FilterJoinTable clause filtering criteria, and we will get just 2 Account entities, with the order_id values of 0 and 1.

Example 93. Traversing collections mapped with @FilterJoinTable
Client client = entityManager.find( Client.class, 1L );

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

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

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

@Filter with @SqlFragmentAlias

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

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

    @Id
    private Long id;

    private Double amount;

    private Double rate;

    private boolean active;

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

    //Getters and setters omitted for brevity

}

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

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

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

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

@Any mapping

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

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

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

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

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

    String getName();

    T getValue();
}


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

    @Id
    private Long id;

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

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

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

    @Override
    public Integer getValue() {
        return value;
    }

    //Getters and setters omitted for brevity
}


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

    @Id
    private Long id;

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

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

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

    @Override
    public String getValue() {
        return value;
    }

    //Getters and setters omitted for brevity
}

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

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

    @Id
    private Long id;

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

    //Getters and setters are omitted for brevity

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

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

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

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

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

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

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

To see the @Any annotation in action, consider the next examples.

If we persist an IntegerProperty as well as a StringProperty entity, and associate the StringProperty entity with a PropertyHolder, Hibernate will generate the following SQL queries:

Example 99. @Any mapping persist example
IntegerProperty ageProperty = new IntegerProperty();
ageProperty.setId( 1L );
ageProperty.setName( "age" );
ageProperty.setValue( 23 );

session.persist( ageProperty );

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

session.persist( nameProperty );

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

session.persist( namePropertyHolder );
INSERT INTO integer_property
       ( "name", "value", id )
VALUES ( 'age', 23, 1 )

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

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

When fetching the PropertyHolder entity and navigating its property association, Hibernate will fetch the associated StringProperty entity like this:

Example 100. @Any mapping query example
PropertyHolder propertyHolder = session.get( PropertyHolder.class, 1L );

assertEquals("name", propertyHolder.getProperty().getName());
assertEquals("John Doe", propertyHolder.getProperty().getValue());
SELECT ph.id AS id1_1_0_,
       ph.property_type AS property2_1_0_,
       ph.property_id AS property3_1_0_
FROM   property_holder ph
WHERE  ph.id = 1


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

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

In the following example, the PropertyRepository entity has a collection of Property entities.

The repository_properties link table holds the associations between PropertyRepository and Property entities.

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

    @Id
    private Long id;

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

    //Getters and setters are omitted for brevity

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

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

To see the @ManyToAny annotation in action, consider the next examples.

If we persist an IntegerProperty as well as a StringProperty entity, and associate both of them with a PropertyRepository parent entity, Hibernate will generate the following SQL queries:

Example 102. @ManyToAny mapping persist example
IntegerProperty ageProperty = new IntegerProperty();
ageProperty.setId( 1L );
ageProperty.setName( "age" );
ageProperty.setValue( 23 );

session.persist( ageProperty );

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

session.persist( nameProperty );

PropertyRepository propertyRepository = new PropertyRepository();
propertyRepository.setId( 1L );

propertyRepository.getProperties().add( ageProperty );
propertyRepository.getProperties().add( nameProperty );

session.persist( propertyRepository );
INSERT INTO integer_property
       ( "name", "value", id )
VALUES ( 'age', 23, 1 )

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

INSERT INTO property_repository ( id )
VALUES ( 1 )

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

When fetching the PropertyRepository entity and navigating its properties association, Hibernate will fetch the associated IntegerProperty and StringProperty entities like this:

Example 103. @ManyToAny mapping query example
PropertyRepository propertyRepository = session.get( PropertyRepository.class, 1L );

assertEquals(2, propertyRepository.getProperties().size());

for(Property property : propertyRepository.getProperties()) {
    assertNotNull( property.getValue() );
}
SELECT pr.id AS id1_1_0_
FROM   property_repository pr
WHERE  pr.id = 1

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

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

@JoinFormula mapping

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

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

    @Id
    private Long id;

    private String firstName;

    private String lastName;

    private String phoneNumber;

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

    //Getters and setters omitted for brevity

}

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

    @Id
    private Integer id;

    private String name;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

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

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

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

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

Considering we have the following entities:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@JoinColumnOrFormula mapping

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

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

    @Id
    private Long id;

    private String firstName;

    private String lastName;

    private String language;

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

    //Getters and setters omitted for brevity

}

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

    @Id
    private Integer id;

    private String name;

    private String primaryLanguage;

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

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getPrimaryLanguage() {
        return primaryLanguage;
    }

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

    public boolean isDefault() {
        return _default;
    }

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

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

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

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

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

Considering we have the following entities:

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

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

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

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

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

} );

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

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

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

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

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

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

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

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

@Target mapping

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

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

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

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

@Embeddable
public static class GPS implements Coordinates {

    private double latitude;

    private double longitude;

    private GPS() {
    }

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

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

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

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

    @Id
    @GeneratedValue
    private Long id;

    private String name;

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

    //Getters and setters omitted for brevity

}

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

Assuming we have persisted the following City entity:

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

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

    entityManager.persist( cluj );
} );

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

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

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

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

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

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

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

@Parent mapping

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

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

    private double latitude;

    private double longitude;

    @Parent
    private City city;

    private GPS() {
    }

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

    public double getLatitude() {
        return latitude;
    }

    public double getLongitude() {
        return longitude;
    }

    public City getCity() {
        return city;
    }

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

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

    @Id
    @GeneratedValue
    private Long id;

    private String name;

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

    //Getters and setters omitted for brevity

}

Assuming we have persisted the following City entity:

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

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

    entityManager.persist( cluj );
} );

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

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

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

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

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