JPA Entities & Mapping
Reviewed & published by Brayan K
A JPA entity is a plain Java class that maps to a database table. With a handful of annotations — @Entity, @Id, @GeneratedValue, @Column, @Table — you describe how objects become rows and back again.
Part of the free Java course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
Before You Start
You should understand classes and objects and the basics of Spring Data JPA. These examples use the jakarta.persistence annotations.
What You'll Learn
- Marking a class persistent with @Entity
- Defining the primary key with @Id
- Generating keys with @GeneratedValue
- Customizing columns with @Column
- Naming tables with @Table
- Excluding fields with @Transient
The Big Idea — A Form That Fills a Spreadsheet Row
💡 Analogy: Think of an @Entity class as a paper form and the database table as a spreadsheet. Each field on the form is a column; each filled-in form becomes one row. @Id is the row number, @GeneratedValue means the spreadsheet stamps that number for you, and @Column notes are the rules ("this box can't be blank, max 100 characters"). You design the form once; the framework files every submission as a tidy row.
Object-relational mapping is just this translation: objects to rows, rows to objects, automatically.
1️⃣ A Basic Entity
Annotate a class with @Entity, mark its key with @Id, and let the database generate ids. @Table and @Column fine-tune the names and constraints.
import jakarta.persistence.*;
// @Entity marks this class as a JPA-managed table mapping.
// @Table lets you name the table explicitly (defaults to "Customer").
@Entity
@Table(name = "customers")
public class Customer {
@Id // primary key
@GeneratedValue(strategy = GenerationType.IDENTITY) // DB auto-increment
private Long id;
@Column(nullable = false, length = 100) // NOT NULL VARCHAR(100)
private String name;
@Column(name = "email_address", unique = true) // custom column name + UNIQUE
private String email;
// JPA requires a no-arg constructor (the provider builds instances reflectively).
protected Customer() { }
public Customer(String name, String email) {
this.name = name;
this.email = email;
}
public Long getId() { return id; }
public String getName() { return name; }
public String getEmail() { return email; }
}-- The mapping above produces schema roughly like this (Hibernate DDL):
CREATE TABLE customers (
id BIGINT NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
email_address VARCHAR(255),
PRIMARY KEY (id),
UNIQUE (email_address)
);2️⃣ How a Generated Id Behaves
With @GeneratedValue, the id is null until the row is inserted; the database then assigns the value. Here is a plain-Java simulation of that lifecycle you can actually run.
// PLAIN-JAVA illustration of how an auto-generated id behaves:
// before persisting, id is null; the database assigns it on insert.
public class Main {
static class Customer {
Long id; // null until "saved"
String name;
Customer(String name) { this.name = name; }
}
// Simulate a database assigning sequential ids on insert.
static long nextId = 1;
static void save(Customer c) { c.id = nextId++; }
public static void main(String[] args) {
Customer a = new Customer("Ana");
Customer b = new Customer("Ben");
System.out.println("before save, a.id = " + a.id);
save(a);
save(b);
System.out.println("after save, a.id = " + a.id);
System.out.println("after save, b.id = " + b.id);
}
}3️⃣ Keeping Fields Out — @Transient
JPA persists every field by default. A value you only compute in memory — a formatted price, a cached total — should be marked @Transient so it never becomes a column.
import jakarta.persistence.*;
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(name = "price_cents", nullable = false)
private int priceCents; // store money as integer cents
// @Transient: computed in memory only — NOT a database column.
@Transient
private String displayPrice;
protected Product() { }
public Product(String name, int priceCents) {
this.name = name;
this.priceCents = priceCents;
}
// derived value, recomputed after load — never stored
public String getDisplayPrice() {
return "$" + (priceCents / 100) + "." + String.format("%02d", priceCents % 100);
}
}🧠 Quick Recall
Predict before you peek.
Answer: Invoice — JPA derives the table name from the entity name unless @Table(name = "...") overrides it.
Answer: null for a generated Long id — the database assigns it on insert.
Answer: @Transient. The field stays in memory only and is never mapped to a column.
🎯 YOUR TURN — Map a Book
Create a Book entity with a generated id, a NOT NULL title, and a unique isbn_13 column.
import jakarta.persistence.*;
// YOUR TURN: map a Book entity.
// - table name "books"
// - id: Long, auto-generated by the database
// - title: String, NOT NULL, max length 200
// - isbn: String, mapped to column "isbn_13", UNIQUE
// - add a no-arg constructor and a (title, isbn) constructor
//
// @Entity
// @Table(name = "books")
// public class Book { ... }🧩 MINI-CHALLENGE — Employee with a derived field
Map an Employee with custom column names and a @Transient computed salary.
import jakarta.persistence.*;
// MINI-CHALLENGE: map an Employee with a couple of subtleties.
// - table "employees"
// - id auto-generated
// - fullName -> column "full_name", NOT NULL, length 120
// - salaryCents (int) -> column "salary_cents", NOT NULL
// - a @Transient field "annualSalary" computed from salaryCents (NOT stored)
//
// Think: which annotation keeps annualSalary out of the table?
// which strategy lets the database own the id?Common Errors
- ❌ No no-arg constructor. The provider can't instantiate the entity. Fix: add a protected or public no-arg constructor.
- ❌ Forgetting @Id. Every entity needs a primary key. Fix: mark exactly one field (or composite) with @Id.
- ❌ Using a primitive for a generated id. It can't be null before insert. Fix: use Long, not long.
- ❌ Storing money as double. Rounding bugs creep in. Fix: store integer cents or use BigDecimal.
- ❌ Persisting computed fields. Derived values bloat the table. Fix: mark them @Transient.
- ❌ Mixing javax and jakarta imports. Modern Spring Boot uses jakarta.persistence. Fix: import from jakarta, not javax.
Pro Tips
- 💡 Use wrapper types for ids (Long) so a not-yet-saved entity reads as null.
- 💡 Add explicit @Column constraints — nullable, length, unique — to shape the generated schema.
- 💡 Keep a protected no-arg constructor for JPA and a real constructor for your own code.
- 💡 Prefer IDENTITY or SEQUENCE over assigning ids by hand in application code.
📋 Quick Reference
| Annotation | Purpose | Note |
|---|---|---|
| @Entity | class is persistent | Maps to a table |
| @Table(name) | custom table name | Defaults to class name |
| @Id | primary key | Exactly one |
| @GeneratedValue | auto-generate key | IDENTITY / SEQUENCE |
| @Column(name) | custom column name | Defaults to field |
| @Column(nullable) | NOT NULL constraint | false = required |
| @Column(length) | VARCHAR size | For String columns |
| @Column(unique) | UNIQUE constraint | No duplicates |
| @Transient | not persisted | In-memory only |
| no-arg ctor | required by JPA | protected is fine |
📚 Resources / Keep Learning
- 📘 Jakarta Persistence 3.1 specification (jakarta.ee)
- 📗 Spring Data JPA — entity persistence (docs.spring.io)
- 📗 Hibernate ORM documentation (hibernate.org)
🎉 Lesson Complete!
You can now map a class to a table with @Entity, define and auto-generate a primary key with @Id and @GeneratedValue, shape columns and tables with @Column and @Table, and exclude computed fields with @Transient.
Practice quiz
What does the @Entity annotation mark a class as?
- A REST controller
- A JPA-managed persistent entity mapped to a table
- A Spring bean only
- A configuration file
Answer: A JPA-managed persistent entity mapped to a table. @Entity tells JPA the class maps to a database table and its instances are persistent entities.
Which annotation marks the primary key field of an entity?
- @Key
- @Primary
- @Id
- @Column(primary=true)
Answer: @Id. @Id designates the field that maps to the table's primary key.
What does @GeneratedValue(strategy = GenerationType.IDENTITY) do?
- Generates a random UUID in Java
- Lets the database auto-increment the key column
- Disables key generation
- Uses a table to store keys only
Answer: Lets the database auto-increment the key column. IDENTITY delegates key generation to the database's own auto-increment column.
By default, what table name does JPA use for an @Entity class named Customer?
- customers
- tbl_Customer
- Customer
- entity_customer
Answer: Customer. Without @Table, JPA derives the table name from the entity name, here Customer.
Which annotation overrides the default table name for an entity?
- @Table(name = "...")
- @Entity(table = "...")
- @MapTo
- @Schema
Answer: @Table(name = "..."). @Table(name = "...") sets an explicit table name, useful when it differs from the class name.
What does @Column(nullable = false) enforce?
- The column is the primary key
- The column is unique
- The column is indexed
- The column may not store NULL
Answer: The column may not store NULL. nullable = false adds a NOT NULL constraint to the generated column.
A no-argument constructor is required on a JPA entity because...
- Java forbids other constructors
- The persistence provider instantiates entities reflectively
- It speeds up queries
- It is needed for equals()
Answer: The persistence provider instantiates entities reflectively. JPA providers create entity instances via a no-arg constructor before populating fields.
What is the effect of @Column(length = 100) on a String field?
- Limits the Java String length at runtime
- Truncates reads to 100 chars
- Sets the VARCHAR length in the generated DDL
- Indexes the first 100 characters
Answer: Sets the VARCHAR length in the generated DDL. length controls the column size (e.g. VARCHAR(100)) in the schema DDL.
If you omit @Column on a mapped field, JPA will...
- Skip the field entirely
- Throw an exception at startup
- Map it using the field name and default settings
- Treat it as transient
Answer: Map it using the field name and default settings. Persistent fields are mapped automatically; @Column is only needed to customize the mapping.
Which annotation tells JPA NOT to persist a particular field?
- @Transient
- @Ignore
- @Skip
- @NotPersisted
Answer: @Transient. @Transient marks a field as non-persistent so JPA leaves it out of the table mapping.
Continue this course
- Previous: Spring Data JPA Basics
- Next: Entity Relationships — @OneToMany, @ManyToOne, @ManyToMany, fetch types and cascades
- Quick reference: Java cheat sheet
Frequently asked questions
What does the @Entity annotation actually do?
@Entity marks a plain Java class as a JPA entity — an object whose state is mapped to a row in a relational table. The persistence provider (commonly Hibernate under Spring Data JPA) scans for @Entity classes, builds a mapping between fields and columns, and can then store, load, update, and delete instances as database rows. Without @Entity, the class is just a regular object the persistence layer ignores.
Why does a JPA entity need a no-argument constructor?
The persistence provider instantiates entities reflectively when it materializes rows from the database — it calls a no-arg constructor and then populates fields. JPA requires this constructor to be present and at least protected (or public). If you only define a constructor with arguments, the provider cannot create instances and startup or query execution fails. The constructor can be protected so you do not encourage misuse from your own code.
What is the difference between @Id and @GeneratedValue?
@Id marks which field is the entity's primary key — the unique identifier for each row. @GeneratedValue says the value of that key is produced automatically rather than set by your code. The strategy attribute chooses how: GenerationType.IDENTITY uses a database auto-increment column, SEQUENCE uses a database sequence, AUTO lets the provider pick, and TABLE uses a separate table to track values. You use @Id alone for assigned keys, and add @GeneratedValue when the database or provider should generate them.
When do I actually need @Column and @Table?
You only need them to override defaults. JPA derives the table name from the entity name and each column name from the field name, mapping every persistent field automatically. Add @Table(name = "...") when the table name differs from the class, and @Column when you want a different column name, a length, a NOT NULL constraint (nullable = false), uniqueness (unique = true), or other column-level settings. If the defaults are fine, you can leave both off.
How do I stop a field from being saved to the database?
Annotate it with @Transient. JPA persists every non-static, non-transient field by default, so a value you compute in memory — like a formatted display price derived from a stored integer — would otherwise become a column. @Transient tells the provider to skip the field entirely: it lives only on the in-memory object and is recomputed as needed, never written to or read from the table.