Identifiers

Identifiers model the primary key of an entity. They are used to uniquely identify each specific entity.

Hibernate and JPA both make the following assumptions about the corresponding database column(s):

UNIQUE

The values must uniquely identify each row.

NOT NULL

The values cannot be null. For composite ids, no part can be null.

IMMUTABLE

The values, once inserted, can never be changed. This is more a general guide, than a hard-fast rule as opinions vary. JPA defines the behavior of changing the value of the identifier attribute to be undefined; Hibernate simply does not support that. In cases where the values for the PK you have chosen will be updated, Hibernate recommends mapping the mutable value as a natural id, and use a surrogate id for the PK. See Natural Ids.

Technically the identifier does not have to map to the column(s) physically defined as the table primary key. They just need to map to column(s) that uniquely identify each row. However this documentation will continue to use the terms identifier and primary key interchangeably.

Every entity must define an identifier. For entity inheritance hierarchies, the identifier must be defined just on the entity that is the root of the hierarchy.

An identifier might be simple (single value) or composite (multiple values).

Simple identifiers

Simple identifiers map to a single basic attribute, and are denoted using the javax.persistence.Id annotation.

According to JPA only the following types should be used as identifier attribute types:

  • any Java primitive type

  • any primitive wrapper type

  • java.lang.String

  • java.util.Date (TemporalType#DATE)

  • java.sql.Date

  • java.math.BigDecimal

  • java.math.BigInteger

Any types used for identifier attributes beyond this list will not be portable.

Assigned identifiers

Values for simple identifiers can be assigned, as we have seen in the examples above. The expectation for assigned identifier values is that the application assigns (sets them on the entity attribute) prior to calling save/persist.

Example 1. Simple assigned entity identifier
@Entity(name = "Book")
public static class Book {

    @Id
    private Long id;

    private String title;

    private String author;

    //Getters and setters are omitted for brevity
}
Generated identifiers

Values for simple identifiers can be generated. To denote that an identifier attribute is generated, it is annotated with javax.persistence.GeneratedValue

Example 2. Simple generated identifier
@Entity(name = "Book")
public static class Book {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    private String author;

    //Getters and setters are omitted for brevity
}

Additionally, to the type restriction list above, JPA says that if using generated identifier values (see below) only integer types (short, int, long) will be portably supported.

The expectation for generated identifier values is that Hibernate will generate the value when the save/persist occurs.

Identifier value generations strategies are discussed in detail in the Generated identifier values section.

Composite identifiers

Composite identifiers correspond to one or more persistent attributes. Here are the rules governing composite identifiers, as defined by the JPA specification.

  • The composite identifier must be represented by a "primary key class". The primary key class may be defined using the javax.persistence.EmbeddedId annotation (see Composite identifiers with @EmbeddedId), or defined using the javax.persistence.IdClass annotation (see Composite identifiers with @IdClass).

  • The primary key class must be public and must have a public no-arg constructor.

  • The primary key class must be serializable.

  • The primary key class must define equals and hashCode methods, consistent with equality for the underlying database types to which the primary key is mapped.

The restriction that a composite identifier has to be represented by a "primary key class" is only JPA specific. Hibernate does allow composite identifiers to be defined without a "primary key class", although that modeling technique is deprecated and therefore omitted from this discussion.

The attributes making up the composition can be either basic, composite, ManyToOne. Note especially that collections and one-to-ones are never appropriate.

Composite identifiers with @EmbeddedId

Modeling a composite identifier using an EmbeddedId simply means defining an embeddable to be a composition for the one or more attributes making up the identifier, and then exposing an attribute of that embeddable type on the entity.

Example 3. Basic @EmbeddedId
@Entity(name = "SystemUser")
public static class SystemUser {

    @EmbeddedId
    private PK pk;

    private String name;

    //Getters and setters are omitted for brevity
}

@Embeddable
public static class PK implements Serializable {

    private String subsystem;

    private String username;

    public PK(String subsystem, String username) {
        this.subsystem = subsystem;
        this.username = username;
    }

    private PK() {
    }

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        PK pk = (PK) o;
        return Objects.equals( subsystem, pk.subsystem ) &&
                Objects.equals( username, pk.username );
    }

    @Override
    public int hashCode() {
        return Objects.hash( subsystem, username );
    }
}

As mentioned before, EmbeddedIds can even contain @ManyToOne attributes:

Example 4. @EmbeddedId with @ManyToOne
@Entity(name = "SystemUser")
public static class SystemUser {

    @EmbeddedId
    private PK pk;

    private String name;

    //Getters and setters are omitted for brevity
}

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

    @Id
    private String id;

    private String description;

    //Getters and setters are omitted for brevity
}

@Embeddable
public static class PK implements Serializable {

    @ManyToOne(fetch = FetchType.LAZY)
    private Subsystem subsystem;

    private String username;

    public PK(Subsystem subsystem, String username) {
        this.subsystem = subsystem;
        this.username = username;
    }

    private PK() {
    }

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        PK pk = (PK) o;
        return Objects.equals( subsystem, pk.subsystem ) &&
                Objects.equals( username, pk.username );
    }

    @Override
    public int hashCode() {
        return Objects.hash( subsystem, username );
    }
}

Hibernate supports directly modeling the ManyToOne in the PK class, whether @EmbeddedId or @IdClass.

However, that is not portably supported by the JPA specification. In JPA terms one would use "derived identifiers"; for details, see Derived Identifiers.

Composite identifiers with @IdClass

Modeling a composite identifier using an IdClass differs from using an EmbeddedId in that the entity defines each individual attribute making up the composition. The IdClass simply acts as a "shadow".

Example 5. Basic @IdClass
@Entity(name = "SystemUser")
@IdClass( PK.class )
public static class SystemUser {

    @Id
    private String subsystem;

    @Id
    private String username;

    private String name;

    public PK getId() {
        return new PK(
            subsystem,
            username
        );
    }

    public void setId(PK id) {
        this.subsystem = id.getSubsystem();
        this.username = id.getUsername();
    }

    //Getters and setters are omitted for brevity
}

public static class PK implements Serializable {

    private String subsystem;

    private String username;

    public PK(String subsystem, String username) {
        this.subsystem = subsystem;
        this.username = username;
    }

    private PK() {
    }

    //Getters and setters are omitted for brevity

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        PK pk = (PK) o;
        return Objects.equals( subsystem, pk.subsystem ) &&
                Objects.equals( username, pk.username );
    }

    @Override
    public int hashCode() {
        return Objects.hash( subsystem, username );
    }
}

Non-aggregated composite identifiers can also contain ManyToOne attributes as we saw with aggregated ones (still non-portably).

Example 6. IdClass with @ManyToOne
@Entity(name = "SystemUser")
@IdClass( PK.class )
public static class SystemUser {

    @Id
    @ManyToOne(fetch = FetchType.LAZY)
    private Subsystem subsystem;

    @Id
    private String username;

    private String name;

    //Getters and setters are omitted for brevity
}

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

    @Id
    private String id;

    private String description;

    //Getters and setters are omitted for brevity
}

public static class PK implements Serializable {

    private Subsystem subsystem;

    private String username;

    public PK(Subsystem subsystem, String username) {
        this.subsystem = subsystem;
        this.username = username;
    }

    private PK() {
    }

    //Getters and setters are omitted for brevity
}

With non-aggregated composite identifiers, Hibernate also supports "partial" generation of the composite values.

Example 7. @IdClass with partial identifier generation using @GeneratedValue
@Entity(name = "SystemUser")
@IdClass( PK.class )
public static class SystemUser {

    @Id
    private String subsystem;

    @Id
    private String username;

    @Id
    @GeneratedValue
    private Integer registrationId;

    private String name;

    public PK getId() {
        return new PK(
            subsystem,
            username,
            registrationId
        );
    }

    public void setId(PK id) {
        this.subsystem = id.getSubsystem();
        this.username = id.getUsername();
        this.registrationId = id.getRegistrationId();
    }

    //Getters and setters are omitted for brevity
}

public static class PK implements Serializable {

    private String subsystem;

    private String username;

    private Integer registrationId;

    public PK(String subsystem, String username) {
        this.subsystem = subsystem;
        this.username = username;
    }

    public PK(String subsystem, String username, Integer registrationId) {
        this.subsystem = subsystem;
        this.username = username;
        this.registrationId = registrationId;
    }

    private PK() {
    }

