❌ Problem:
- Fetching unnecessary data from the database
- Increasing response time due to large joins
- N+1 query problem (multiple queries slowing down performance)
💡 Solution: Use FetchType.LAZY
to load only the necessary data when it's actually needed.
In this article, we’ll explore:
✅ What is FetchType.LAZY
?
✅ Why EAGER fetching is bad for performance
✅ A complete example with one-to-many relationship
✅ How FetchType.LAZY
reduces database queries
⚡ What is FetchType.LAZY
in Hibernate?
In Hibernate, lazy loading (FetchType.LAZY
) means that related entities are not fetched from the database until you explicitly access them.
📌 Example: Using FetchType.LAZY
in One-to-Many Relationship
Let’s assume we have a Department
and Employee
relationship where a department has multiple employees.
1️⃣ Define Department
Entity (One-to-Many Relationship)
@Entity
@Table(name = "departments")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "department", fetch = FetchType.LAZY) // Lazy Loading
private List<Employee> employees;
// Constructors, Getters, and Setters
}
✅ FetchType.LAZY
ensures that employees are not loaded until explicitly needed.
2️⃣ Define Employee
Entity (Many-to-One Relationship)
@Entity
@Table(name = "employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String role;
@ManyToOne
@JoinColumn(name = "department_id")
private Department department;
// Constructors, Getters, and Setters
}
❌ What Happens with FetchType.EAGER
? (Bad Practice)
If we set fetch = FetchType.EAGER
, Hibernate will always fetch all employees when retrieving a department.
@OneToMany(mappedBy = "department", fetch = FetchType.EAGER) // BAD PRACTICE
private List<Employee> employees;
🚨 Problem: Unnecessary Data Fetching
📌 Query Generated (EAGER Fetching)
SELECT * FROM departments d
LEFT JOIN employees e ON d.id = e.department_id
✅ Fetching ALL employees even if we don’t need them 😨
✅ Slows down performance when departments have thousands of employees
✅ Optimized API with FetchType.LAZY
(Best Practice)
Now, let’s create a Spring Boot API that loads departments without employees (Lazy Loading).
3️⃣ Create a Repository Layer
public interface DepartmentRepository extends JpaRepository<Department, Long> {
}
4️⃣ Create a Service Layer (Lazy Loading in Action)
@Service
public class DepartmentService {
private final DepartmentRepository departmentRepository;
public DepartmentService(DepartmentRepository departmentRepository) {
this.departmentRepository = departmentRepository;
}
public List<Department> getAllDepartments() {
return departmentRepository.findAll(); // Employees are NOT fetched here
}
public Department getDepartmentById(Long id) {
return departmentRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Department not found"));
}
public List<Employee> getEmployeesByDepartment(Long departmentId) {
Department department = getDepartmentById(departmentId);
return department.getEmployees(); // Employees are only fetched here!
}
}
How Lazy Loading Works Here
1️⃣ getAllDepartments()
- Fetching Departments Without Employees
public List<Department> getAllDepartments() {
return departmentRepository.findAll(); // Employees are NOT fetched here
}
📌 What happens here?
- The method fetches only department details.
- Employees are NOT loaded because
FetchType.LAZY
is set in the@OneToMany
relationship. - Database Query (Generated by Hibernate):
SELECT * FROM departments;
- Result: API returns only department details, keeping the response lightweight.
2️⃣ getDepartmentById()
- Fetching a Single Department Without Employees
public Department getDepartmentById(Long id) {
return departmentRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Department not found"));
}
📌 What happens here?
- The method retrieves only the department from the database.
- Employees are NOT fetched at this stage.
- Database Query (Generated by Hibernate):
SELECT * FROM departments WHERE id = ?;
- Result: Only department data is returned, avoiding unnecessary database calls.
3️⃣ getEmployeesByDepartment()
- Fetching Employees When Needed
public List<Employee> getEmployeesByDepartment(Long departmentId) {
Department department = getDepartmentById(departmentId);
return department.getEmployees(); // Employees are only fetched here!
}
📌 What happens here?
- We first fetch the department details using
getDepartmentById()
. - When we call
department.getEmployees()
, Hibernate triggers a separate SQL query to load the employees. - Database Queries (Generated by Hibernate):
SELECT * FROM departments WHERE id = ?; -- Fetch department SELECT * FROM employees WHERE department_id = ?; -- Fetch employees only when needed
- Result: Employees are loaded only when required, preventing unnecessary data fetching.
5️⃣ Create REST Controller for API Calls
@RestController
@RequestMapping("/api/departments")
public class DepartmentController {
private final DepartmentService departmentService;
public DepartmentController(DepartmentService departmentService) {
this.departmentService = departmentService;
}
// Fetch departments WITHOUT employees (Lazy Loading)
@GetMapping
public ResponseEntity<List<Department>> getAllDepartments() {
return ResponseEntity.ok(departmentService.getAllDepartments());
}
// Fetch employees of a department (Triggers Lazy Loading)
@GetMapping("/{id}/employees")
public ResponseEntity<List<Employee>> getEmployeesByDepartment(@PathVariable Long id) {
return ResponseEntity.ok(departmentService.getEmployeesByDepartment(id));
}
}
🔑 Key Takeaways
✅ Use FetchType.LAZY
for one-to-many and many-to-many relationships
✅ Avoid EAGER fetching unless absolutely necessary
✅ Lazy Loading reduces database load and improves API performance
✅ Load related entities only when needed (e.g., separate API call for employees)
By following these best practices, your Spring Boot applications will run faster and handle large datasets efficiently. 🚀
Comments
Post a Comment
Leave Comment