Entities and Identifiers
|
This section documents Hibernate ORM 7.4.x (User Guide, Introduction, Query Language Guide, Data Repositories Guide), Jakarta Persistence 3.2, Hibernate Search 8.4.x, and the Hibernate Validator / Hibernate Reactive references — which are the references these pages are written and verified against. This content was generated with the assistance of AI and should be verified against those official docs before being relied on in production. Three older reference books were consulted as bibliography only while preparing these pages and are not the
primary or main source for any page. All three predate Jakarta Persistence 3.2 and Hibernate ORM 6/7 (the
This section’s bibliography lists the reference material consulted while preparing these pages. |
The identifier is the one mapping every entity must have. This page covers declaring an entity, choosing an
access strategy, the identifier-generation strategies, composite keys, and the equals()/hashCode() question
every entity eventually raises.
@Entity and @Table
@Entity
@Table(name = "book", schema = "library")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
// ...
}
@Entity is required; @Table is optional — omitted, Hibernate derives a table name from the entity name
using its naming strategy. @Table also accepts uniqueConstraints and indexes, honored only by
schema-generation tooling (see Schema Generation &
Tooling), never enforced by Hibernate at runtime.
Field vs. property access
Hibernate decides access type by where @Id (or, for property access, the corresponding getter) is placed:
-
Field access — annotations on fields; Hibernate reads/writes fields directly (via reflection or bytecode enhancement), bypassing getters/setters entirely. Simpler, but any custom logic in a setter is skipped by Hibernate itself.
-
Property access — annotations on getters; Hibernate always goes through the accessor methods, so a computed or validated getter/setter runs on every read/write, including Hibernate’s own.
Mixing the two per-entity is allowed (@Access(AccessType.PROPERTY) on an individual field/getter) but adds a
surprising edge case for every reader — pick one access type per entity and stay consistent.
@GeneratedValue strategies
| Strategy | Behavior |
|---|---|
|
Delegates to an auto-increment/identity column ( |
|
Uses a database sequence, optionally with |
|
Emulates a sequence with a dedicated table row, for databases with neither identity columns nor sequences.
Portable but the slowest option (extra |
|
Generates a random |
Custom ( |
Implement the SPI directly (see Distributed ID
Generation for a |
Composite keys
Two ways to model a multi-column primary key:
// (a) @EmbeddedId -- the key is a first-class @Embeddable value type
@Embeddable
public class OrderLineId implements Serializable {
private Long orderId;
private Integer lineNumber;
// equals()/hashCode() over both fields, getters/setters omitted
}
@Entity
public class OrderLine {
@EmbeddedId
private OrderLineId id;
private int quantity;
}
// (b) @IdClass -- the entity itself carries the plain @Id fields; the class named in
// @IdClass mirrors them and supplies equals()/hashCode()
@Entity
@IdClass(OrderLinePk.class)
public class OrderLine2 {
@Id
private Long orderId;
@Id
private Integer lineNumber;
}
@EmbeddedId is generally preferred: the key is one navigable object (orderLine.getId().getLineNumber()),
and it composes naturally with @MapsId below. @IdClass avoids introducing a wrapper type when the composite
fields are also meaningful as plain entity fields, at the cost of duplicating them (once as @Id fields, once
in the @IdClass shadow class).
@NaturalId
@NaturalId marks a business key — unique, rarely-changing, but not the primary key (an ISBN, a username, a
tax ID). It is not itself a generation strategy; it is a lookup optimization: Session.byNaturalId(Book.class)
resolves an entity by its natural id through the same identity-map/first-level-cache machinery as find() by
primary key, and, with @NaturalIdCache, through the second-level cache too (see
Second-Level Cache).
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NaturalId
private String isbn;
}
// ...
Book book = session.byNaturalId(Book.class).using("isbn", "9781617290459").load();
Derived identity with @MapsId
@MapsId lets a @OneToOne (or @ManyToOne) child share its parent’s primary key value instead of having its
own generated one — the classic "one row per parent, same PK" shared-primary-key pattern:
@Entity
public class Employee {
@Id
private Long id;
}
@Entity
public class EmployeeDetails {
@Id
private Long id; // same value as the owning Employee's id
@MapsId
@OneToOne
@JoinColumn(name = "id")
private Employee employee;
}
See Associations for the general @OneToOne shapes this composes
with.
equals() and hashCode() for entities
The three plausible strategies, and why business-key equality is usually the right default:
-
Reference equality (no override) — correct only while both instances live in the same persistence context (the identity map already guarantees
==there); breaks the moment an entity crosses contexts or ends up in aSetalongside a detached/newly-loaded duplicate. -
Primary-key equality — compares the
@Idvalue, but a transient entity has no id yet (null), so two distinct new entities can spuriously look "equal" (bothnull), and an entity’s identity/hash code changes the instant it gets an id assigned afterpersist()— aHashSet<Book>corrupts itself if the entity’s hash code changes while it is a member. -
Business-key equality (recommended) — compare a stable, non-null attribute the domain considers the "real" identity (an
@NaturalId, or a manually chosen field). This is what the User Guide recommends, and it is the only strategy stable across transient, managed, and detached states.
There is no single answer that fits every entity; an entity with no natural business key at all is one of the
few legitimate cases for leaving equals()/hashCode() at their default Object behavior and simply avoiding
putting transient instances into hash-based collections.