    //Getters and setters are omitted for brevity

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        PK pk = (PK) o;
        return Objects.equals( subsystem, pk.subsystem ) &&
                Objects.equals( username, pk.username ) &&
                Objects.equals( registrationId, pk.registrationId );
    }

    @Override
    public int hashCode() {
        return Objects.hash( subsystem, username, registrationId );
    }
}

This feature exists because of a highly questionable interpretation of the JPA specification made by the SpecJ committee.

Hibernate does not feel that JPA defines support for this, but added the feature simply to be usable in SpecJ benchmarks. Use of this feature may or may not be portable from a JPA perspective.

Composite identifiers with associations

Hibernate allows defining a composite identifier out of entity associations. In the following example, the PersonAddress entity identifier is formed of two @ManyToOne associations.

Example 8. Composite identifiers with associations
@Entity(name = "Book")
public static class Book implements Serializable {

    @Id
    @ManyToOne(fetch = FetchType.LAZY)
    private Author author;

    @Id
    @ManyToOne(fetch = FetchType.LAZY)
    private Publisher publisher;

    @Id
    private String title;

    public Book(Author author, Publisher publisher, String title) {
        this.author = author;
        this.publisher = publisher;
        this.title = title;
    }

    private Book() {
    }

    //Getters and setters are omitted for brevity


    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        Book book = (Book) o;
        return Objects.equals( author, book.author ) &&
                Objects.equals( publisher, book.publisher ) &&
                Objects.equals( title, book.title );
    }

    @Override
    public int hashCode() {
        return Objects.hash( author, publisher, title );
    }
}

@Entity(name = "Author")
public static class Author implements Serializable {

    @Id
    private String name;

    //Getters and setters are omitted for brevity

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        Author author = (Author) o;
        return Objects.equals( name, author.name );
    }

    @Override
    public int hashCode() {
        return Objects.hash( name );
    }
}

@Entity(name = "Publisher")
public static class Publisher implements Serializable {

    @Id
    private String name;

    //Getters and setters are omitted for brevity

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }
        Publisher publisher = (Publisher) o;
        return Objects.equals( name, publisher.name );
    }

    @Override
    public int hashCode() {
        return Objects.hash( name );
    }
}

Although the mapping is much simpler than using an @EmbeddedId or an @IdClass, there’s no separation between the entity instance and the actual identifier. To query this entity, an instance of the entity itself must be supplied to the persistence context.

Example 9. Fetching with composite identifiers
Book book = entityManager.find( Book.class, new Book(
    author,
    publisher,
    "High-Performance Java Persistence"
) );

assertEquals( "Vlad Mihalcea", book.getAuthor().getName() );

Generated identifier values

For discussion of generated values for non-identifier attributes, see Generated properties.

Hibernate supports identifier value generation across a number of different types. Remember that JPA portably defines identifier value generation just for integer types.

Identifier value generation is indicates using the javax.persistence.GeneratedValue annotation. The most important piece of information here is the specified javax.persistence.GenerationType which indicates how values will be generated.

The discussions below assume that the application is using Hibernate’s "new generator mappings" as indicated by the hibernate.id.new_generator_mappings setting or MetadataBuilder.enableNewIdentifierGeneratorSupport method during bootstrap. Starting with Hibernate 5, this is set to true by default. If applications set this to false the resolutions discussed here will be very different. The rest of the discussion here assumes this setting is enabled (true).

AUTO (the default)

Indicates that the persistence provider (Hibernate) should choose an appropriate generation strategy. See Interpreting AUTO.

IDENTITY

Indicates that database IDENTITY columns will be used for primary key value generation. See Using IDENTITY columns.

SEQUENCE

Indicates that database sequence should be used for obtaining primary key values. See Using sequences.

TABLE

Indicates that a database table should be used for obtaining primary key values. See Using table identifier generator.

Interpreting AUTO

How a persistence provider interprets the AUTO generation type is left up to the provider.

The default behavior is to look at the java type of the identifier attribute.

If the identifier type is UUID, Hibernate is going to use a UUID identifier.

If the identifier type is numerical (e.g. Long, Integer), then Hibernate is going to use the IdGeneratorStrategyInterpreter to resolve the identifier generator strategy. The IdGeneratorStrategyInterpreter has two implementations:

FallbackInterpreter

