Spring & Kotlin querydsl - Fetch Join

박미소·2024년 1월 23일
0

코틀린

목록 보기
33/44

Fetch Join


패치조인은 sql 에 존재하는 조인의 종류가 아닌 JPQL 의 성능을 최적화하기 위해 제공하는 조인의 한 형태이다.

Qcomment 로 EntityPathBase 를 만들어 comment 변수로 comment 엔티티에 접근 가능하게 만들고,

.leftJoin 으로 todo.comments -> todo 와 comment 로 이뤄진 ListPath 가 연결돼 comment 데이터 더미에 접근할 수 있게 됐다.

/**
 * QTodo is a Querydsl query type for Todo
 */
@Generated("com.querydsl.codegen.DefaultEntitySerializer")
public class QTodo extends EntityPathBase<Todo> {

    private static final long serialVersionUID = 740783912L;

    public static final QTodo todo = new QTodo("todo");

    public final ListPath<com.teamsparta.todo.domain.comment.model.Comment, com.teamsparta.todo.domain.comment.model.QComment> comments = this.<com.teamsparta.todo.domain.comment.model.Comment, com.teamsparta.todo.domain.comment.model.QComment>createList("comments", com.teamsparta.todo.domain.comment.mode



fetchJoin 이전 쿼리문 개수


3개의 데이터를 조회하는데 페이징에 썼던 count 쿼리문 1개를 제외하고, 4개의 쿼리문이 나갔다.



fetchJoin 이후 쿼리문 개수


두 테이블이 조인돼서 한방에 쿼리가 나갔다. N+1 문제 해결!!

t1_0. todo 프로퍼티뿐만이 아니라
c1_0. comment 의 프로퍼티들을 잘 가져왔다

만약 leftJoin 만 하고 fetchJoin 을 설정하지 않는다면, 똑같이 N+1 문제가 발생한다.




그 이유는 쿼리문에서 selectFrom(todo) todo 에서만 select 조회해오기 때문에 영속성 컨텍스트에 comment 가 없어서 계속 select commentcomment 를 가져오는 쿼리가 발동한 것.

val comment = QComment.comment                         // comment EntityPathBase 정의
        val query = queryFactory.selectFrom(todo)
            .where(whereClause)
            .leftJoin(todo.comments, comment)         // left join == todo 테이블을 기준으로 comment 를 연결해준다는 뜻 , 두 테이블이 join 됨
            .fetchJoin()                              // 레프트 조인 이후 fetchJoin() 을 설정 
            .offset(pageable.offset)
            .limit(pageable.pageSize.toLong())

근데 org.hibernate.orm.query : HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory 경고가 뜬다.

offset 쿼리와 limit 쿼리가 나가지 않았다. Fetch Join 을 하게 되면 offset 과 limit 이 null 로 나가게 된다.

그래서 데이터 리스트를 다 가져와서 어플리케이션 상에서 모든 데이터를 다 가져온 이후에 페이징(offset 과 limit)을 적용하는 것이다.

todo를 다 가져오고 나서 페이징 처리를 한건데 지금은 todo 할일 데이터가 몇개 없지만 만개의 데이터가 된다면 메모리에 만건을 올려두고 페이징 처리를 하게 된다면 큰 문제가 된다.(모든 데이터를 가져와 페이징 개수만큼 자른다)

그래서 todo - comment가 OneToMany 일대다 관계인데 이럴때 Fetch Join 을 하면서 Paging 까지 같이 쓰면 위험하다.

그래서 일대다 관계일 땐 Fetch Join을 지양해야 한다.

또 Fetch Join을 두번 쓴다면 Multiple Fetch Exception 오류가 뜬다.

만약 todo - todoApplication과 같이 또 다른 일대다 관계의 테이블을 left join 하고 성능 최적화를 위해서 fetch join을 쓰는 것은 불가능하다.

N개의 테이블에서 계속 데이터를 가져오게되면서 기하급수적으로 늘 수 있기 때문에 Hibernate 에서 허용하지 않는다. --> 주의하기!!

ManyToOne 관계를 여러 개 하는건 상관 없다.


일대다 관계에서 Pagination도 적용하면서 성능 최적화를 하고 싶다면, fetch Join 을 꼭 !! 설정하지 말고!! yml 파일에 default_batch_fetch_size 를 적용하면 된다(보통 500개 정도만 씀)

연관된 엔티티를 500 개까지 가져올 수 있게 된다. default_batch_fetch_size 는 IN 쿼리를 사용한다.

IN 쿼리는 ROW를 체크만하는 EXISTS랑 다르게 ROW의 데이터를 모두 확인하기 때문에 쿼리가 여러번 나가지 않고 한방에 사용할 수 있는 것이다.


다대일 관계는 Fetch Join을 여러개 써도 된다.

0개의 댓글