2017년 1월 21일 토요일

JPA LazyInitializationException 문제 해결하기

JPA를 사용해서 신나게 개발하다가 처음 겪는 장벽이 있다면
바로 LazyInitializationException 일 것이다.

JPA나 hibernate를 사용하지 않는다면 겪을 일이 없는 예외이기 때문이다.

우선 JPA는 기본적으로 다음과 같이 table과 column을 관리한다.

* table
 @Entity로 정의된 class에 대응

* column
class에 선언된 멤버 변수의 타입을 체크, primitive type의 경우에는 그대로 column을 생성.
one-to-one, one-to-many, many-to-one, many-to-many annotation이 붙은 경우에는 상황에 맞게 처리한다.

자. primitive type의 경우에는 아무런 문제가 없다.
jpaRepository를 이용해서 값을 가져오면 잘 가져오고 잘 저장한다.

LazyInitializationException을 만날 일 자체가 없다.

그런데, @OneToOne 부터는 이야기가 다르다.

JPA가 instance를 관리하는 life-cycle을 이해하지 않으면 LazyInitializationException에서 벗어날 수가 없다.

https://docs.oracle.com/cd/E16439_01/doc.1013/e13981/undejbs003.htm
JPA lifeCycle from https://docs.oracle.com/cd/E16439_01/doc.1013/e13981/undejbs003.htm

새로 생성해서 instance를 저장한 경우 managed 상태가 된다. 이경우에는 lazyLoading이 적용된다. 그러다가 @Tranactional로 선언된 경계를 벗어난 순간 detached 상태가 된다. 이때 lazyLoading 대기 상태이던 필드에 접근하면 LazyInitializationException이 발생한다.

요컨대, JPA 관리 범위안에(@Tranactional) 있는 경우에는 lazyLoading 요청에 대해서 대응할 수 있지만 범위를 벗어나면 exception이 발생하는 것이다.

그렇다면 해결책은 무엇인가?

* OSIV(Open Session In View) : true로 관리범위를 presentation layer까지 확장한다. @Tranactional의 범위가 늘어난다고 생각하자.

* OSIV false인 경우, DTO(viewModel)을 활용

1. repository에서 값을 가져올때 fetch join 해서 가져온다. JPQL등을 이용해서 query로 해당 칼럼을 미리 join해서 lazyLoading이 아닌 eagerLoading이 이루어 지게 한다.

2. @Tranactional 범위 안에서 필요한 필드의 값을 loading 한다. Collections라면 size() 메서드를 호출하는 것으로 충분하다.


댓글 없음:

댓글 쓰기