This is the default strategy since Hibernate 5.0. For older versions, this strategy is enabled through the hibernate.id.new_generator_mappings configuration property . When using this strategy, AUTO always resolves to SequenceStyleGenerator. If the underlying database supports sequences, then a SEQUENCE generator is used. Otherwise, a TABLE generator is going to be used instead.

LegacyFallbackInterpreter

This is a legacy mechanism that was used by Hibernate prior to version 5.0 or when the hibernate.id.new_generator_mappings configuration property is false. The legacy strategy maps AUTO to the native generator strategy which uses the Dialect#getNativeIdentifierGeneratorStrategy to resolve the actual identifier generator (e.g. identity or sequence).

Using sequences

For implementing database sequence-based identifier value generation Hibernate makes use of its org.hibernate.id.enhanced.SequenceStyleGenerator id generator. It is important to note that SequenceStyleGenerator is capable of working against databases that do not support sequences by switching to a table as the underlying backing. This gives Hibernate a huge degree of portability across databases while still maintaining consistent id generation behavior (versus say choosing between SEQUENCE and IDENTITY). This backing storage is completely transparent to the user.

The preferred (and portable) way to configure this generator is using the JPA-defined javax.persistence.SequenceGenerator annotation.

The simplest form is to simply request sequence generation; Hibernate will use a single, implicitly-named sequence (hibernate_sequence) for all such unnamed definitions.

Example 10. Unnamed sequence
@Entity(name = "Product")
public static class Product {

    @Id
    @GeneratedValue(
        strategy = GenerationType.SEQUENCE
    )
    private Long id;

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

    //Getters and setters are omitted for brevity

}

Using javax.persistence.SequenceGenerator, you can specify a specific database sequence name.

Example 11. Named sequence
@Entity(name = "Product")
public static class Product {

    @Id
    @GeneratedValue(
        strategy = GenerationType.SEQUENCE,
        generator = "sequence-generator"
    )
    @SequenceGenerator(
        name = "sequence-generator",
        sequenceName = "product_sequence"
    )
    private Long id;

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

    //Getters and setters are omitted for brevity

}

The javax.persistence.SequenceGenerator annotataion allows you to specify additional configurations as well.

Example 12. Configured sequence
@Entity(name = "Product")
public static class Product {

    @Id
    @GeneratedValue(
        strategy = GenerationType.SEQUENCE,
        generator = "sequence-generator"
    )
    @SequenceGenerator(
        name = "sequence-generator",
        sequenceName = "product_sequence",
        allocationSize = 5
    )
    private Long id;

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

    //Getters and setters are omitted for brevity

}

Using IDENTITY columns

For implementing identifier value generation based on IDENTITY columns, Hibernate makes use of its org.hibernate.id.IdentityGenerator id generator which expects the identifier to generated by INSERT into the table. IdentityGenerator understands 3 different ways that the INSERT-generated value might be retrieved:

  • If Hibernate believes the JDBC environment supports java.sql.Statement#getGeneratedKeys, then that approach will be used for extracting the IDENTITY generated keys.

  • Otherwise, if Dialect#supportsInsertSelectIdentity reports true, Hibernate will use the Dialect specific INSERT+SELECT statement syntax.

  • Otherwise, Hibernate will expect that the database supports some form of asking for the most recently inserted IDENTITY value via a separate SQL command as indicated by Dialect#getIdentitySelectString.

It is important to realize that this imposes a runtime behavior where the entity row must be physically inserted prior to the identifier value being known. This can mess up extended persistence contexts (conversations). Because of the runtime imposition/inconsistency Hibernate suggest other forms of identifier value generation be used.

There is yet another important runtime impact of choosing IDENTITY generation: Hibernate will not be able to JDBC batching for inserts of the entities that use IDENTITY generation. The importance of this depends on the application specific use cases. If the application is not usually creating many new instances of a given type of entity that uses IDENTITY generation, then this is not an important impact since batching would not have been helpful anyway.

Using table identifier generator

Hibernate achieves table-based identifier generation based on its org.hibernate.id.enhanced.TableGenerator which defines a table capable of holding multiple named value segments for any number of entities.

The basic idea is that a given table-generator table (hibernate_sequences for example) can hold multiple segments of identifier generation values.

Example 13. Unnamed table generator
@Entity(name = "Product")
public static class Product {

