[Spring] Error - org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role : could not initialize proxy - no Session

하쮸·2024년 12월 12일

Error, Why, What, How

목록 보기
8/68

1. 상황.

@Entity
@Setter
@Getter
public class Question {
		(생략)
	@OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE)
	private List<Answer> answerList;
}
@Test
void Jpa_Question_Answer() {
	Optional<Question> oq = this.questionRepository.findById(2);
	assertTrue(oq.isPresent());
	Question question = oq.get();
		
	List<Answer> answerList = question.getAnswerList();
	assertEquals(1, answerList.size());
	assertEquals("네 자동으로 생성됨.", answerList.get(0).getContent());
}
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: could not initialize proxy - no Session
  • 작성한 테스트 코드를 테스트 하던 도중 발생한 에러.

2. 원인.

  • 예외 발생 흐름

    1. Question 엔터티가 로드됨
      (questionRepository.findById(2) 호출 시 Question 엔터티가 데이터베이스에서 로드.)
    2. answerList는 Lazy로 설정되어 있어 초기에는 데이터베이스 쿼리가 실행되지 않고 프록시 객체만 생성.
      • 프록시는 실제 데이터베이스 액세스를 대체하는 가짜 객체.
      • 여기서 액세스란?
        • Hibernate가 데이터베이스와 통신하여 실제 데이터를 가져오는 작업.
        • 프록시 객체는 처음에 실제 데이터를 담고 있지 않고 필요할 때 데이터베이스에서 값을 조회하려고 함.
        • 이 과정에서 Hibernate가 데이터베이스에 쿼리를 실행하고 해당 데이터를 메모리에 로드하는 것을 액세스라고 표현.
    3. Hibernate 세션이 닫힘.
    4. 테스트 코드에서 question.getAnswerList() 호출.
    5. Lazy Loading이 시도되지만 세션이 닫혀있음.
    6. Hibernate가 데이터를 로드할 수 없으므로 LazyInitializationException이 발생.
  • 즉, questionRepositoryfindById()메서드를 통해 Question 객체를 조회하고 나면 DB 세션이 끊어지기 때문

  • answerList 필드는 Question 엔터티에서 @OneToMany 관계로 정의된 컬렉션이고, 기본적으로 Lazy Fetching(fetch=FetchType.LAZY)으로 설정.

    • 데이터가 필요할 때 요청하는 방식을 지연(Lazy)방식이라 함
    • Lazy Fetching은 해당 필드를 실제로 접근할 때 데이터를 로드하지만, 테스트 코드에서 접근 시 Hibernate 세션이 닫혀 있어 데이터를 가져올 수 없어서 발생하는 에러.

3. 해결.


3-1. FetchType 변경.

  • @OneToMany(fetch = FetchType.Lazy) -> @OneToMany(fetch = FetchType.Eager)
    • Lazy에서 Eager로 변경하게 되면 연관 관계 설정이 되어있는 Entity를 모두 다 가져옴.
    • 다만, 이 방식은 연관된 데이터를 항상 다 로드하기 때문에 성능에 영향을 줄 수 있으므로 신중하게 사용해야됨.
@Entity
@Setter
@Getter
public class Question {
		(생략)
	@OneToMany(mappedBy = "question", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
	private List<Answer> answerList;
}

3-2. @Transactional

  • @Transactional를 쓰면 메서드가 종료될 때까지 DB세션이 유지됨.
    • 즉, 문제의 원인이였던 DB 세션 종료를 원천적으로 막아줌.

4. 참고.


  • Lazy Loading과 Hibernate의 동작 방식
    • Lazy Loading의 기본 개념
      • Lazy Loading은 데이터베이스에서 연관된 데이터를 즉시 로드하지 않고, 해당 데이터가 필요한 시점에 로드하는 방식.

Ex

@OneToMany(mappedBy = "question", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private List<Answer> answerList;
  • 이 설정은 answerList가 필요할 때만 쿼리가 실행.
    • Hibernate는 초기에 실제 데이터를 가져오지 않고, 대신 프록시 객체를 생성.

  • 프록시는 실제 클래스를 상속받아서 만들어짐.
    • 하이버네이트가 내부적으로 상속받아서 만듦.
    • 프록시 객체는 처음 사용할 때 한 번만 초기화 됨.
    • 프록시 객체가 초기화 되면 프록시 객체를 통해서 실제 엔티티에 접근 가능.
  • Hibernate Session의 역할
    • Lazy Loading이 동작하려면 Hibernate Session이 열려 있어야 함.
    • 프록시 객체는 세션이 열린 동안 데이터베이스에 접근하여 데이터를 가져올 수 있음.
    • 하지만 세션이 닫힌 후 프록시 객체가 접근하면, Hibernate데이터베이스에 접근할 수 없으므로 LazyInitializationException를 발생시킴.
profile
Every cloud has a silver lining.

0개의 댓글