JPA
Hibernate
Spring Controller
FetchType.LAZY
Java Programming

How to fetch FetchType.LAZY associations with JPA and Hibernate in a Spring Controller

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Java Persistence API (JPA) and Hibernate are powerful tools for database operations within Java applications. Fetching associations efficiently is crucial for the performance of your application, and Hibernate provides two fetch types to choose from: EAGER and LAZY. While EAGER loading is straightforward, FetchType.LAZY allows for deferred loading of associated entities, which can lead to performance improvements by avoiding unnecessary data retrieval. This article explains how to properly fetch FetchType.LAZY associations within a Spring Controller by leveraging JPA and Hibernate features.

Understanding FetchType.LAZY

In JPA, FetchType.LAZY indicates that the associated entities or collections should be loaded lazily, i.e., not fetched from the database until they are explicitly accessed. This helps optimize performance by reducing the number of queries and the amount of data transferred.

Example Entity Setup

Consider a simple example with two entities: Author and Book. An Author can have many Books.

java
1@Entity
2public class Author {
3    @Id
4    @GeneratedValue(strategy = GenerationType.IDENTITY)
5    private Long id;
6
7    private String name;
8
9    @OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
10    private List<Book> books;
11    
12    // Getters and setters
13}
14
15@Entity
16public class Book {
17    @Id
18    @GeneratedValue(strategy = GenerationType.IDENTITY)
19    private Long id;
20
21    private String title;
22
23    @ManyToOne
24    @JoinColumn(name = "author_id")
25    private Author author;
26
27    // Getters and setters
28}

In the Author entity, the books collection is marked with FetchType.LAZY. By default, many-to-one associations (Book.author) are eagerly loaded in Hibernate unless specified otherwise.

Fetching Lazy Associations in a Spring Controller

Fetching lazy associations (like books in the previous example) can lead to LazyInitializationException if accessed outside of a transaction context. To handle this properly in a Spring application, you can use several approaches.

1. Use Open Session in View Pattern

Spring's Open Session in View (OSIV) pattern keeps the Hibernate session open during the duration of a web request, allowing lazy loading to occur within a single transaction. To enable this, Spring Boot includes an OpenSessionInViewFilter by default.

properties
# application.properties
spring.jpa.open-in-view=true

2. Using @Transactional Annotation

Another approach is to use the @Transactional annotation on service layer methods to ensure that lazy loading occurs within a transaction boundary.

java
1@Service
2public class AuthorService {
3    @Autowired
4    private AuthorRepository authorRepository;
5
6    @Transactional
7    public Author getAuthorWithBooks(Long id) {
8        // Fetches an author and initializes its books collection
9        Author author = authorRepository.findById(id).orElseThrow();
10        author.getBooks().size(); // Fetches the lazy collection
11        return author;
12    }
13}
java
1@RestController
2@RequestMapping("/authors")
3public class AuthorController {
4
5    @Autowired
6    private AuthorService authorService;
7
8    @GetMapping("/{id}/books")
9    public ResponseEntity<Author> getAuthorWithBooks(@PathVariable Long id) {
10        Author author = authorService.getAuthorWithBooks(id);
11        return ResponseEntity.ok(author);
12    }
13}

3. Explicitly Fetch with JPQL or Criteria API

When you want more control over data retrieval, you can explicitly fetch associations using JPQL or Hibernate Criteria API.

java
1public interface AuthorRepository extends JpaRepository<Author, Long> {
2
3    @Query("SELECT a FROM Author a LEFT JOIN FETCH a.books WHERE a.id = :id")
4    Author findAuthorWithBooks(@Param("id") Long id);
5}

4. Use DTO Projections

Instead of loading the full entity graph with potentially unnecessary information, use Data Transfer Objects (DTOs) to project only necessary data.

java
1public class AuthorDTO {
2    private Long id;
3    private String name;
4    private List<String> bookTitles;
5
6    // Constructors, getters, and setters
7}
8
9@RestController
10@RequestMapping("/authors")
11public class AuthorController {
12
13    @Autowired
14    private AuthorRepository authorRepository;
15
16    @GetMapping("/{id}/book-titles")
17    public ResponseEntity<AuthorDTO> getAuthorBookTitles(@PathVariable Long id) {
18        Author author = authorRepository.findById(id).orElseThrow();
19        List<String> bookTitles = author.getBooks().stream()
20                                        .map(Book::getTitle)
21                                        .collect(Collectors.toList());
22        AuthorDTO authorDTO = new AuthorDTO(author.getId(), author.getName(), bookTitles);
23        return ResponseEntity.ok(authorDTO);
24    }
25}

Summary Table

ApproachDescriptionProsCons
Open Session in ViewKeeps session open for the entire web requestSimple to implementMay lead to lazy loading in views
@TransactionalEnsures lazy loading within a transaction in the service layerClear transaction boundariesOnly solves part of the problem if not careful
JPQL/Criteria APIUses custom queries to fetch all required data eagerlyFull control over queriesMore complex queries
DTO ProjectionsLoads only necessary data into custom objectsEfficient data retrievalRequires additional DTO classes

Conclusion

Fetching lazy associations effectively in JPA and Hibernate requires a deliberate choice of strategy based on your application requirements. While leveraging Spring's OSIV is easy, fine-grained control through transactions, queries, or DTOs can result in more efficient database operations. Understanding the implications of each approach can help in optimizing your application’s performance.


Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design