    @Id
    @GeneratedValue(
        strategy = GenerationType.TABLE
    )
    private Long id;

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

    //Getters and setters are omitted for brevity

}
create table hibernate_sequences (
    sequence_name varchar2(255 char) not null,
    next_val number(19,0),
    primary key (sequence_name)
)

If no table name is given Hibernate assumes an implicit name of hibernate_sequences.

Additionally, because no javax.persistence.TableGenerator#pkColumnValue is specified, Hibernate will use the default segment (sequence_name='default') from the hibernate_sequences table.

However, you can configure the table identifier generator using the @TableGenerator annotation.

Example 14. Configured table generator
@Entity(name = "Product")
public static class Product {

    @Id
    @GeneratedValue(
        strategy = GenerationType.TABLE,
        generator = "table-generator"
    )
    @TableGenerator(
        name =  "table-generator",
        table = "table_identifier",
        pkColumnName = "table_name",
        valueColumnName = "product_id",
        allocationSize = 5
    )
    private Long id;

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

    //Getters and setters are omitted for brevity

}
create table table_identifier (
    table_name varchar2(255 char) not null,
    product_id number(19,0),
    primary key (table_name)
)

Now, when inserting 3 Product entities, Hibernate generates the following statements:

Example 15. Configured table generator persist example
for ( long i = 1; i <= 3; i++ ) {
    Product product = new Product();
    product.setName( String.format( "Product %d", i ) );
    entityManager.persist( product );
}
select
    tbl.product_id
from
    table_identifier tbl
where
    tbl.table_name = ?
for update

-- binding parameter [1] - [Product]

insert
into
    table_identifier
    (table_name, product_id)
values
    (?, ?)

-- binding parameter [1] - [Product]
-- binding parameter [2] - [1]

update
    table_identifier
set
    product_id= ?
where
    product_id= ?
    and table_name= ?

-- binding parameter [1] - [6]
-- binding parameter [2] - [1]

select
    tbl.product_id
from
    table_identifier tbl
where
    tbl.table_name= ? for update

update
    table_identifier
set
    product_id= ?
where
    product_id= ?
    and table_name= ?

-- binding parameter [1] - [11]
-- binding parameter [2] - [6]

insert
into
    Product
    (product_name, id)
values
    (?, ?)

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

insert
into
    Product
    (product_name, id)
values
    (?, ?)

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

insert
into
    Product
    (product_name, id)
values
    (?, ?)

-- binding parameter [1] as [VARCHAR] - [Product 3]
-- binding parameter [2] as [BIGINT]  - [3]

Using UUID generation

As mentioned above, Hibernate supports UUID identifier value generation. This is supported through its org.hibernate.id.UUIDGenerator id generator.

UUIDGenerator supports pluggable strategies for exactly how the UUID is generated. These strategies are defined by the org.hibernate.id.UUIDGenerationStrategy contract. The default strategy is a version 4 (random) strategy according to IETF RFC 4122. Hibernate does ship with an alternative strategy which is a RFC 4122 version 1 (time-based) strategy (using ip address rather than mac address).

Example 16. Implicitly using the random UUID strategy
@Entity(name = "Book")
public static class Book {

    @Id
    @GeneratedValue
    private UUID id;

    private String title;

    private String author;

    //Getters and setters are omitted for brevity
}

To specify an alternative generation strategy, we’d have to define some configuration via @GenericGenerator. Here we choose the RFC 4122 version 1 compliant strategy named org.hibernate.id.uuid.CustomVersionOneStrategy.

Example 17. Implicitly using the random UUID strategy
@Entity(name = "Book")
public static class Book {

    @Id
    @GeneratedValue( generator = "custom-uuid" )
    @GenericGenerator(
        name = "custom-uuid",
        strategy = "org.hibernate.id.UUIDGenerator",
        parameters = {
            @Parameter(
                name = "uuid_gen_strategy_class",
                value = "org.hibernate.id.uuid.CustomVersionOneStrategy"
            )
        }
    )
    private UUID id;

    private String title;

    private String author;

    //Getters and setters are omitted for brevity
}

Optimizers

