Mapping an Entity
Mapping an Entity
In JPA and Hibernate, a plain Java class becomes a persistent entity the moment you annotate it correctly. Three annotations do almost all of the work: @Entity tells the persistence provider that the class participates in ORM, @Table controls how that class maps to a database table, and @Column fine-tunes how each field maps to a column. This lesson covers all three in depth — the defaults, the options, and the trade-offs that matter in production code.
@Entity — Registering a Class with JPA
Placing @Entity on a class is the minimal declaration that a class is managed by the persistence context. Hibernate will include it in schema generation, scanning, and JPQL queries.
By default, JPA maps this class to a table whose name equals the unqualified class name — Product maps to the table Product (or product on case-insensitive engines like MySQL). This is fine for quick prototyping, but in a team or enterprise environment you almost always want explicit control.
protected rather than public to signal that application code should not call it directly — the persistence provider can still reach it.
@Table — Controlling the Table Name and Constraints
@Table sits at the class level alongside @Entity. Its most important attribute is name, which sets the exact SQL table name.
The key attributes of @Table are:
- name — the exact table name in SQL. Always set this explicitly in production code; do not rely on the class-name default.
- schema — the database schema or namespace. Useful in multi-schema deployments (e.g.,
catalog,orders,authas separate schemas in the same database). - catalog — the database catalog name (less commonly needed; relevant for certain databases like SQL Server).
- uniqueConstraints — DDL-level unique constraints generated by Hibernate's schema tooling. These are purely declarative hints for schema generation (
spring.jpa.hibernate.ddl-auto=createorvalidate) — they do not enforce anything at the JPA level at runtime. - indexes — additional indexes to emit during schema generation. Again, DDL-only; Hibernate does not use these at query time.
snake_case table names. Always declare @Table(name = "orders") explicitly rather than leaving it to chance. This also prevents surprises when moving between a case-sensitive filesystem (Linux) and a case-insensitive one (macOS, Windows).
@Column — Mapping Fields to Columns
Without any annotation, JPA maps each non-static, non-transient field to a column whose name matches the field name. @Column lets you override every aspect of that mapping.
The most important @Column attributes:
- name — the SQL column name. Follow the same snake_case convention as table names.
- nullable — when
false, Hibernate adds aNOT NULLconstraint during schema generation. It also enables Bean Validation integration when thejakarta.validationAPI is on the classpath. - unique — adds a single-column unique constraint. For multi-column unique constraints use
@Table(uniqueConstraints = ...)instead. - length — applies to
VARCHARcolumns (String fields). Defaults to 255 — always set an explicit length for string columns that are indexed or whose max length you know. - precision and scale — used for
DECIMAL/NUMERICcolumns (BigDecimal fields).precision= total significant digits;scale= digits after the decimal point. - updatable — when
false, Hibernate omits this column from SQLUPDATEstatements. Use it for audit fields likecreated_atthat must never change after insert. - insertable — when
false, Hibernate omits the column from SQLINSERTstatements. Useful for database-generated columns (e.g., a computed column or a default supplied by a trigger). - columnDefinition — a raw DDL fragment that overrides the entire column definition in schema generation. Use sparingly; it ties your code to a specific database dialect.
nullable = false tells Hibernate to emit NOT NULL when generating the schema and to enable Bean Validation constraints, but it does not throw a JPA exception if you persist an entity with a null value. The database will throw a constraint-violation error on flush — which surfaces as a ConstraintViolationException wrapping a DataIntegrityViolationException in Spring. Always pair it with @NotNull from Bean Validation (jakarta.validation.constraints.NotNull) to catch the problem at the service layer before hitting the database.
Putting It All Together: a Realistic Entity
Here is a complete, production-quality entity that uses all three annotations coherently:
Access Type: Field vs. Property
JPA determines where to read and write values based on where you place the @Id annotation. If @Id is on a field, Hibernate accesses all fields directly via reflection (field access). If @Id is on a getter method, Hibernate accesses all persistent state through getters and setters (property access).
Field access is the modern default and strongly preferred. It keeps mapping annotations co-located with the state they describe, avoids the need for artificial getter/setter boilerplate purely for JPA, and works cleanly with records in future versions. Never mix field and property access in the same entity hierarchy without an explicit @Access annotation — the behaviour is undefined.
Summary
Three annotations form the skeleton of every JPA entity. @Entity registers the class with the persistence provider. @Table controls the SQL table name, schema, unique constraints, and indexes. @Column fine-tunes each field's column name, nullability, length, precision, and mutability. Always be explicit — relying on naming defaults leads to surprises as the codebase grows or the database dialect changes. Pair nullable = false with Bean Validation annotations so violations are caught before they reach the database. In the next lesson you will see how @Id and @GeneratedValue control primary-key generation strategies.