Entity Relationships
Real data is connected. JPA maps those connections with @OneToMany , @ManyToOne , and @ManyToMany — plus @JoinColumn , fetch types, and cascading to control how the links behave.
Learn Entity Relationships in our free Java course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Java course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Before You Start
Make sure you are comfortable mapping a single JPA entity with @Entity , @Id , and @Column before connecting entities together.
What You'll Learn
The Big Idea — Lines Drawn Between Tables
💡 Analogy: Picture each table as a box and a relationship as a line drawn between boxes. A many-to-one line means many child rows each point at one parent — like many order lines pointing at one order; the child carries the @JoinColumn "pointer." The one-to-many view is just looking down the same line from the parent. A many-to-many needs a little switchboard table in the middle because neither side can hold a single pointer.
Only one end of each line is "in charge" of writing it — that is the owning side.
1️⃣ One-to-Many / Many-to-One
The @ManyToOne child owns the foreign key; the @OneToMany parent uses mappedBy to point back. Keep both sides in sync in a helper method so the in-memory graph matches the database.
2️⃣ Cascading & Orphan Removal — the idea
cascade propagates operations from parent to child; orphanRemoval deletes a child removed from the collection. Here is a plain-Java sketch of that ownership you can run.
3️⃣ Many-to-Many with a Join Table
When both sides have "many," neither row can hold a single pointer, so JPA uses a join table . The owning side declares it with @JoinTable ; the inverse side just uses mappedBy .
🧠 Quick Recall
Answer: the @ManyToOne (child) side — it holds the @JoinColumn . The @OneToMany side is the inverse via mappedBy .
Answer: LAZY . To-many collections load only when accessed; to-one defaults to EAGER .
Answer: neither side can store a single foreign key for "many," so a separate link table holds pairs of keys.
🎯 YOUR TURN — Posts and Comments
Model a bidirectional one-to-many: one Post has many Comment s, with the comment owning the foreign key.
🧩 MINI-CHALLENGE — Authors and Books
Model a many-to-many between Author and Book with a book_author join table.
Common Errors
- ❌ Updating only the inverse side. The foreign key won't change. Fix: set the owning (@ManyToOne) side too, ideally via a sync helper.
- ❌ Leaving @ManyToOne EAGER everywhere. It pulls huge graphs. Fix: use fetch = FetchType.LAZY and fetch joins when needed.
- ❌ Cascading REMOVE across many-to-many. It deletes shared rows. Fix: cascade nothing (or only persist/merge) there.
- ❌ Forgetting mappedBy. JPA creates two separate mappings (and a stray join table). Fix: mark the inverse side with mappedBy .
- ❌ Using List for @ManyToMany. Duplicates and ordering surprises. Fix: prefer a Set .
- ❌ N+1 queries. Lazy collections fetched in a loop. Fix: use a JPQL fetch join or an entity graph.
Pro Tips
- 💡 Add helper methods ( addItem ) that set both sides so your object graph stays consistent.
- 💡 Default to LAZY and explicitly fetch what a use-case needs.
- 💡 Use cascade + orphanRemoval only for true ownership like order to line items.
- 💡 Pick a single owning side per relationship to avoid duplicate writes.
📋 Quick Reference
Concept
Annotation / idea
Note
many-to-one
@ManyToOne
Owns the FK; EAGER default
one-to-many
@OneToMany(mappedBy)
Inverse; LAZY default
many-to-many
@ManyToMany + @JoinTable
Needs link table
foreign key
@JoinColumn(name)
On the owning side
inverse marker
mappedBy = "field"
Points to owner
eager
FetchType.EAGER
Default to-one
lazy
FetchType.LAZY
Default to-many
cascade
cascade = CascadeType.ALL
Propagate ops
orphan removal
orphanRemoval = true
Delete detached child
join table
@JoinTable(joinColumns,...)
For many-to-many
📚 Resources / Keep Learning
- 📘 Jakarta Persistence 3.1 specification (jakarta.ee)
- 📗 Hibernate User Guide — associations (docs.jboss.org)
- 📗 Spring Data JPA reference (docs.spring.io)
❓ Frequently Asked Questions
🎉 Lesson Complete!
You can now connect entities with @OneToMany , @ManyToOne , and @ManyToMany , name foreign keys with @JoinColumn , choose the owning side with mappedBy , tune EAGER vs LAZY fetching, and manage child lifecycles with cascade and orphanRemoval.
Next up: Derived Queries & @Query — letting Spring Data turn method names and JPQL into SQL.
Practice quiz
Which annotation models the 'many' side that owns a foreign key column?
- @ManyToOne
- @OneToMany
- @OneToOne
- @ManyToMany
Answer: @ManyToOne. @ManyToOne sits on the child entity and, by default, owns the foreign key column referencing the parent.
In a bidirectional one-to-many, which side is typically the inverse (non-owning) side?
- The @ManyToOne side
- The @OneToMany side
- Both sides equally
- Neither side
Answer: The @OneToMany side. The @OneToMany collection side uses mappedBy and is the inverse side; the @ManyToOne side owns the foreign key.
What does @JoinColumn specify?
- The primary key column
- A unique index
- The fetch strategy
- The foreign key column name used for the association
Answer: The foreign key column name used for the association. @JoinColumn names the foreign key column that links the two tables.
What is the DEFAULT fetch type for @ManyToOne?
- LAZY
- EAGER
- ASYNC
- There is no default
Answer: EAGER. To-one associations (@ManyToOne, @OneToOne) default to EAGER fetching.
What is the DEFAULT fetch type for @OneToMany?
- EAGER
- BATCH
- LAZY
- JOIN
Answer: LAZY. To-many associations (@OneToMany, @ManyToMany) default to LAZY fetching.
What does the mappedBy attribute indicate?
- The owning field on the other entity
- The join table name
- The cascade type
- The fetch mode
Answer: The owning field on the other entity. mappedBy points to the field on the owning side, marking this side as the inverse (non-owning) side.
Which relationship requires a join table by default?
- @ManyToOne
- @OneToOne
- @ManyToMany
- @OneToMany unidirectional with @JoinColumn
Answer: @ManyToMany. @ManyToMany cannot be expressed with a single foreign key, so it uses a separate join table.
What does cascade = CascadeType.ALL do on a parent's collection?
- Deletes the database
- Propagates persist, merge, remove, etc. to the children
- Disables lazy loading
- Makes the relationship read-only
Answer: Propagates persist, merge, remove, etc. to the children. CascadeType.ALL propagates all entity operations (persist, merge, remove, refresh, detach) from parent to children.
What problem does orphanRemoval = true solve?
- N+1 queries
- Eager fetching
- Unique constraints
- Deleting a child removed from the parent's collection
Answer: Deleting a child removed from the parent's collection. orphanRemoval deletes a child row once it is removed from the owning collection and no longer referenced.
Why is LAZY often preferred for associations?
- It always loads everything immediately
- It avoids loading related data until it is actually used
- It removes foreign keys
- It is required by JPA
Answer: It avoids loading related data until it is actually used. LAZY fetching defers loading related entities until accessed, avoiding unnecessary queries and large result sets.
Continue this course
- Previous: JPA Entities & Mapping
- Next: Derived Queries & @Query