JAVA
[JPA] Fetch와 Fetch Join
바디스
2023. 9. 21. 20:13
Fetch전략
fetch전략은 특정 엔티티를 조회할 때, 연관관계에 있는 다른 엔티티를 «언제» 불러올까에 대한 옵션입니다.
User와 Team가 양방향 관계를 갖고 있는 경우
FetchType이 EAGER일 경우, Team를 조회한 «직후» User까지 조회하게 됩니다.
FetchType이 LAZY일 경우, Team를 조회하고 «추후» Team를 통해 User를 사용하려하면 그제서야 User를 조회하는 쿼리를 통해 조회합니다.
하지만 FetchType이 EAGER일 경우, N+1 문제가 발생하게 되고 이 N+1의 문제를 해결하기 위해서 Fetch Join을 사용하게 됩니다.
N+1 문제란?
연관 관계에서 발생하는 이슈로 연관 관계가 설정된 엔티티를 조회할 경우에 조회된 데이터 갯수(n) 만큼 연관관계의 조회 쿼리가 추가로 발생하여 데이터를 읽어오는 문제
DB 구조는 유저(USER)는 한개의 팀(TEAM)에만 속할 수 있고 팀(TEAM) 하나는 여러 명의 유저(USER)가 가입 할 수 있습니다.
@Entity
public class User {
@Id
@GeneratedValue
private long id;
private String firstName;
private String lastName;
@ManyToOne(fetch = FetchType.EAGER) // 즉시 로딩
@JoinColumn(name = "team_id", nullable = false)
private Team team;
}
@Entity
public class Team {
@Id
@GeneratedValue
private long id;
private String name;
@OneToMany(fetch = FetchType.EAGER)
private List<User> users = new ArrayList<>();
}
여기서 팀을 조회 합니다.(유저 4명이 속한 팀)(@OneToMany 로 연결되어있음)
Hibernate: select team0_.id as id1_0_, team0_.name as name2_0_ from team team0_
Hibernate: select users0_.team_id as team_id1_1_0_, users0_.users_id as users_id2_1_0_, user1_.id as id1_2_1_, user1_.first_name as first_na2_2_1_, user1_.last_name as last_nam3_2_1_, user1_.team_id as team_id4_2_1_ from team_users users0_ inner join user user1_ on users0_.users_id=user1_.id where users0_.team_id=?
Hibernate: select users0_.team_id as team_id1_1_0_, users0_.users_id as users_id2_1_0_, user1_.id as id1_2_1_, user1_.first_name as first_na2_2_1_, user1_.last_name as last_nam3_2_1_, user1_.team_id as team_id4_2_1_ from team_users users0_ inner join user user1_ on users0_.users_id=user1_.id where users0_.team_id=?
Hibernate: select users0_.team_id as team_id1_1_0_, users0_.users_id as users_id2_1_0_, user1_.id as id1_2_1_, user1_.first_name as first_na2_2_1_, user1_.last_name as last_nam3_2_1_, user1_.team_id as team_id4_2_1_ from team_users users0_ inner join user user1_ on users0_.users_id=user1_.id where users0_.team_id=?
Hibernate: select users0_.team_id as team_id1_1_0_, users0_.users_id as users_id2_1_0_, user1_.id as id1_2_1_, user1_.first_name as first_na2_2_1_, user1_.last_name as last_nam3_2_1_, user1_.team_id as team_id4_2_1_ from team_users users0_ inner join user user1_ on users0_.users_id=user1_.id where users0_.team_id=?
유저를 4번 조회합니다.
이렇게 연관 관계가 설정된 엔티티를 조회할 경우에 조회된 데이터 갯수(n) 만큼 연관관계의 조회 쿼리가 추가로 발생하는 문제를 N+1 문제라고 합니다.
이 N+1의 문제를 해결하기 위해서 Fetch Join을 사용하게 됩니다.
@Query("select t from Team t join fetch t.users")
Join, Fetch Join 차이점
- 일반 Join
- Fetch Join과 달리 연관 Entity에 Join을 걸어도 실제 쿼리에서 SELECT 하는 Entity는
오직 JPQL에서 조회하는 주체가 되는 Entity만 조회하여 영속화 - 조회의 주체가 되는 Entity만 SELECT 해서 영속화하기 때문에 데이터는 필요하지 않지만 연관 Entity가 검색조건에는 필요한 경우에 주로 사용됨
- Fetch Join과 달리 연관 Entity에 Join을 걸어도 실제 쿼리에서 SELECT 하는 Entity는
- Fetch Join
- 조회의 주체가 되는 Entity 이외에 Fetch Join이 걸린 연관 Entity도 함께 SELECT 하여 모두 영속화
- Fetch Join이 걸린 Entity 모두 영속화하기 때문에 FetchType이 Lazy인 Entity를 참조하더라도
이미 영속성 컨텍스트에 들어있기 때문에 따로 쿼리가 실행되지 않은 채로 N+1문제가 해결됨
실무에서 N+1문제로 DB가 죽어버리는 문제를 방지하기 위해서는 어떻게 해야 할까?
우선 연관관계에 대한 설정이 필요하다면 FetchType을 성능 최적화를 하기 어려운 즉시 로딩(EAGER)을 사용하는 게 아니라 지연 로딩 (LAZY) 모드로 사용을 하고 성능 최적화가 필요한 부분에서는 Fetch 조인을 사용