@Entity & Column Mapping
An entity is a plain Java class that Hibernate maps to a database table. The jakarta.persistence annotations let you control exactly how fields become columns: their names, types, nullability, and length. This page covers the everyday mapping annotations you reach for on almost every entity. For the broader picture, see the Spring Data JPA intro.
A minimal entity
Every entity needs the @Entity annotation, an @Id, and a no-args constructor (JPA instantiates entities reflectively).
import jakarta.persistence.*;
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private BigDecimal price;
protected Product() { } // required by JPA
public Product(String name, BigDecimal price) {
this.name = name;
this.price = price;
}
// getters and setters
}
Without a @Table, Hibernate derives the table name from the class name using the active naming strategy (covered below). For Product that becomes the product table.
@Table — controlling the table
Use @Table to set the table name explicitly, place it in a schema, or declare table-level constraints and indexes.
@Entity
@Table(
name = "products",
schema = "catalog",
uniqueConstraints = @UniqueConstraint(columnNames = {"sku"}),
indexes = @Index(name = "idx_product_name", columnList = "name")
)
public class Product { /* ... */ }
Tip: Pluralizing table names (
products) while keeping singular class names (Product) is a common convention.@Table(name = "products")makes that mapping explicit.
@Column — per-field control
@Column is optional, but it’s where you tune the most important physical details.
@Column(name = "product_name", nullable = false, length = 120)
private String name;
@Column(unique = true, updatable = false)
private String sku;
@Column(precision = 10, scale = 2)
private BigDecimal price;
| Attribute | Effect | DDL impact |
|---|---|---|
name | Column name (defaults to field name) | product_name |
nullable | false adds NOT NULL | not null |
length | Max length for VARCHAR (default 255) | varchar(120) |
unique | Adds a unique constraint | unique |
precision / scale | Total/fractional digits for decimals | numeric(10,2) |
insertable / updatable | Exclude from generated INSERT/UPDATE | — |
With ddl-auto: create, the entity above generates:
create table products (
id bigint not null auto_increment,
product_name varchar(120) not null,
sku varchar(255),
price numeric(10,2),
primary key (id)
) engine=InnoDB
Warning:
@Column(nullable = false)only affects schema generation. It is not a runtime validation. Use Bean Validation (@NotNull) to reject bad input before it reaches the database.
@Enumerated — persisting enums
By default JPA stores enums by their ordinal() (an int). That is fragile: reordering enum constants silently corrupts existing data. Always use EnumType.STRING.
public enum Status { ACTIVE, ARCHIVED, OUT_OF_STOCK }
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private Status status;
status varchar(20) not null -- stores 'ACTIVE', not 0
Warning:
EnumType.ORDINAL(the default) breaks the moment someone inserts a new constant in the middle of the enum. SetEnumType.STRINGon every enum field.
@Lob — large objects
@Lob maps very large content to CLOB (text) or BLOB (binary) columns.
@Lob
private String description; // CLOB / TEXT
@Lob
private byte[] image; // BLOB
@Transient — excluding a field
Annotate a field with @Transient to keep it in your Java object but out of the database entirely. It is ideal for derived or computed values.
@Transient
private BigDecimal priceWithTax; // computed at runtime, never persisted
@Temporal and modern date/time types
With Java 8+ date/time types (LocalDate, LocalDateTime, Instant), Hibernate 6 maps them correctly out of the box — @Temporal is not needed.
private LocalDate releaseDate; // maps to DATE
private LocalDateTime createdAt; // maps to TIMESTAMP
private Instant lastModified; // maps to TIMESTAMP
@Temporal is only relevant for the legacy java.util.Date/Calendar types, where you must declare the precision:
@Temporal(TemporalType.TIMESTAMP)
private java.util.Date legacyTimestamp;
Tip: Prefer
java.timetypes. They are immutable, unambiguous, and need no@Temporal. See Java Date/Time usage patterns for working with them.
Naming strategies
When you don’t supply explicit name attributes, Hibernate derives column and table names through two strategies. Spring Boot’s default implicit strategy turns camelCase into snake_case.
| Java field | Default column |
|---|---|
productName | product_name |
releaseDate | release_date |
SKU | sku |
The physical naming strategy is configured in application.yml:
spring:
jpa:
hibernate:
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
properties:
hibernate:
format_sql: true
Note: Spring Boot’s default
CamelCaseToUnderscoresNamingStrategyis what convertsreleaseDatetorelease_date. Override it only if you need to match a legacy schema, and prefer explicit@Column(name = ...)for one-off exceptions.
Putting it together
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 120)
private String name;
@Column(unique = true, updatable = false)
private String sku;
@Column(precision = 10, scale = 2)
private BigDecimal price;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private Status status = Status.ACTIVE;
@Lob
private String description;
private LocalDateTime createdAt = LocalDateTime.now();
@Transient
private BigDecimal priceWithTax;
protected Product() { }
// constructor, getters, setters
}
Related Topics
- Spring Data JPA — section overview
- Primary Keys & Generation —
@Idand@GeneratedValue - Repositories — persist and query entities
- JPA Auditing — auto-populate created/modified timestamps
- Validation Constraints — runtime field validation
- Java Records — for DTOs that carry entity data