처음 웹 공부를 했을 때부터 JPA 를 썼기 때문에 그 쿼리를 주의깊게 보지 않았던 것 같다. 연관관계 매핑 시 fetchType 을 LAZY 로 하는 경우를 많이 봤을 것이다. 그 이유에 대해서 책을 통해 정리해보려 한다.
JPA 의 FetchType ( LAZY 와 EAGER )
연관관계 매핑 설정 시 FetchType 은 데이터베이스에서 데이터를 가져오는 전략을 지정하는 데 사용된다.
종류로는 LAZY 와 EAGER 두 가지가 있다. LAZY 는 지연로딩으로 해당 데이터를 필요할 때 가져오고, EAGER 는 즉시로딩으로 데이터를 즉시 가져온다.
JPA 에서 @OneToMany 와 @ManyToMany 의 기본 FetchType 값은 LAZY 이고, @ManyToOne과 @OneToOne 은 EAGER 이다. 그렇다면 필요한 것만 바꾸면 될까 ? 개인적으로는, 다른 이슈가 없더라도 사람은 실수를 할 수 있기 때문에 양 쪽 다 똑같이 설정하는 것이 좋을 것 같다.
# JPA 기본 FetchType 설정 값 정리
- @OneToMany 와 @ManyToMany : LAZY ( 지연로딩 )
- @ManyToOne과 @OneToOne : EAGER ( 즉시로딩 )
말료 표현하는 것은 와닿지 않기 때문에 예시로 확인해보자.
Member Entity 와 있고 Team Entity 가 있는 상황에서 다대일 매핑을 한 상태로, 아래 글에서 사용했던 케이스와 동일하다.
2023.10.01 - [Spring] - [JPA] 1:N 단방향 매핑과 N:1 양방향 매핑
초기 설정은 아래와 같다.
- Member Entity
public class Member {
..
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
- Team Entity
public class Team {
..
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
1. 즉시로딩 ( EAGER )
즉시로딩을 사용하기위해 FetchType 을 EAGER 로 설정한다.
public class Member {
..
@ManyToOne(fetch = FetchType.EAGER) // ★
@JoinColumn(name = "TEAM_ID")
private Team team;
}
public class Team {
..
@OneToMany(mappedBy = "team", fetch = FetchType.EAGER) // ★
private List<Member> members = new ArrayList<>();
}
즉시로딩으로 설정했기 때문에 Member Entity 를 조회하면 Team 이 즉시 조회된다. 쿼리를 통해 확인해보자.
- 테스트 ( 아래의 지연 로딩 테스트와 동일 )
@Test
void jpaFetchTypeTest(){
Team team = new Team("IT");
teamRepository.save(team);
Member member = new Member(1L,"limnj@test.com", "1234", "limnj");
member.setTeam(team);
team.addMember(member);
memberRepository.save(member);
// FetchType 에 따른 쿼리 확인
Member findMember = memberRepository.findById(1L).get();
findMember.getTeam();
}
- 실행 결과

실행 결과를 보면 OUTER JOIN 으로 데이터를 한 번에 조회함을 확인할 수 있다.
즉, 연관된 엔티티를 즉시 조회하며 하이버네이트는 가능하면 SQL 조인을 사용해서 한 번에 조회한다.
2. 지연로딩 ( LAZY )
지연로딩을 사용하기 위해 FetchType을 LAZY 로 설정한다.
public class Member {
..
@ManyToOne(fetch = FetchType.LAZY) // ★
@JoinColumn(name = "TEAM_ID")
private Team team;
}
public class Team {
..
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY) // ★
private List<Member> members = new ArrayList<>();
}
지연로딩으로 설정했기 때문에 Member 만 조회하고 Team 은 조회하지 않는다. 쿼리를 통해 확인해보자.
- 테스트 ( 위의 즉시 로딩 테스트와 동일 )
@Test
void jpaFetchTypeTest(){
Team team = new Team("IT");
teamRepository.save(team);
Member member = new Member(1L,"limnj@test.com", "1234", "limnj");
member.setTeam(team);
team.addMember(member);
memberRepository.save(member);
// FetchType 에 따른 쿼리 확인
Member findMember = memberRepository.findById(1L).get();
findMember.getTeam(); // 프록시객체
}
- 실행 결과

여기서 의문이 생긴다. Team Entity 를 조회하지않았는데 getTeam() 은 실제 값이 조회되는 것일까 ?
그렇지않다. team 멤버변수에는 프록시 객체가 들어간다. 이 프록시 객체는 실제 사용될 때가지 데이터 로딩을 미루게 된다. 즉, 연관된 엔티티를 프록시로 조회하고 프록시를 실제 사용할 때 초기화하면서 데이터베이스를 조회한다.
즉시로딩 VS 지연로딩
연관된 엔티티를 한 번에 조회하는 것이 좋은지 아니면 실제 사용할 때까지 기다렸다가 조회하는 것이 좋은지는 상황에 따라 다르겠지만, 예를 들어 특정 회원이 연관된 컬렉션에 데이터를 수만 건 등록했는데 즉시 로딩이면 해당 회원을 로딩할 때 수만 건의 데이터가 함께 로딩되는 되는 문제가 발생할 수 있다.
그래서 책에서 추천하는 방법은 가급적이면 모든 연관관계에 지연 로딩을 사용하는 것이다.
그리고 애플리케이션 개발이 어느 정도 완료단계에 왔을 때 상황을 보고 필요한 경우에만 즉시 로딩을 사용하도록 한다.
나는 지연 로딩을 지향하되 즉시 로딩을 사용해야 하는 상황에 대해 고민을 좀 해봐야겠다.
/* to do list
- 즉시 로딩 시나리오 생각해보기
*/
참고
김영한. 자바 ORM 표준 프로그래밍, 에이콘출판, 2015
'Web' 카테고리의 다른 글
| [System Design] URL 단축기 설계 -1 (0) | 2023.11.05 |
|---|---|
| [Spring] Transaction 에 대해서 (3) | 2023.10.22 |
| [JPA] 1:N 단방향 매핑과 N:1 양방향 매핑 (0) | 2023.10.01 |
| [Spring] CORS Configuration with Spring security (0) | 2023.09.17 |
| [Spring] 동시성 이슈와 synchronized 에 관하여 (0) | 2023.09.10 |