Working with JPA Entity Relationships
1. Implementing One-to-One Relationships
Example: Bidirectional one-to-one mapping
@Entity class User {
@Id @GeneratedValue Long id;
@OneToOne(mappedBy="user", cascade=CascadeType.ALL, orphanRemoval=true)
Profile profile;
}
@Entity class Profile {
@Id @GeneratedValue Long id;
@OneToOne(fetch=FetchType.LAZY)
@JoinColumn(name="user_id", unique=true)
User user;
}
2. Implementing One-to-Many Relationships
| Side | Annotation |
|---|---|
| Owning (FK) | @ManyToOne @JoinColumn(name="parent_id") |
| Inverse | @OneToMany(mappedBy="parent") |
| Helper | Add addChild()/removeChild() methods to maintain both sides |
3. Implementing Many-to-One Relationships
Example: Many-to-one with lazy loading
@Entity class LineItem {
@Id @GeneratedValue Long id;
@ManyToOne(fetch=FetchType.LAZY, optional=false)
@JoinColumn(name="order_id", nullable=false)
Order order;
}
Warning: Always set
fetch=LAZY on @ManyToOne to avoid hidden
N+1 fetches.4. Implementing Many-to-Many Relationships
Example: Many-to-many with join table
@Entity class Course {
@ManyToMany
@JoinTable(name="course_student",
joinColumns=@JoinColumn(name="course_id"),
inverseJoinColumns=@JoinColumn(name="student_id"))
Set<Student> students = new HashSet<>();
}
| Tip | Reason |
|---|---|
Use Set not List |
Avoid MultipleBagFetchException |
| Promote to entity if join carries data | e.g. enrollment date |
5. Using Bidirectional Relationships
| Concept | Detail |
|---|---|
mappedBy |
On inverse side; references owning field |
| Owning side | Holds FK; updates persist |
| Sync helper | Manually keep both sides in sync |
toString() |
Avoid including bidirectional collections (StackOverflow) |
6. Using Cascade Operations
| CascadeType | Effect |
|---|---|
PERSIST |
Save children when parent saved |
MERGE |
Merge propagates |
REMOVE |
Delete children with parent |
REFRESH |
Reload children |
DETACH |
Detach children |
ALL |
All of the above |
7. Configuring Fetch Types (LAZY vs EAGER)
| FetchType | When to Use |
|---|---|
| LAZY | Default for collections; safe choice |
| EAGER | Almost never — leads to N+1 + over-fetch |
| JOIN FETCH | Per-query eager loading |
| EntityGraph | Declarative fetch plan per call |
8. Using @JoinColumn for Foreign Keys
Example: JoinColumn with named FK constraint
@ManyToOne
@JoinColumn(name="customer_id",
referencedColumnName="id",
nullable=false,
foreignKey=@ForeignKey(name="fk_order_customer"))
Customer customer;
9. Using @JoinTable for Join Tables
| Attribute | Purpose |
|---|---|
name |
Join table name |
joinColumns |
FK to owning side |
inverseJoinColumns |
FK to other side |
uniqueConstraints |
Composite unique |
10. Handling Orphan Removal
Example: Cascade delete with orphan removal
@OneToMany(mappedBy="order", cascade=CascadeType.ALL, orphanRemoval=true)
List<LineItem> items;
// order.getItems().remove(li); // triggers DELETE
Note: Different from
CascadeType.REMOVE — orphanRemoval triggers
when reference is removed from collection.11. Using @EntityGraph for Fetch Optimization
Example: Entity graph to avoid N+1
@EntityGraph(attributePaths = {"customer", "items.product"})
@Query("select o from Order o where o.id = :id")
Optional<Order> findByIdWithDetails(@Param("id") Long id);
// Or named graph
@NamedEntityGraph(name="Order.detail",
attributeNodes = {@NamedAttributeNode("items"), @NamedAttributeNode("customer")})