Spring Data JPA (2)

 

 

프로젝트를 진행하면서 처음보는 오류를 발견하였다. 🙄

LazyInitializationException

같은 백엔드 팀원분과 서칭해서 반나절만에 해결할 수 있었다. 

 

환경 : AWS ec2 인스턴스에서 서버 배포 + Spring Data JPA(hibernate) + MySQL

 

어떤 오류?

2022-09-02 06:59:34.637 ERROR 35806 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.lucky7.stackoverflow.question.entity.Question.comment, could not initialize proxy - no Session] with root cause

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.lucky7.stackoverflow.question.entity.Question.comment, could not initialize proxy - no Session
        at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:614) ~[hibernate-core-5.6.10.Final.jar!/:5.6.10.Final]

요약하자면 Lazy(지연) Initialization(초기화) 오류이다.

프록시를 초기화할 수 없다고 나오며 no Session이라고 세션이 없다는 로그가 보인다.

 

 

오류가 발생하는 이유?

 

일단 Spring Data JPA의 Lazy 전략에 대한 이해가 부족하였고 영속성 컨텍스트의 생명주기에 대해 알지 못하여서 발생하는 문제였다.

간단하게 정리해보자면 이렇다.

  • 서비스 단에 @Transactional을 붙여놓은 상태여서 메서드가 종료되면 Hibernate의 Session도 함께 종료되어 영속성 컨텍스트에서 사라진다.
  • 영속성 컨텍스트는 보통 트랜잭션과 생명주기를 같이한다. → service 호출 끝나고 controller로 돌아가면 영속성 상태가 끝난다.
  • jpa의 효율 문제 때문에 엔티티가 List나 객체로 참조하고있는 부분을 전부 쿼리문을 날려서 채워놓진 않고 일단 프록시 객체로 채워놓은 이후 나중에 getter를 사용하면 쿼리를 보내서 실제 데이터로 채운다.
  • Question에 조회하는 요청 시 owner나 comment, answer 에 실제 데이터를 쿼리를 날려서 저장하는 것이 아닌 프록시 객체로 채워진다.(stub 데이터라고 보면 됨) -> 프록시 객체로 채워진 상태에서 Service 메서드 호출이 끝나면 영속성 컨텍스트의 관리대상에서 제외되기 때문에 이후엔 gatter를 사용해도 쿼리문을 날려서 해당 객체를 실제 데이터로 채우지 않는다.
  • QuestionService에 @Transactional 애너테이션 붙어있음 → controller에서 service의 메서드 호출한 순간부터 트랜젝션 생성 → 서비스 메서드 끝나면서 트랜젝션 종료됨 → mapper에서 question객체를 dto 객체로 변환시켜주면서 owner, comment, answer 객체를 불러오는 쿼리 보냄 → 영속성 컨텍스트에 존재하지 않아서 프록시 객체를 실제 객체로 채우지 못함 → LazyInitializationException !

 

 

참고 자료 : https://cantcoding.tistory.com/78

 

 

 

해결 방법

 

mapper에서 entity 객체에 gatter 메서드를 호출하는 부분까지 트랜잭션 안으로 포함시켜주면 해결할 수 있다.

-> controller 에 @Transactional 애너테이션 붙여서 mapper 사용하는 부분까지 전부 하나의 트랜잭션으로 포함시켜 주었다.

이렇게 하면 http 요청이 와서 컨트롤러에 메서드가 호출하는 시점부터 트랜잭션이 생성되고 서비스에서 저장이나 삭제를 처리하고 다시 컨트롤러로 돌아와서 매퍼를 호출할 때까지도 트랜잭션이 유지되기 때문에 영속성 컨텍스트에서 관리하는 상태가 된다.

그리고 컨트롤러에서 응답을 보내게 되면(return) 메서드 호출이 끝나므로 트랜잭션이 종료된다.

 

 

아직은 이해가 깊지 않아 이게 가장 좋은 방법인지는 모르겠다.

하지만 해결법이 간단하고 성능상의 문제 없이 해결할 수 있었고 Spring Data JPA라는 녀석에 대해서 한걸음 더 이해할 수 있었다.

 

 

 

 

 

 

Spring Data JPA

 

Spring Data JDBC와 Spring Data JPA는 모두 Spring Data라는 프로젝트에서 지원하는 PSA(일관된 서비스 추상화)가 적용되어 있기 때문에 사용법이 거의 동일하다.

 

Spring Data JPA는 Spring Data 패밀리 기술 중 하나이다.

JPA 기반의 데이터 액세스 기술을 좀 더 쉽게 사용할 수 있게 해준다.

 

 

➤ JPA vs Hibernate ORM vs Spring Data JPA

 

JPA

  • 엔터프라이즈 Java 애플리케이션에서 관계형 데이터베이스를 사용하기 위해 정해 놓은 표준 스펙(사양 또는 명세, Specification)

Hibernate ORM

  • JPA의 표준 스펙을 구현한 구현체, 실제로 사용할 수 있는 API라고 보면 된다.

Spring Data JPA

  • JPA 스펙을 구현한 구현체의 API(일반적으로 Hibernate ORM)을 조금 더 쉽게 사용할 수 있도록 해주는 모듈이다.

 

 

 

 

Repository에 JPA 기능 추가하기

 

엔티티 클래스들 정의 -> 엔티티 클래스 매핑 -> repository 인터페이스 작성

 

repository 인터페이스에서 spring data jdbc와 달라진 점 : CrudRepository를 상속하는 대신 JpaRepository를 상속한다.

JpaRepository : CrudRepository보다 JPA에 특화된 더 많은 기능을 포함하고 있다.

 

 

▶️ JPQL을 통한 객체 지향 쿼리 사용

 

  • JPA에서는 JPQL이라는 객체 지향 쿼리를 통해 데이터베이스 내의 테이블을 조회할 수 있다.
  • JPQL은 엔티티 클래스 객체를 대상으로 객체를 조회하는 방법이다.  (데이터베이스의 테이블을 대상으로 조회 x)
  • JPQL의 문법을 사용해서 객체를 조회하면 JPA가 내부적으로 JPQL을 분석해서 적절한 SQL을 만든 후 데이터베이스를 조회하고 조회한 결과를 엔티티 객체로 매핑한 뒤 반환한다.
  • ‘SELECT c’와 같이 별칭으로 생략한 형태로 사용 가능하다.

 

 

▶️ 네이티브 SQL을 통한 조회

 

  • Spring Data JDBC와 JPA는 네이티브 SQL 쿼리를 작성해서 사용할 수 있다.
  • nativeQuery 애트리뷰트 값을 true로 설정하면 value 애트리뷰트에 작성한 SQL 쿼리가 적용된다.

 

 

➤ Spring Data JDBC와 Spring Data JPA의 @Query 차이

 

둘다 애너테이션 이름은 같지만 패키지 자체가 다르다.

➡️ Starter 모듈이 둘 다 의존 라이브러리에 포함되어 있는 경우 패키지 경로를 혼동하지 않도록 주의한다.

 

Spring Data JDBC의 @Query 애너테이션 패키지 경로

import org.springframework.data.jdbc.repository.query.Query

 

Spring Data JPA의 @Query 애너테이션 패키지 경로

org.springframework.data.jpa.repository.Query

 

 

 

 

 

 

읽어주셔서 감사합니다.

오개념에 대한 지적은 늘 환영입니다.

 

1