본 포스팅은 인프러의 JPA 기본편을 수강하고 정리하는 내용입니다.
Member를 조회할 때 Team도 함께 조회해야 할까?
단순히 member 정보만 사용하는 비즈니스 로직 println(member.getName())
메소드가 있다고 해보자.
지연 로딩 LAZY을 사용해서 프록시로 조회
1
2
3
4
5
6
7
8
9
10
11
12
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.LAZY) //**
@JoinColumn(name = "TEAM_ID")
private Team team;
..
}
- 지연로딩으로 세팅하면 연관된 것을 프록시로 가져온다. (Team 멤버 변수)
지연 로딩 (Lazy Loading)
1
2
Team team = member.getTeam();
team.getName(); // 실제 team을 사용하는 시점에 초기화(DB 조회)
반대로 Member와 Team을 자주 함께 사용한다면?
즉시 로딩 EAGER를 사용해서 함께 조회
1
2
3
4
5
6
7
8
9
10
11
12
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.EAGER) //**
@JoinColumn(name = "TEAM_ID")
private Team team;
..
}
- Member랑 Team을 한 방에 같이 조회한다.
- Team의 프록시 타입을 조회해보면 실제 Team 엔티티가 출력된다.
- JPA 구현체는 가능하면 조인을 사용해서 SQL 한번에 함께 조회
- 만약 애플리케이션 개발시 두 개가 무조건 전부 필요한 경우라면 Eager 로딩 방식을 적용하면 된다.
프록시와 즉시로딩 주의
- 가급적 지연 로딩만 사용(특히 실무에서)
- 실무에서 테이블이 복잡하게 얽혀있을 수 밖에 없기 때문에 가급적이 아니라 무조건!!!
- 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생
- 조인이 한 두개라면 문제가 되진 않지만 5개가 조인이 걸린다하면 쿼리가 엄청 예상치 못한 쿼리가 날라간다..
- 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
- 1)
- @ManyToOne, @OneToOne은 기본이 즉시 로딩
=> LAZY로 설정
- JPQL로 Member를 조회한다고 해보자. ~~~ Team team = new Team(); team.setName(“teamA”); em.persist(team);
Team teamB = new Team(); teamB.setName(“teamB”); em.persist(teamB);
Member member1 = new Member(); member1.setUsername(“memberS1”); member1.setTeam(team); em.persist(member1);
Member member2 = new Member(); member2.setUsername(“memberS1”); member2.setTeam(teamB); em.persist(member2);
em.flush(); em.clear();
List
members = em.createQuery("select m from Member m", Member.class) .getResultList(); tx.commit(); 1 2 3
- JPQL은 `em.createQuery("select m from Member m", Member.class)` 를 실행할 때 먼저 SQL로 번역한다. 그러면 당연히 Member 테이블만 select한다. - Member를 가져왔더니 Team이 즉시로딩되있네? 그러면 Member쿼리나가고 Member쿼리가 10개면 10개만큼 Team테이블을 조회하는 쿼리가 나간다.
select * from member select * from Team where TEAM_ID = xxx ~~~
- N(추가 쿼리: 실제 첫 쿼리에서 조회된 데이터의 갯수) + 1 (첫 쿼리)
Lazy
로 변경하면 위의 예제 코드에선 이와 같은 문제가 나오지 않는다. (Team은 프록시로만 다 박혀있게됨)- 하지만 여기서 Team을 루프로 돌리면 Team 테이블을 조회하는 쿼리가 계속 나갈 것이다.
- 일단은 모든 연관 관계를 지연 로딩으로 다 깔고
fetch join
,@entity graph
,batch size
와 같은 3가지 방법으로 해결할 수 있다. - 이에 관련한 내용은 여기를 참고하자.
- @OneToMany, @ManyToMany는 기본이 지연 로딩
- @ManyToOne, @OneToOne은 기본이 즉시 로딩이기에 다 Lazy로 설정을 해줘야한다.
Note: 실무에선 lazy로 다 바르고 딱 필요해서 동시에 한 방에 조회할 필요가 있을때 fetch join으로 가져오도록 하자.
지연 로딩 활용
이 부분은 굉장히 이론적인 내용이고 실무에선 다 지연로딩으로 설정해야 한다.
- Member와 Team은 자주 함께 사용 -> 즉시 로딩
- Member와 Order는 가끔 사용 -> 지연 로딩
- Order와 Product는 자주 함께 사용 -> 즉시 로딩
teamA는 실제 엔티티가 들어오고 orders는 프록시로 들어오게 된다.
지연 로딩 활용 - 실무
- 모든 연관관계에 지연 로딩을 사용해라!
- 실무에서 즉시 로딩을 사용하지 마라!
- JPQL fetch 조인이나, 엔티티 그래프 기능을 사용해라!(뒤에서 설명)
- 즉시 로딩은 상상하지 못한 쿼리가 나간다.