Most of the Hibernate generators that separately obtain identifier values from database structures support the use of pluggable optimizers. Optimizers help manage the number of times Hibernate has to talk to the database in order to generate identifier values. For example, with no optimizer applied to a sequence-generator, every time the application asked Hibernate to generate an identifier it would need to grab the next sequence value from the database. But if we can minimize the number of times we need to communicate with the database here, the application will be able to perform better. Which is, in fact, the role of these optimizers.

none

No optimization is performed. We communicate with the database each and every time an identifier value is needed from the generator.

pooled-lo

The pooled-lo optimizer works on the principle that the increment-value is encoded into the database table/sequence structure. In sequence-terms this means that the sequence is defined with a greater-that-1 increment size.

For example, consider a brand new sequence defined as create sequence m_sequence start with 1 increment by 20. This sequence essentially defines a "pool" of 20 usable id values each and every time we ask it for its next-value. The pooled-lo optimizer interprets the next-value as the low end of that pool.

So when we first ask it for next-value, we’d get 1. We then assume that the valid pool would be the values from 1-20 inclusive.

The next call to the sequence would result in 21, which would define 21-40 as the valid range. And so on. The "lo" part of the name indicates that the value from the database table/sequence is interpreted as the pool lo(w) end.

pooled

Just like pooled-lo, except that here the value from the table/sequence is interpreted as the high end of the value pool.

hilo; legacy-hilo

Define a custom algorithm for generating pools of values based on a single value from a table or sequence.

These optimizers are not recommended for use. They are maintained (and mentioned) here simply for use by legacy applications that used these strategies previously.

Applications can also implement and use their own optimizer strategies, as defined by the org.hibernate.id.enhanced.Optimizer contract.

Using @GenericGenerator

@GenericGenerator allows integration of any Hibernate org.hibernate.id.IdentifierGenerator implementation, including any of the specific ones discussed here and any custom ones.

To make use of the pooled or pooled-lo optimizers, the entity mapping must use the @GenericGenerator annotation:

Example 18. Pooled-lo optimizer mapping using @GenericGenerator mapping
@Entity(name = "Product")
public static class Product {

    @Id
    @GeneratedValue(
        strategy = GenerationType.SEQUENCE,
        generator = "product_generator"
    )
    @GenericGenerator(
        name = "product_generator",
        strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
        parameters = {
            @Parameter(name = "sequence_name", value = "product_sequence"),
            @Parameter(name = "initial_value", value = "1"),
            @Parameter(name = "increment_size", value = "3"),
            @Parameter(name = "optimizer", value = "pooled-lo")
        }
    )
    private Long id;

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

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

    //Getters and setters are omitted for brevity

}

Now, when saving 5 Person entities and flushing the Persistence Context after every 3 entities:

Example 19. Pooled-lo optimizer mapping using @GenericGenerator mapping
for ( long i = 1; i <= 5; i++ ) {
    if(i % 3 == 0) {
        entityManager.flush();
    }
    Product product = new Product();
    product.setName( String.format( "Product %d", i ) );
    product.setNumber( String.format( "P_100_%d", i ) );
    entityManager.persist( product );
}
CALL NEXT VALUE FOR product_sequence

INSERT INTO Product (p_name, p_number, id)
VALUES (?, ?, ?)

-- binding parameter [1] as [VARCHAR] - [Product 1]
-- binding parameter [2] as [VARCHAR] - [P_100_1]
-- binding parameter [3] as [BIGINT]  - [1]

INSERT INTO Product (p_name, p_number, id)
VALUES (?, ?, ?)

-- binding parameter [1] as [VARCHAR] - [Product 2]
-- binding parameter [2] as [VARCHAR] - [P_100_2]
-- binding parameter [3] as [BIGINT]  - [2]

CALL NEXT VALUE FOR product_sequence

INSERT INTO Product (p_name, p_number, id)
VALUES (?, ?, ?)

-- binding parameter [1] as [VARCHAR] - [Product 3]
-- binding parameter [2] as [VARCHAR] - [P_100_3]
-- binding parameter [3] as [BIGINT]  - [3]

INSERT INTO Product (p_name, p_number, id)
VALUES (?, ?, ?)

-- binding parameter [1] as [VARCHAR] - [Product 4]
-- binding parameter [2] as [VARCHAR] - [P_100_4]
-- binding parameter [3] as [BIGINT]  - [4]

INSERT INTO Product (p_name, p_number, id)
VALUES (?, ?, ?)

