Spring 과 JPA

 

J2EE 컨테이너 환경의 대표격인 Spring 에서

JPA 를 사용할 경우 알아야할 중요한 개념을 나열해보겠다.

 

 

순수하게 J2SE 환경에서 JPA 를 사용하면 개발자가 직접 EntityManager 를 생성해야하며,

Transaction 도 관리해야한다.

 

J2EE 환경에서 JPA 를 사용하면 트랜잭션 범위의 영속성 컨텍스트 전략이 기본으로 채택된다.

 

Spring 에서 트랜잭션 범위와 영속성 컨텍스트 생존 범위가 동일한 전략을 따르면, 트랜잭션 AOP 를 활용한다.

 

 

트랜잭션 범위와 영속성 컨텍스트 생존 범위가 동일한 전략

해당 전략에서는 트랜잭션을 시작할 때, entityManager 를 생성하고

트랜잭션이 종료 될 때, entityManager 도 닫힌다.

 

트랜잭션이 같으면 동일한 EntityManager 를 사용하며, 동일한 영속성 컨텍스트를 사용하게 된다.

트랜잭션이 다르면 다른 EntityManager 를 사용하며, 다른 영속성 컨텍스트를 사용하게 된다.

 

트랜잭션 범위의 영속성 컨텍스트 전략을 사용하면,

트랜잭션 범위에서 벗어난 계층(presentation layer 등) 에서의 엔티티는 준영속 상태이다.

 

엔티티가 준영속 상태라면,

변경 감지와 지연로딩이 동작하지 않는다.

트랜잭션을 벗어난 범위 이므로..

변경 감지의 기능이 동작하지 않는다라는 점은 문제가 되지 않고..

지연로딩이 동작하지 않는다는 점은 문제가 될 수 있다.

 

트랜잭션 범위를 벗어나서 지연로딩을 사용할 수 없는 문제는 2 가지 방법으로 해결가능하다.

1. 트랜잭션 범위에서 미리 로딩 해놓기

2. OSIV

 

 

트랜잭션 범위에서 미리 로딩

트랜잭션 범위에서 미리 로딩 해놓기의 방법은 총 3 가지가 존재한다.

1. 글로벌 페치 전략을 Eager 로..

-> 글로벌 페치 전략을 Eager 로 하면, 사용하지 않는 엔티티를 항상 로딩하여 성능상 문제가 될 수 있다.

-> N+1 문제가 발생

페치 조인을 사용하지 않은 JPQL 로 조회시, 즉시 로딩 전략에 의해 연관된 엔티티에 대한 추가적인 쿼리가 실행된다.

(글로벌 페치 전략 보다 우선으로 동작하는 것이 JPQL 이다.)

 

2. 페치 조인

-> 연관된 엔티티까지 함께 조회 하므로 N+1 문제도 발생하지 않는다.

연관된 엔티티를 함께 조회 하였으므로 글로벌 페치 전략이 무의미하다.

그러나, 화면 별로 필요한 데이터에 따라 페치 조인을 무분별하게 사용하는 것은

repository layer 가 presentation layer 을 논리적으로 의존하는 것일 수 있으니 검토가 필요하다.

 

3. 강제 초기화

트랜잭션 범위에서 미리 로딩을 위해 트랜잭션 범위를 벗어나기 직전에

강제 초기화를 한다. (서비스 계층에서 하는게 마음에 안들면 Facade 계층을 만들어야할 수 도 있다.)

 

 

OSIV (open session in view)

OSIV 는 Hibernate 에서 사용하는 용어이며,

JPA 공식 용어는 OEIV(open entitymanager in view) 이나 OSIV 로 관례상 불림

 

OSIV 는 영속성 컨텍스트를 뷰까지 열어두는 기능이다.

영속성 컨텍스트가 뷰까지 살아 있으면 엔티티도 영속 상태로 유지 되므로

뷰에서도 지연 로딩을 사용할 수 있는 것이다.

 

OSIV 의 구현 방식은 2 가지가 존재한다.

1. 요청 당 트랜잭션의 OSIV

2. 비즈니스 계층 트랜잭션의 OSIV

 

하나 씩 알아보겠다.

 

1. 요청 당 트랜잭션, OSIV

