패치조인은 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
3개의 데이터를 조회하는데 페이징에 썼던 count 쿼리문 1개를 제외하고, 4개의 쿼리문이 나갔다.
두 테이블이 조인돼서 한방에 쿼리가 나갔다. N+1 문제 해결!!
t1_0. todo 프로퍼티뿐만이 아니라
c1_0. comment 의 프로퍼티들을 잘 가져왔다
만약 leftJoin 만 하고 fetchJoin 을 설정하지 않는다면, 똑같이 N+1 문제가 발생한다.
그 이유는 쿼리문에서 selectFrom(todo)
todo 에서만 select
조회해오기 때문에 영속성 컨텍스트에 comment
가 없어서 계속 select comment
로 comment
를 가져오는 쿼리가 발동한 것.
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을 여러개 써도 된다.