-- binding parameter [1] as [VARCHAR] - [Product 5]
-- binding parameter [2] as [VARCHAR] - [P_100_5]
-- binding parameter [3] as [BIGINT]  - [5]

As you can see from the list of generated SQL statements, you can insert 3 entities for one database sequence call. This way, the pooled and the pooled-lo optimizers allow you to reduce the number of database roundtrips, therefore reducing the overall transaction response time.

Derived Identifiers

JPA 2.0 added support for derived identifiers which allow an entity to borrow the identifier from a many-to-one or one-to-one association.

Example 20. Derived identifier with @MapsId
@Entity(name = "Person")
public static class Person  {

    @Id
    private Long id;

    @NaturalId
    private String registrationNumber;

    public Person() {}

    public Person(String registrationNumber) {
        this.registrationNumber = registrationNumber;
    }

    //Getters and setters are omitted for brevity
}

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

    @Id
    private Long id;

    private String nickName;

    @OneToOne
    @MapsId
    private Person person;

    //Getters and setters are omitted for brevity
}

In the example above, the PersonDetails entity uses the id column for both the entity identifier and for the one-to-one association to the Person entity. The value of the PersonDetails entity identifier is "derived" from the identifier of its parent Person entity.

Example 21. Derived identifier with @MapsId persist example
doInJPA( this::entityManagerFactory, entityManager -> {
    Person person = new Person( "ABC-123" );
    person.setId( 1L );
    entityManager.persist( person );

    PersonDetails personDetails = new PersonDetails();
    personDetails.setNickName( "John Doe" );
    personDetails.setPerson( person );

    entityManager.persist( personDetails );
} );

doInJPA( this::entityManagerFactory, entityManager -> {
    PersonDetails personDetails = entityManager.find( PersonDetails.class, 1L );

    assertEquals("John Doe", personDetails.getNickName());
} );

The @MapsId annotation can also reference columns from an @EmbeddedId identifier as well.

The previous example can also be mapped using @PrimaryKeyJoinColumn.

Example 22. Derived identifier @PrimaryKeyJoinColumn
@Entity(name = "Person")
public static class Person  {

    @Id
    private Long id;

    @NaturalId
    private String registrationNumber;

    public Person() {}

    public Person(String registrationNumber) {
        this.registrationNumber = registrationNumber;
    }

    //Getters and setters are omitted for brevity
}

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

    @Id
    private Long id;

    private String nickName;

    @OneToOne
    @PrimaryKeyJoinColumn
    private Person person;

    public void setPerson(Person person) {
        this.person = person;
        this.id = person.getId();
    }

    //Other getters and setters are omitted for brevity
}

Unlike @MapsId, the application developer is responsible for ensuring that the identifier and the many-to-one (or one-to-one) association are in sync as you can see in the PersonDetails#setPerson method.

@RowId

If you annotate a given entity with the @RowId annotation and the underlying database supports fetching a record by ROWID (e.g. Oracle), then Hibernate can use the ROWID pseudo-column for CRUD operations.

Example 23. @RowId entity maapping
@Entity(name = "Product")
@RowId("ROWID")
public static class Product {

    @Id
    private Long id;

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

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

    //Getters and setters are omitted for brevity

}

Now, when fetching an entity and modifying it, Hibernate uses the ROWID pseudo-column for the UPDATE SQL statement.

Example 24. @RowId example
Product product = entityManager.find( Product.class, 1L );

product.setName( "Smart phone" );
SELECT
    p.id as id1_0_0_,
    p."name" as name2_0_0_,
    p."number" as number3_0_0_,
    p.ROWID as rowid_0_
FROM
    Product p
WHERE
    p.id = ?

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

-- extracted value ([name2_0_0_] : [VARCHAR]) - [Mobile phone]
-- extracted value ([number3_0_0_] : [VARCHAR]) - [123-456-7890]
-- extracted ROWID value: AAAwkBAAEAAACP3AAA

UPDATE
    Product
SET
    "name" = ?,
    "number" = ?
WHERE
    ROWID = ?

-- binding parameter [1] as [VARCHAR] - [Smart phone]
-- binding parameter [2] as [VARCHAR] - [123-456-7890]
-- binding parameter [3] as ROWID     - [AAAwkBAAEAAACP3AAA]