1. Fetch Type
JPA에서 테이블 간 연관 관계는 객체의 참조를 통해 이루어 진다. 이때 서비스가 커질수록, 참조하는 객체가 많아지고, 객체가 가지는 데이터의 양이 많아지게 된다.
이렇게 객체가 커질수록 DB로 부터 참조하는 객체들의 데이터까지 한꺼번에 가져오는 행동은 부담이 커지게 되고, 이에 따라 JPA는 참조하는 객체들의 데이터를 가져오는 시점을 정할 수 있는데, 이를 Fetch Type 이라고 한다.
Fetch Type에는 두 가지 속성이 있다.
- Eager
- Lazy
2. Eager ( 즉시로딩 )
@xxToxx(fetch = FetchType.EAGER)
다음 예제는 Member 엔티티와 Team 엔티티가 N:1 관계를 가지고 있다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String username;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "team_id")
Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String teamname;
}
멤버 엔티티를 즉시 로딩으로 조회 해 보겠다.
Member findMember = em.createQuery("select m from Member m", Member.class).getSingleResult();
sql 쿼리
select
member0_.id as id1_0_,
member0_.team_id as team_id3_0_,
member0_.username as username2_0_
from
Member member0_
select
team0_.id as id1_3_0_,
team0_.name as name2_3_0_
from
Team team0_
where
team0_.id=?
위 쿼리를 보면 나는 분명 Member만 조회를 했을 뿐인데 Team을 조회하는 쿼리까지 날린 것을 확인 할 수 있다.
그럼 지연 로딩을 사용하면 어떨까?
3. Lazy (지연로딩)
@xxToxx(fetct = FetchType.Lazy)
즉시 로딩을 사용해 조회한 것과 똑같이 멤버 하나를 조회 해 보겠다.
Member findMember = em.createQuery("select m from Member m", Member.class).getSingleResult();
sql 쿼리
select
member0_.id as id1_0_,
member0_.team_id as team_id3_0_,
member0_.username as username2_0_
from
Member member0_
이번엔 Team 을 조회하는 쿼리가 발생하지 않은 것을 확인 할 수 있다!
지연로딩에서는 Member를 조회할때 Team을 조회하는 쿼리가 동시에 발생하지 않고,
Member를 조회할 경우엔 Member를 조회하는 쿼리만 발생하고, Team을 추가로 조회할 때 Team을 조회하는 쿼리가 발생하기 된다.
지연로딩을 사용해 Member를 조회하는 경우 Member 엔티티 아래에 있는 Team에는 프록시 라는 가짜 객체를 주입하여 실제 객체가 들어있는 것 처럼 동작 하게 된다.
프록시에 대한 자세한 설명은 추후 다시 다루겠다.
4. Eager vs Lazy
단순히 쿼리가 발생하는 횟수로만 살펴보면 위 예제에서 Eager는 1회, Lazy는 2회 발생하여 Lazy보다 Eager가 성능이 더 좋다고 볼 수 있다.
하지만 Member의 참조가 많아지면 어떻게 될까??
참조가 많아지면 많아질수록 Member를 조회할 때 알 수 없는 테이블들이 전부 join이 되어 등장하게 된다. 또 테이블 설계가 복잡해질수록 록, 하나의 엔티티가 참조하는 테이블들은 늘어나고, 그에 따른 쿼리문도 굉장히 길어지게 된다.
또 위의 Eager 예제에서는 Member와 연관된 Team이 단 1개 였기 때문에 Team을 조회하는 쿼리가 1개 나갔지만, 만약 Member와 연관된 Team이 100개 라면 Member를 조회하는 쿼리 단 하나로, Team을 조회하는 쿼리 100개가 추가로 발생하게 된다.
이를 N+1 Select Problem 이라고 한다. 하나의 Select 요청에 대한 Select문이 N개나 추가되는 현상이다.
Fetch Type이 Eager인 경우 Member 내의 Team 참조에 대한 데이터를 모두 가져와야 하기 때문에 단순한 조회쿼리 하나가 지나치게 많은 쿼리문을 수행하게 되어 성능 저하를 유발할수 있다.
따라서, 아무리 단순한 구조의 설계라도, 시간이 지나면 변화되고, 추가되기 마련이다. 개발 과정에서 임시로 사용하는 것이 아니라면, 항상 지연로딩을 사용함으로 성능 이슈가 발생할 가능성을 줄여주는 것이 좋다.