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 의 정보도 모두 받아오게 된다.
끝!