JPA를 사용하는 백엔드 개발자라면 누구나 한 번쯤은 예상치 못한 쿼리 폭발 또는 무한 참조를 경험했을 겁니다. 대부분의 원인은 연관관계 설정 시 Fetch 전략을 정확히 이해하지 못한 데서 비롯됩니다.
FetchType.LAZY
와 FetchType.EAGER
의 동작 방식, 각각의 장단점, 그리고 실무에서 언제 어떤 전략을 써야 하는지를 예제를 통해 알아보겠습니다.
1. Fetch 전략이란?
JPA에서 연관된 엔티티를 언제 로딩할지 결정하는 정책을 Fetch 전략이라 부릅니다. 크게 두 가지 방식이 있습니다:
- Lazy Loading (지연 로딩): 연관된 엔티티를 실제로 사용할 때 쿼리를 실행
- Eager Loading (즉시 로딩): 엔티티를 조회할 때 연관된 엔티티도 함께 조회
2. 기본 전략: 단방향 vs 양방향
@ManyToOne
,@OneToOne
은 기본적으로FetchType.EAGER
@OneToMany
,@ManyToMany
는 기본적으로FetchType.LAZY
즉, 연관관계 방향에 따라 기본 전략이 다르므로 명시적으로 fetch = FetchType.LAZY
를 설정하는 것이 안전합니다.
3. 예제로 보는 Lazy vs Eager
엔티티 설정
@Entity
public class Post {
@Id @GeneratedValue
private Long id;
private String title;
@OneToMany(mappedBy = "post", fetch = FetchType.LAZY)
private List<Comment> comments = new ArrayList<>();
}
@Entity
public class Comment {
@Id @GeneratedValue
private Long id;
private String content;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id")
private Post post;
}
Lazy Loading 결과
아래 코드처럼 Post만 조회할 경우, comments는 실제 사용될 때까지 쿼리가 실행되지 않습니다.
Post post = postRepository.findById(1L).get();
// 쿼리: select * from post where id = 1
int size = post.getComments().size();
// 이 시점에서 쿼리: select * from comment where post_id = 1
Eager Loading 결과
fetch = FetchType.EAGER
일 경우, Post 조회 시 댓글도 함께 불러옵니다.
select * from post left outer join comment on post.id = comment.post_id where post.id = 1
필요 없는 연관 데이터를 무조건 끌고 오기 때문에 성능 저하와 쿼리 복잡도 증가로 이어질 수 있습니다.
4. 실무 팁: Lazy가 기본, 필요한 곳만 Eager
- Lazy를 기본값으로 두고, 필요한 경우
@EntityGraph
,join fetch
등으로 컨트롤 - API 응답 객체에 직렬화 시 무한 순환 참조 문제 발생 → DTO로 분리하거나 Jackson 설정 필요
- 테스트나 디버깅 시
LazyInitializationException
발생 주의 (트랜잭션 밖에서 접근 시)
5. 성능 문제 예시: N+1 문제
Lazy 전략을 잘못 사용할 경우 다음과 같은 문제가 발생할 수 있습니다.
List<Post> posts = postRepository.findAll();
for (Post post : posts) {
System.out.println(post.getComments().size());
}
위 코드는 Post 수만큼 추가 쿼리가 실행되는 N+1 쿼리 문제를 발생시킵니다. 해결하려면 join fetch
또는 @EntityGraph
를 사용해야 합니다.
@Query("SELECT p FROM Post p JOIN FETCH p.comments")
List<Post> findAllWithComments();
결론
JPA를 사용할 때 Fetch 전략은 성능과 설계에 큰 영향을 미칩니다. 단순히 "편하니까 EAGER"로 가면, 장기적으로 시스템이 느려지고, 유지보수 난이도가 높아집니다. Lazy를 기본 전략으로 삼고, 필요한 곳만 명시적으로 조절하는 전략이 가장 안전합니다.
'개발 > JAVA' 카테고리의 다른 글
[JAVA] Spring에서 트랜잭션 관리하기 – @Transactional 완벽 이해 (0) | 2025.10.16 |
---|---|
[JAVA] N+1 문제와 해결 방법 – Fetch Join과 @EntityGraph로 해결하기 (0) | 2025.10.15 |
[JAVA] Entity 연관관계 완전 정복 – 1:N, N:M, Cascade까지 실무 중심 정리 (0) | 2025.10.13 |
[JAVA] JPQL과 @Query 사용법 – Spring Data JPA에서 복잡한 쿼리 다루기 (0) | 2025.10.12 |
[JAVA] JPA Repository 메서드 쿼리 작성법 – 실무 중심 가이드 (0) | 2025.10.11 |