Hibernate uses JPA annotations to map Java classes to database tables. The key annotations are:
| Annotation | Description |
|---|---|
| @Entity | Marks class as a JPA entity (mapped to a table) |
| @Table | Specifies table name, schema, indexes |
| @Id | Marks the primary key field |
| @GeneratedValue | Specifies ID generation strategy |
| @Column | Maps field to a column (name, nullable, length, unique) |
| @Transient | Field is NOT persisted to database |
| @Temporal | Maps Date/Calendar to DATE, TIME, or TIMESTAMP |
| @Enumerated | Maps enum to ORDINAL (int) or STRING |
| @Lob | Maps to BLOB or CLOB (large objects) |
package com.example.entity;
import jakarta.persistence.*;
import java.util.Date;
@Entity
@Table(name = "employees",
uniqueConstraints = @UniqueConstraint(columnNames = {"email"}),
indexes = @Index(name = "idx_dept", columnList = "department_id"))
public class Employee {
// AUTO: Hibernate picks best strategy
// IDENTITY: DB auto-increment (MySQL)
// SEQUENCE: DB sequence (PostgreSQL, Oracle)
// TABLE: Hibernate-managed table (portable)
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "first_name", nullable = false, length = 50)
private String firstName;
@Column(name = "last_name", nullable = false, length = 50)
private String lastName;
@Column(nullable = false, unique = true, length = 100)
private String email;
@Column(precision = 10, scale = 2) // DECIMAL(10,2)
private java.math.BigDecimal salary;
// @Temporal for java.util.Date (not needed for java.time.*)
@Temporal(TemporalType.DATE)
@Column(name = "hire_date")
private Date hireDate;
// Store enum as string "FULL_TIME", "PART_TIME", "CONTRACT"
@Enumerated(EnumType.STRING)
@Column(name = "employment_type", nullable = false)
private EmploymentType employmentType = EmploymentType.FULL_TIME;
// Large text field (CLOB/TEXT)
@Lob
@Column(name = "bio")
private String bio;
// Large binary field (BLOB)
@Lob
@Column(name = "profile_photo")
private byte[] profilePhoto;
// Not persisted to DB
@Transient
private String fullName;
// Computed field
public String getFullName() {
return firstName + " " + lastName;
}
public enum EmploymentType { FULL_TIME, PART_TIME, CONTRACT }
// Constructors, getters, setters...
}
// IDENTITY - uses DB auto-increment (MySQL, SQL Server)
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// SEQUENCE - uses DB sequence (PostgreSQL, Oracle)
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq")
@SequenceGenerator(name = "user_seq", sequenceName = "user_sequence",
allocationSize = 50) // Fetch 50 IDs at once for performance
private Long id;
// table - uses a separate table to track IDs (portable, but slow)
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "user_table_gen")
@TableGenerator(name = "user_table_gen", table = "id_generator",
pkColumnName = "gen_name", valueColumnName = "gen_value",
pkColumnValue = "user_id", allocationSize = 1)
private Long id;
// UUID - generates UUID string
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id; // e.g., "550e8400-e29b-41d4-a716-446655440000"
// Manual ID (no generation)
@Id
private String productCode; // You set this manually
// @Embeddable: value object embedded in another entity
@Embeddable
public class Address {
private String street;
private String city;
private String state;
private String zipCode;
private String country;
// getters/setters
}
// @Embedded: embed the Address in User
@Entity
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "street", column = @Column(name = "home_street")),
@AttributeOverride(name = "city", column = @Column(name = "home_city"))
})
private Address homeAddress;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "street", column = @Column(name = "work_street")),
@AttributeOverride(name = "city", column = @Column(name = "work_city"))
})
private Address workAddress;
}
Entity mapping translates Java fields and value objects into table names, column definitions, constraints, and conversions. Review column names, embedded values, enum storage, nullability, length, precision, scale, and naming rules as one schema contract.
These choices exist before any query runs. A weak mapping can produce incorrect DDL, lossy values, awkward SQL, or migrations that no longer match the application model.
Mapping annotations describe the model Hibernate expects; production schema changes still need a controlled migration. Validate mappings against the deployed schema during CI or startup, but do not rely on automatic destructive DDL for a shared production database. Review generated SQL types, lengths, defaults, nullability, indexes, and constraint names against the migration.
A safe rollout may require adding a nullable column, backfilling data, deploying code that reads both shapes, enforcing the constraint, and later removing the old field. Attribute converters and naming strategies must remain compatible throughout that sequence.
The persistence context uses the entity type and identifier to represent one database row as one managed object.
Use an embeddable when the value belongs to its owning entity, has no independent identity, and is stored as part of the owner’s row or mapped columns. Use an entity when the object has its own lifecycle, relationships, or shared references.
Hash-based collections assume the fields used by hashCode remain stable while the object is stored. If equality depends on a generated ID that changes from null to a database value after persistence, the object may become difficult to find in a Set.
Explore 500+ free tutorials across 20+ languages and frameworks.