JPA Entity Mapping – Tables, Columns and Ids
🎧 Focus sounds
Soft beat sounds that many learners like while reading. Pick one, press Play, and keep studying.
Keep the volume low. These sounds are for comfort while studying, not a medical treatment. If you have epilepsy or are sensitive to pulsing sounds, use the background noise only.
In this lesson
By default JPA maps a class Course to a table Course and every field to a column of the same name. Real databases have naming conventions, constraints and special types. Annotations let you control all of it.
A fully mapped entity#
package com.vitalstack;
import jakarta.persistence.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Entity
@Table(name = "students",
uniqueConstraints = @UniqueConstraint(name = "uk_student_email", columnNames = "email"))
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "full_name", nullable = false, length = 100)
private String name;
@Column(nullable = false)
private String email;
@Enumerated(EnumType.STRING) // store "ACTIVE", not 0
@Column(length = 20)
private Status status = Status.ACTIVE;
private LocalDate birthDate; // DATE column
private LocalDateTime joinedAt; // TIMESTAMP column
@Lob // large text (CLOB)
private String bio;
@Embedded // columns of Address live in this table
private Address address;
@Transient // NOT stored in the database
private int unreadNotifications;
protected Student() { }
public Student(String name, String email, Address address) {
this.name = name;
this.email = email;
this.address = address;
this.joinedAt = LocalDateTime.now();
}
public Long getId() { return id; }
public String getName() { return name; }
public Status getStatus() { return status; }
public Address getAddress() { return address; }
}
package com.vitalstack;
public enum Status { ACTIVE, SUSPENDED, GRADUATED }
package com.vitalstack;
import jakarta.persistence.Embeddable;
@Embeddable // a value object: no table, no id of its own
public class Address {
private String city;
private String pin;
protected Address() { }
public Address(String city, String pin) { this.city = city; this.pin = pin; }
public String getCity() { return city; }
}
package com.vitalstack;
import jakarta.persistence.*;
public class Main {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("school");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(new Student("Asha Kumar", "asha@example.com", new Address("Pune", "411001")));
em.getTransaction().commit();
Student s = em.createQuery("select s from Student s", Student.class).getSingleResult();
System.out.println(s.getName() + " | " + s.getStatus() + " | " + s.getAddress().getCity());
em.close();
emf.close();
}
}
Generated table (H2, reformatted for reading):
create table students (
birthDate date,
id bigint generated by default as identity,
joinedAt timestamp(6),
full_name varchar(100) not null,
city varchar(255),
email varchar(255) not null,
pin varchar(255),
bio clob,
status enum ('ACTIVE','GRADUATED','SUSPENDED'),
primary key (id),
constraint uk_student_email unique (email))
The column type for status depends on the database: H2 and MySQL get an enum type, others a varchar.
Output: Asha Kumar | ACTIVE | Pune
@Table and @Column#
| Attribute | Example | Effect |
|---|---|---|
@Table(name) | "students" | table name |
@Column(name) | "full_name" | column name |
nullable | false | NOT NULL |
length | 100 | VARCHAR(100) |
unique | true | unique constraint |
precision, scale | 10, 2 | for BigDecimal money: DECIMAL(10,2) |
insertable, updatable | false | read-only columns |
These constraints matter only for schema generation; they don’t validate data in Java. Use Bean Validation (@NotBlank, @Email, @Size) for that. You’ll use it in the Spring Boot course.
Id generation strategies#
| Strategy | How it works | Notes |
|---|---|---|
IDENTITY | database auto-increment column | simple; MySQL’s natural choice; disables JDBC insert batching |
SEQUENCE | database sequence object | best for PostgreSQL/Oracle; supports batching |
UUID | @GeneratedValue(strategy = GenerationType.UUID) on a UUID id | globally unique; good for distributed systems and public ids |
AUTO (default) | provider chooses | Hibernate 6 picks SEQUENCE for most databases |
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "student_seq")
@SequenceGenerator(name = "student_seq", sequenceName = "student_seq", allocationSize = 50)
private Long id;
allocationSize = 50 lets Hibernate reserve 50 ids per database round trip: much faster inserts.
Enums: always STRING#
@Enumerated(EnumType.STRING)
private Status status;
The default ORDINAL stores 0, 1, 2. If someone later inserts a new constant in the middle of the enum, every existing row silently changes meaning. STRING stores readable names and is safe.
Dates and times#
Use java.time types: they map automatically.
| Java | SQL |
|---|---|
LocalDate | DATE |
LocalTime | TIME |
LocalDateTime | TIMESTAMP |
Instant, OffsetDateTime | TIMESTAMP WITH TIME ZONE (provider dependent) |
Avoid old java.util.Date and @Temporal in new code.
Embeddables: value objects#
Address has no identity of its own: it belongs to a student. @Embeddable + @Embedded store its fields as columns of the owner’s table. Reuse it in Teacher and Company too. Rename columns per use with @AttributeOverride.
@Transient#
Fields marked @Transient (or Java transient) aren’t persisted: perfect for computed or temporary values.
Field vs property access#
Where you put @Id decides the access type: on a field → JPA reads/writes fields directly (recommended); on a getter → JPA uses getters/setters. Don’t mix them in one entity.
Keep entities focused on data and simple rules. Don’t put HTTP or UI logic in them, and don’t return entities directly from public APIs; map them to DTOs/records (Spring Boot course).
Common mistakes#
- Forgetting
@Enumerated(EnumType.STRING). - Using
doublefor money: useBigDecimalwithprecision/scale, or store paise in along. - Column names that are SQL reserved words (
order,user,group) → rename with@Table/@Column. - Expecting
nullable = falseto validate before saving (it only affects DDL; the database throws an exception).
Exercise#
- Map a
Productentity:sku(unique, 20 chars),name(not null),priceasBigDecimal(10,2),categoryenum,createdAt. - Create an
@Embeddable Money(amount, currency)and use it inOrdertwice (subtotal,tax) with@AttributeOverrides. - Switch
Studentto a UUID id and check the generated DDL.
Hint for 2
@AttributeOverrides({ @AttributeOverride(name = "amount", column = @Column(name = "tax_amount")), @AttributeOverride(name = "currency", column = @Column(name = "tax_currency")) }) on the second field.
Interview corner#
- @Column(nullable=false) vs @NotNull? The first only shapes the DDL; the second is Bean Validation that checks before persisting.
- IDENTITY vs SEQUENCE? IDENTITY uses auto-increment (no batching); SEQUENCE fetches ids from a sequence (batch-friendly with allocationSize).
- Why EnumType.STRING? ORDINAL breaks when enum constants are reordered.
- What is @Embeddable? A value type whose fields are stored in the owning entity’s table.
Summary#
@Table,@Columncontrol names and constraints;@Id+@GeneratedValuechoose ids.- Map enums as STRING, dates with
java.time, big text with@Lob. @Embeddablevalue objects group columns;@Transientskips fields.
Next: the EntityManager and the four states of an entity, animated.
Quick quiz
3 questions. Check what stuck and earn 2 XP for each right answer.
Progress is saved in this browser. Create a free account to keep it on every device.
Get new lessons in your inbox
One email when we publish a new tutorial or guide. No spam, unsubscribe in one click.