" 컨테이너 환경(스프링)에서 동작하는 JPA 내부 동작 방식을 이해해보자 “
트랜잭션 범위의 영속성 컨텍스트 #
컨테이너의 도움 없이 순수 자바 환경에서 JPA 를 사용한다면, 아래와 같은 작업을 직접 처리해야 한다.
- EntityManager 관리
- 트랜잭션(Transaction) 관리
컨테이너(스프링)는 위와 같은 작업을 대신해준다.
따라서, 컨테이너가 제공하는 방법/전략(영속성 컨텍스트, 트랜잭션과의 관계)을 이해할 필요가 있다.
스프링 컨테이너 기본 전략 : 트랜잭션 범위 = 영속성 컨텍스트 범위 #
OSIV : false
트랜잭션 범위와 영속성 컨텍스트의 생존 범위가 같다.
- 트랜잭션 시작될 때, 영속성 컨텍스트 생성
같은 트랜잭션 = 같은 영속성 컨텍스트
- 트랜잭션 종료될 때, 영속성 컨텍스트 종료
- 준영속 상태가 되기 때문에
지연 로딩
,변경 감지
와 같은 기능을 사용할 수 없다. - JPA 표준에는 이 문제가 발생했을 때에 대해서 구체적으로 정의되지 않았다고 한다. 즉, 구현체마다 발생하는 예외가 다를 것이다. (하이버네이트 기준으로는,
org.hibernate.LazyInitializationException
예외가 발생한다.)
- 준영속 상태가 되기 때문에
@Transactional
을 사용하면, 해당 메서드 실행 직전/직후에 스프링의 트랜잭션 AOP 가 동작한다.
@Controller
class MyController {
@Autowired
MyService myService;
public void my() {
My my = myService.getMy(); // "준영속 상태"
}
}
(다양한 위치에서 엔티티 매니저를 주입받아 사용해도) 트랜잭션이 같다면 영속성 컨텍스트는 같다.
- 반대로 트랜잭션이 다르다면, 영속성 컨텍스트는 다르다.
- Thread-Safe 를 보장한다.
- Thread 별로 서로 다른 트랜잭션/영속성 컨텍스트를 사용한다고 한다.
@Service
class MyService {
@Autowired
MyRepository1 myRepository1;
@Autowired
MyRepository2 myRepository2;
@Transactional
public void my() {
// MyRepository1, MyRepository2 의 영속성 컨텍스트는 같다.
myRepository1.findMy(); // MyRepository1 의 EntityManager
myRepository2.findMy(); // MyRepository2 의 EntityManager
}
}
개발자가 직접 멀티 스레드 환경이나 영속성 컨텍스트/트랜잭션 등에 대해 관리했다고 한다면, 엄청 복잡했을 것이다.
준영속 상태에서는 지연로딩을 활용할 수 없다.
따라서, 아래와 같은 방법을 사용할 수 있다.
- 페치 전략 변경 : LAZY -> EAGER
- 강제초기화 : LAZY 엔티티에 대해 강제로 초기화한다. (* 영속성 컨텍스트 종료 전에)
- JPA 표준 : 프록시 강제초기화 메서드를 제공하지 않는다.
- 단, 초기화 여부 확인 메서드는 제공한다. (
PersistenceUnitUtil.isLoaded()
)
- 단, 초기화 여부 확인 메서드는 제공한다. (
- 하이버네이트 : 프록시 강제초기화 메서드를 제공한다.
- JPA 표준 : 프록시 강제초기화 메서드를 제공하지 않는다.
- FETCH JOIN : fetch join 하여 초기화를 해둔다.
- FACADE 계층 추가 : Controller / Service 사이에 하나의 Layer 를 더 둔다. 여기서
@Transactional
시작한다. (전제 : Service 는 비즈니스 로직만 처리하고, 프리젠테이션 계층을 위한 초기화 작업 등은 새로운 Layer 에서 한다.)
Front(View)에 맞는 데이터를 내려주기 위해 무분별하게 (service, repository) method 를 생성하는 것은 프리젠테이션 계층이 백엔드의 계층에 관여하는 것을 의미한다. (적당한 선에서 데이터를 공통으로 내려줄 수 있도록 하는 것이 좋을 수도 있다.) 이 부분을 인지하자.
OSIV #
위의 ‘준영속 상태’와 관련된 문제를 해결하기 위해 나온 개념이다.
영속성 컨텍스트를 View 단까지 열어둔다는 것이다.
JPA 용어는 OEIV 라고 한다. 다만, 관례상 OSIV 로 부른다고 한다.
- JPA : OEIV (Open EntityManager In View)
- Hibernate : OSIV (Open Session In View)
아래와 같은 방식이 있는데, 이에 대한 자세한 글은 따로 작성한다.
- 요청 당 트랜잭션
- 요청 시 : 트랜잭션, 영속성 컨텍스트 생성
- 응답 시 : 트랜잭션, 영속성 컨텍스트 종료
- (스프링에서 제공하는) 비즈니스 계층 트랜잭션
- 요청 시 : 영속성 컨텍스트 생성
@Transactional
시 : 트랜잭션 생성 ~ 종료
- 응답 시 : 영속성 컨텍스트 종료
- 요청 시 : 영속성 컨텍스트 생성
스프링 프레임워크가 제공하는 OSIV 라이브러리는 다음과 같다.
아래의 원하는 필터/인터셉터를 등록하면 된다.
Vendor | 적용 시점 | Class |
---|---|---|
JPA | 서블릿 필터 | org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter |
JPA | 스프링 인터셉터 | org.springframeowkr.orm.jpa.support.OpenEntityManagerInViewInterceptor |
Hibernate | 서블릿 필터 | org.springframework.hibernate4.support.OpenSessionInViewFilter |
Hibernate | 스프링 인터셉터 | org.springframework.hibernate4.support.OpenSessionInViewInterceptor |
트랜잭션 없이 읽기
스프링의 비즈니스 계층 트랜잭션(OSIV)와 관련하여 같이 이해해야할 내용이다.
영속성 컨텍스트를 통한 모든 변경은 트랜잭션 안에서 이뤄져야 한다.
- 트랜잭션 없이 엔티티 변경 후 영속성 컨텍스트를 플러시하면,
javax.persistence.TransactionRequiredException
예외가 발생한다.
조회 기능은 트랜잭션 없이 가능하다.
- 지연로딩 가능