요청 당 트랜잭션에서는 Client 의 요청이 들어오자마자 트랜잭션을 시작하고

요청이 끝날 때 트랜잭션을 종료하는 것이다.

이 방식도 트랜잭션 범위와 영속성 컨텍스트의 범위가 동일한 전략을 따른다.

 

Spring 에서는 필터나 인터셉터에서 트랜잭션을 시작하고 종료하는 방식이다.

영속성 컨텍스트 범위도 대폭 증가하여 필터, 인터셉터에서 시작 종료 되며

presentation layer 에서도 영속성 컨텍스트가 살아 있는 것이다.

-> 따라서, presentation layer 에서 지연 로딩이 가능하지만

필터나 인터셉터에서 트랜잭션이 종료되어 presentation layer 에서 변경한 엔티티가

변경 감지에 포함된다.

 

2. 비즈니스 계층 트랜잭션, OSIV (트랜잭션 범위와 영속성 컨텍스트 생존 범위가 다른 전략)

해당 방법은 Spring 이 제공하는 방법이며,

비즈니스 계층에서만 트랜잭션을 사용하는 OSIV 이다. (@Transactional 범위)

-> 트랜잭션 범위와 영속성 컨텍스트 범위가 다르다.

 

동작 순서를 알아보자

1. Client 요청이 들어오면 Servlet Filter 나 Spring Interceptor 에서

영속성 컨텍스트를 생성한다. (트랜잭션 시작은 하지 않음)

 

2. Service layer 에서 @Transactional 로 트랜잭션을 시작할 때

미리 생성해둔 영속성 컨텍스트를 이용하여 트랜잭션 시작

 

3. Service layer 가 끝나면 트랜잭션을 커밋 하고 영속성 컨텍스트를 flush 한다.

(트랜잭션 종료, 영속성 컨텍스트는 유지)

 

4. Controller layer (View 포함) 에서도 영속성 컨텍스트가 유지 되므로

조회된 엔티티는 영속 상태이다. (지연 로딩 가능)

 

5. Servlet Filter 나 Spring Interceptor 에서 영속성 컨텍스트를 종료한다.

(트랜잭션 커밋은 아니므로 flush 를 호출 하지는 않는다.)

 

 

참고. 트랜잭션 커밋과 영속성 컨텍스트

트랜잭션 커밋 == 영속성 컨텍스트 Flush + 실제 commit

영속성 컨텍스트 Flush == 변경 감지 + 쓰기 지연 SQL flush

 

 

참고. 영속성 컨텍스트와 엔티티 조회/수정

영속성 컨텍스트는 트랜잭션 범위 안에서 엔티티를 조회하고 수정할 수 있다.

영속성 컨텍스트는 트랜잭션 범위 밖에서 엔티티를 조회만 할 수 있다.

(직접 flush 호출하여 수정 시, TransactionRequiredException 발생)

 

따라서, 스프링이 제공하는 OSIV 를 사용하면, presentation layer 에서는

트랜잭션 범위 밖이므로 조회만 가능하다.

영속상태의 엔티티 이지만, 수정하여도 filter, interceptor 에서 flush 하지 않고 close 이므로

변경감지가 동작하지는 않는다.

 

참고.

controller 에서 service 를 호출하여 영속상태의 엔티티를 얻고

엔티티를 수정한 상태에서 또 다른 service 를 호출하고 트랜잭션 커밋을 하면..

controller 에서 수정한 엔티티가 변경감지로 반영 되어버린다...

-> 서로 다른 트랜잭션이지만.. filter, interceptor 에서 생성한 동일한 영속성 컨텍스트를 공유하여

사용하기 때문이다.

 

참고.

스프링 OSIV 에서 트랜잭션이 commit 되어도 DB connection 은 반환 되지 않는다.

반환 되지 않았기 때문에 지연 로딩도 가능한 것..

'Spring > DB, Cache 연동' 카테고리의 다른 글

DBCP (HikariCP)  (0) 2024.04.22
Spring Data JPA (Hibernate), N + 1 Query Problem  (0) 2024.02.20
JPA 등록, 기본 키 생성 전략  (0) 2023.06.19
JPA 변경 감지와 플러시  (0) 2023.06.15
JPA Entity Default Constructor  (1) 2023.06.07