Phase 16
JPA / Hibernate ORM & Entity Relationships Architecture
📂 Module 6_ Database and ORM \ Demos_Introduction_to_ORM_JPA & Demos_Entity_Relationships
19.1 Object-Relational Impedance Mismatch, Entity Life Cycle & Relationships
📖 Cay S. Horstmann • Core Java Volume II — Chapter 5, Section 5.1 ("Object-Relational Persistence")
“Writing raw JDBC code requires tedious mapping between relational database rows and Java object graphs. Object-Relational Mapping (ORM) frameworks like Hibernate, adhering to the Java Persistence API (JPA) standard, bridge this impedance mismatch. An entity represents a persistent domain object managed by an EntityManager within a persistence context. Relationships between entities must be modeled with precision to prevent catastrophic performance degradation.”
- Entity Life Cycle Four States: New/Transient (created in memory, no DB id), Managed (tracked in persistence context, auto-dirtied), Detached (session closed), Removed (scheduled for SQL DELETE).
-
Lazy Loading Axiom
Always Default to LAZY: All collection relationships (
@OneToMany,@ManyToMany) MUST beFetchType.LAZYto prevent loading entire relational tables into RAM!
💡 Real-World Analogy: The Currency Exchange & The N+1 Restaurant Trap
• Impedance Mismatch: Tables have rows and foreign keys (2D grid). Java has objects, polymorphism, and collections (graph). JPA is the automatic currency exchange translating between Yen and Euros.
• The N+1 Query Trap: Imagine ordering food for 50 people. The waiter brings 1 bill for the group (Query 1), then walks back to the kitchen 50 separate times to fetch each person's fork one by one (N queries)! Solution: Use
• The N+1 Query Trap: Imagine ordering food for 50 people. The waiter brings 1 bill for the group (Query 1), then walks back to the kitchen 50 separate times to fetch each person's fork one by one (N queries)! Solution: Use
JOIN FETCH to bring all forks and plates in 1 trip.
⚡ Pro-Tip / Interview Memory Hook: The mappedBy Rule
Who owns the Foreign Key? In a bidirectional
@OneToMany / @ManyToOne relationship, the child entity with the @ManyToOne annotation OWNS the foreign key column. The parent entity's @OneToMany MUST have mappedBy = "fieldName", meaning: "I don't own the foreign key column; go look at the child's field!"
📂 Module 6_ Database and ORM/.../Department.java & Employee.java
package com.accenture.ltt.orm;
import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "departments")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// mappedBy points to the owning field in Employee
@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Employee> employees = new ArrayList<>();
public void addEmployee(Employee emp) {
employees.add(emp);
emp.setDepartment(this); // Maintain bidirectional sync!
}
}
@Entity
@Table(name = "employees")
class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String fullName;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "dept_id") // Owning side holding physical foreign key column
private Department department;
public void setDepartment(Department dept) { this.department = dept; }
}
👁 Execution & Memory Breakdown • Persistence Context
Step 1: When
department.addEmployee(emp) is called inside a managed transaction, Hibernate's dirty-checking engine automatically emits the SQL INSERT statements upon transaction commit.
Step 2:
FetchType.LAZY generates dynamic CGLIB proxies so employee records are only queried from the database when department.getEmployees() is explicitly invoked.