fetch join 으로 해결하기!!

meyame·2025년 1월 10일

문제

org.hibernate.LazyInitializationException: Could not initialize proxy

이런 오류가 떴다. 찾아보니 이는 Hibernate 의 Lazy Loading 설정과 관련된 문제라고 한다. Hibernate 가 프록시 객체를 초기화하려고 할 때, 데이터베이스 세션이 이미 종료된 경우 발생한다고 한다.

분석

현재 Quiz 엔티티는 다음과 같이 구성되어있다.

package com.meyame.welcomegameback.domain;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;

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

@Entity
@Getter
@NoArgsConstructor
public class Quiz {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Lob
    private String sentence;

    private boolean isCorrect; // sentence 의 O/X 여부

    @OneToMany(mappedBy = "quiz", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Answer> answers = new ArrayList<>();

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id", nullable = false)
    private Member member;

}

여기서 주목해야할 부분은 Quiz의 member 필드 위에 달린 LAZY 로딩이다.

나는 memberId를 통해 선택한 member 의 전체 퀴즈를 조회하기 위해 아래와 같은 코드를 짰다.

  @Transactional
    public List<QuizDto> findQuizzesByMemberId(Long memberId) {
        List<Quiz> quizzes = quizRepository.findAllByMemberId(memberId);

        return quizzes.stream()
                .map(QuizDto::from)
                .toList();
    }
public interface QuizRepository extends JpaRepository<Quiz, Long> {
    List<Quiz> findAllByMemberId(Long memberId);
}

QuizDto 에는 아래 처럼 member 의 name도 넣어두었기 때문에, quiz의 member 필드를 통해 멤버의 name을 불러와야한다.

@Builder
public record QuizDto (
    Long id,
    String sentence,
    String name
) {
    public static QuizDto from(Quiz quiz) {
        return QuizDto.builder()
                .id(quiz.getId())
                .sentence(quiz.getSentence())
                .name(quiz.getMember().getName())
                .build();
    }
}

그런데 내가 현재 quiz -> member 를 지연로딩 설정해두었기 때문에, quiz.getMember()를 호출하면, member 필드에 프록시 객체만 들어간다. 이런 상태인데 getName() 을 호출하려니 당연히 member 의 name 정보를 찾을 수가 없는 것이다.

그렇다면 어떻게 해결해야할까? 스프링 데이터 JPA 방식으로 해결하려면 @EntityGraph를 사용하면 된다.

해결

어랏!! Lazy Loading 문제!! 얼마 전에 공부했는데! 두근두근 설레는 마음으로 해결을 시작했다.

역시 기초적인 문제였다. 하지만 얼마 전에 공부하지 않았다면 난 헤맸겠지 ㅎㅎ 꾸준히 공부를 더 해야겠다.

아무튼 repository 에 코드 한 줄만 추가해주면 된다.

public interface QuizRepository extends JpaRepository<Quiz, Long> {
    @EntityGraph(attributePaths = {"member"})
    List<Quiz> findAllByMemberId(Long memberId);
}

이렇게 설정하면, QuizService 에서 저 메서드를 호출하며 sql 을 날릴 때, quiz 뿐만 아니라 연관된 member 의 정보도 모두 받아오게 된다.

끝!

profile
유한한 가치를 나누어 무한한 가치를 생성해내자!

0개의 댓글