본문 바로가기
트러블 슈팅

fetchJoin와 BatchSize를 같이 사용한 경우의 경고문

by jhdevtrace 2024. 10. 11.

api를 실행해보면, 에러는 발생하지 않는데, 콘솔창에 어떤 경고 문구가 뜹니다. 이 문제를 발생시킨 코드는 페이지 제일 아래에 첨부!

2024-10-09T16:49:57.674+09:00  WARN 21808 --- [spring-plus] [nio-8080-exec-9] org.hibernate.orm.query                  : HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory

 

 

결론

이 경고가 뜨는 이유는, fetchJoin을 적용하면서 BatchSize까지 적용할 경우, 이런 경고 문구가 뜨는 것입니다.

  • fetchJoin은 즉시 로딩을 통해 연관된 데이터를 한 번에 가져오는 방식입니다.
  • @BatchSize는 지연 로딩(Lazy Fetch)을 사용하면서 필요할 때마다 연관된 데이터를 배치로 가져오는 방식입니다.

둘을 같이 사용하면 Hibernate는 두 가지 상반된 전략을 적용하려고 하기 때문에 경고가 발생하는 것입니다. 둘 중 하나만 적용해서 쓰게 된다면, 경고문이 뜨지 않게 됩니다.

 

 

 

 

============================================================================================

 

 

 

문제 있는 코드

아래의 코드는 QueryDsl에서 fetchJoin() 을 사용하고 있고, 엔티티 클래스에서 @BatchSize를 사용하고 있습니다.

이 경우에는 fetchJoin()을 제외하고 @BatchSize만 사용하면서 문제를 해결했습니다.

QueryDsl

  @Override
    public Page<TodoSearchResponse> searchTodo(Pageable pageable, String title, LocalDateTime startDateTime, LocalDateTime endDateTime, String nickname){
        QTodo todo = QTodo.todo;
        QManager manager = QManager.manager;
        QComment comment = QComment.comment;
        QUser user = QUser.user;

        List<Todo> todos = queryFactory
                .select(todo)
                .from(todo)
                .leftJoin(todo.managers, manager).fetchJoin() // 여기 있는 .fetchJoin() 를 없애면서 문제 해결
                .leftJoin(todo.comments, comment)
                .leftJoin(todo.user, user)
                .offset(pageable.getOffset())
                .where(
                        titleContains(title),
                        userNicknameContains(nickname),
                        todoDateBetween(startDateTime, endDateTime)
                )
                .groupBy(todo.id)
                .orderBy(todo.createdAt.desc())
                .limit(pageable.getPageSize())
                .fetch();

        List<TodoSearchResponse> dtoList = todos.stream()
                .map(t -> new TodoSearchResponse(
                        t.getTitle(),
                        t.getManagers().size(),
                        t.getComments().size()))
                .collect(Collectors.toList());

        return new PageImpl<>(dtoList, pageable, dtoList.size());

    }

    private BooleanExpression titleContains(String titleKeyword) {
        return titleKeyword != null ? todo.title.contains(titleKeyword) : null;
    }

    private BooleanExpression userNicknameContains(String managerNickname) {
        return managerNickname != null ? user.nickname.contains(managerNickname) : null;
    }

    private BooleanExpression todoDateBetween(LocalDateTime startDate, LocalDateTime endDate) {
        return startDate != null && endDate != null ? todo.createdAt.between(startDate, endDate) : null;
    }

Todo Entity

package org.example.expert.domain.todo.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.example.expert.domain.comment.entity.Comment;
import org.example.expert.domain.common.entity.Timestamped;
import org.example.expert.domain.manager.entity.Manager;
import org.example.expert.domain.user.entity.User;
import org.hibernate.annotations.BatchSize;

import java.util.ArrayList;
import java.util.List;

@Getter
@Entity
@NoArgsConstructor
@Table(name = "todos")
public class Todo extends Timestamped {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String contents;
    private String weather;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    @BatchSize(size = 10)
    @OneToMany(mappedBy = "todo", cascade = CascadeType.REMOVE)
    private List<Comment> comments = new ArrayList<>();

    @BatchSize(size = 5)
    @OneToMany(mappedBy = "todo",  cascade = CascadeType.PERSIST)
    private List<Manager> managers = new ArrayList<>();

    public Todo(String title, String contents, String weather, User user) {
        this.title = title;
        this.contents = contents;
        this.weather = weather;
        this.user = user;
        this.managers.add(new Manager(user, this));
    }
}