네로개발일기

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

JPA를 사용하다 보면 N+1의 문제에 마주치고 Fetch Join을 접하게 된다.

일단 Join으로 N+1을 해결하지 못하는지와 Fetch Join과 일반 Join의 차이점을 정리해보자.

 

Join, Fetch Join 차이점

[ 일반 Join ]

- Fetch Join과 달리 연관 Entity에 Join을 걸어도 실제 쿼리에서 SELECT 하는 Entity는 오직 JPQL에서 조회하는 주체가 되는 Entity만 조회하여 영속화

- 조회의 주체가 되는 Entity만 SELECT 해서 영속화하기 때문에 데이터는 필요하지 않지만 연관 Entity가 검색 조건에는 필요한 경우에 주로 사용한다.

[ Fetch Join ]

- 조회의 주체가 되는 Entity 이외에 Fetch Join이 걸린 연관 Entity도 함께 SELECT 하여 모두 영속화

- Fetch Join이 걸린 Entity 모두 영속화하기 때문에 FetchType이 LAZY인 Entity를 참조하더라도 이미 영속성 컨텍스트 안에 들어있기 때문에 따로 쿼리가 실행되지 않은 채로 N+1 문제가 해결됨

 


확인 

Team.java

@Entity
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
@ToString
public class Team {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    @OneToMany(mappedBy = "team")
    @Builder.Default
    private List<Member> members = new ArrayList<>();
    
    public void addMember(Member member) {
        member.setTeam(this);
        members.add(member);
    }
}

Member.java

@Entity
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
@ToString
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    private int age;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    private Team team;
}

 

일반 Join을 이용

// TeamRepository.java
@Query("SELECT distinct t FROM Team t JOIN t.members")
public List<Team> findAllWithMembersUsingJoin();
Hibernate:
    select
         distinct team0_.id as id1_1_,
         team0_.name as name2_1_
    from team team0_ 
    inner join
        member members1_
            on team0_.id=members1_.team_id

Team과 Member가 Join 된 형태의 쿼리가 실행됩니다. 하지만 가져오는 컬럼들은 Team의 컬럼인 id와 name만 가져오고 있다.

쿼리를 보면 분명 Join을 했는데 각 Team의 Lazy Entity인 members가 아직 초기화되지 않았다. 

실제로 일반 Join은 실제 쿼리에 Join을 걸어주기는 하지만 Join 대상에 대한 영속성까지는 관여하지 않는다.

 

Fetch Join을 이용한 N+1 해결

// TeamRepository.java
@Query("SELECT distinct t FROM Team t JOIN FETCH t.members")
public List<Team> findAllWithMembersUsingFetchJoin();
Hibernate:
    select
         distinct team0_.id as id1_1_,
         members1_.id as id1_0_1_,
         team0_.name as name2_1_,
         members1_.age as age2_0_1,
         members1_.name as name3_0_1_,
         members1_.team_id as team_id4_0_1_,
         members1_.team_id as team_id4_0_1_,
         members1_.id as id1_0_1_
    from team team0_ 
    inner join
        member members1_
            on team0_.id=members1_.team_id

일반 Join 과 Join의 형태는 같지만 SELECT 하는 컬럼에서 차이가 있다. 

  • 일반 Join : join 조건을 제외하고 실제 질의하는 대상 Entity에 대한 컬럼만 SELECT
  • Fetch Join : 실제 질의하는 대상 Entity와 Fetch join이 걸려있는 Entity를 포함한 컬럼 함께 SELECT

일반 Join 사용처

무작정 Fetch Join을 사용해서 전부 영속성 컨텍스트에 올려서 쓰기보다는 일반 Join을 적절히 이용하여 필요한 Entity만 영속성 컨텍스트에 올려서 사용하는 것이 좋다.

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !