<aside>
❗
트랜잭션 범위의 영속성 컨텍스트
- 스프링 환경이나 J2EE 컨테이너 환경에서 JPA를 사용하면
컨테이너가 제공하는 전략을 따라야 한다. ( 개발자가 직접 엔티티 매니저와 트랜잭션 관리 X)
[스프링 기본 전략]
-
스프링 컨테이너는 트랜잭션 범위의 영속성 컨텍스트 전략을 기본으로 사용
즉, 트랜잭션의 범위와 영속성 컨텍스트의 생존 범위가 같다.
-
**같은 트랜잭션 안에서는 같은 영속성 컨텍스트에 접근
**한다.
-
즉, 다양한 위치에서 EntityManager를 주입 받아 사용하더라도
같은 트랜잭션
안이면 **항상 같은 영속성 컨텍스트를 사용
**한다.
-
반대로 여러 스레드에서 동시에 요청이 들어온 경우 같은 EntityManager를 사용하는데
**트랜잭션이 다르면 서로 다른 영속성 컨텍스트를 사용
**한다.
-
**스프링 컨테이너는 스레드마다 각각 다른 트랜잭션을 할당
**한다.
따라서 같은 엔티티 매니저를 호출해도 접근하는 영속성 컨텍스트가 다르므로
멀티 스레드 상황에 안전하다.
-
스프링이나 J2EE 같은 컨테이너의 가장 큰 장점은 트랜잭션과 복잡한 멀티 스레드 환경을
컨테이너가 처리해준다는 것
-
개발자는 싱글 스레드 애플리케이션처럼 단순하게 개발할 수 있고, 결과적으로
비즈니스 로직 개발에 집중할 수 있다.
@Transactional
- 이 애너테이션을 사용하면, 호출한 메서드를 실행하기 직전에
스프링 트랜잭션 AOP는 대상 메서드를 호출하기 직전에 트랜잭션을 시작하고
대상 메서드가 정상 종료되면, 트랜잭션을 커밋하면서 종료한다.
- 이때, (1)
트랜잭션을 커밋하면
JPA는 먼저 **(2)영속성 컨텍스트를 플러시해서 변경 내용을 데이터베이스에 반영한 후
**에 **(3)데이터베이스 트랜잭션을 커밋
**한다.
</aside>
<aside>
❗
기본 전략인 경우 발생할 수 있는 문제점
- 컨테이너 환경의 기본전략을 사용하면 트랜잭션 범위에 있는 Service와 Repository를 제외한
컨트롤러나 뷰에서는 엔티티가 준영속 상태가 된다.
- 즉, 지연 로딩이나 변경 감지를 사용할 수 없다.
→ 영속성 컨텍스트가 종료되었기 때문
지연 로딩
- 영속성 컨텍스트가 종료된 시점에 지연 로딩이 필요한 프록시 객체가 초기화 되지 않았다면
프록시 객체를 초기화 할 수 없으므로 예외가 발생한다.
org.hibernate.LazyInitializationException 예외 발생 !!!!
준영속 상태의 지연 로딩 문제를 해결하는 방법은 크게 2가지
- 뷰가 필요한 엔티티를 미리 로딩해두는 것
- 글로벌 페치 전략 수정 - EAGER -
사용하지 말 것
- 사용하지 않는 엔티티까지 조회하므로 성능 저하
- N + 1 문제로 인해 성능 저하
- **
JPQL + Fetch JOIN을 사용하여 미리 로딩
**해두는 것
- 무분별하게 사용하면
화면에 맞춘 리포지토리 메서드가 증가
할 수 있다.
→ 프레젠테이션 계층이 데이터 접근 계층을 침범하게 됨
트랜잭션 안에서 미리 로딩해두는 것 (강제 초기화)
강제 초기화시, 서비스 계층에서 강제 초기화하는 것은
프리젠테이션 계층의 일이 서비스 계층에 침범
하는 것
→ FACADE 계층으로 분리하여 프록시 초기화 역할 담당
- OSIV를 사용해서 엔티티를 항상 영속 상태로 유지하는 방법
</aside>
<aside>
❗
FACADE 계층 추가 (파사드 디자인 패턴)
Controller → FACADE → Service → Repository
- 프록시를 초기화 하려면, 영속성 컨텍스트가 필요하므로 FACADE에서 트랜잭션을 시작해야 한다.
[FACADE 계층의 역할과 특징]
프리젠테이션 계층과 도메인 모델 계층 간의 논리적 의존성을 분리
→ 컨트롤러에서 직접 로직을 수행하지 않고 FACADE 서비스에 위임하기 때문
- 프리젠테이션 계층에서 필요한 프록시 객체를 초기화한다.
- 서비스 계층을 호출해서 비즈니스 로직을 실행한다.
- 리포지토리를 직접 호출해서 뷰가 요구하는 엔티티를 찾는다.
</aside>
<aside>
❗
준영속 상태와 지연 로딩의 문제점
- 뷰를 개발할 때, 엔티티를 미리 초기화하는 방법은 생각보다 오류가 발생할 가능성이 높다.
→ 뷰를 개발할 때, 엔티티 클래스를 보고 개발하지, 이것이 초기화됐는지 까지 확인하지 않음
- 애플리케이션 로직과 뷰가 물리적으로는 나누어져 있지만
논리적으로는 서로 의존하는 문제가 있음
- FACADE를 사용해서 이를 해소할 수 있지만 번거로움
Ex) 엔티티를 조회할 때, 화면별로 최적화된 엔티티를 딱딱 맞아 떨어지게 초기화해서
조회하려면 FACADE 계층에 여러 종류의 조회 메서드가 필요 하게 됨
이런 경우 OSIV를 사용하는 것이 해결책이 될 수 있다.
Open Session In View란 영속성 컨텍스트가 트랜잭션이 종료된 이후에도 유지되는 것을 의미
</aside>
<aside>
❗
OSIV
- 영속성 컨텍스트를 뷰까지 열어둔다는 의미
- 뷰나 컨트롤러에서 지연 로딩을 사용할 수 있다.
초창기 OSIV - 요청당 트랜잭션
- 요청이 들어오자마자 **
서블릿 필터나 스프링 인터셉터에서 영속성 컨텍스트를 만들면서 트랜잭션을 시작
**하고 **요청이 끝날 때 트랜잭션과 영속성 컨텍스트를 함께 종료
**한다.
[문제점]
- 컨트롤러나 뷰 같은 프리젠테이션 계층이 엔티티를 변경할 수 있다는 것
- 고객에게 제공할 때만 XXX로 변경하고 싶은데, 변경 감지로 인해 flush() 될 수 있음
</aside>
<aside>
❗
스프링 OSIV - 비즈니스 계층 트랜잭션

- 스프링 프레임워크의 spring-orm.jar는 다양한 OSIV 클래스 제공
- OSIV를 서블릿 필터에서 적용할지, 스프링 인터셉터에서 적용할지에 따라
원하는 클래스를 선택해서 사용하면 된다.
스프링 OSIV 분석
- 요청이 들어오면 **
서블릿 필터나, 스프링 인터셉터에서 영속성 컨텍스트를 생성
**한다.
이때, 트랜잭션은 시작하지 않는다.
- 서비스 계층에서
트랜잭션을 시작하면 앞에 생성해둔 영속성 컨텍스트에 트랜잭션 시작
- 비즈니스 로직을 실행하고 서비스 계층이 끝나면, 트랜잭션을 커밋하면서
영속성 컨텍스트를 플러시한다.
이때, 트랜잭션만 종료하고 영속성 컨텍스트는 살려둔다.
- 이후 클라이언트의 요청이 끝날 때, 서블릿 필터나 스프링 인터셉터로 요청이 돌아오면
영속성 컨텍스트를 종료한다. 이때, 플러시를 호출하지 않고 바로 종료한다.
[기타]
- 기본적으로 “스레드당 하나의 트랜잭션”이 유지된다.
즉, 한 요청이 여러 개의 스레드를 사용하면, 각 스레드는 별도의 트랜잭션과 영속성 컨텍스트를
가지게 된다.
- 그러므로 한 요청에서 여러 개의 스레드를 사용하는 경우(비동기 실행) 문제가 발생할 수 있다.
- 각 스레드는 서로 다른 트랜잭션을 가짐 → 같은 요청이더라도 서로 다른 영속성 컨텍스트를 가짐
- Lazy Loading 문제
메인 스레드는 OSIV 덕분에 Lazy Loading이 가능하지만
새로운 스레드에서는 기존 영속성 컨텍스트를 공유하지 못하므로
LazyInitializationException 발생 가능
- 동기화 문제
한 스레드에서 엔티티를 변경했지만, 다른 스레드에서는 이를 알지 못할 수 있음
데이터 정합성 문제가 발생할 수 있음
트랜잭션 없이 읽기
영속성 컨텍스트를 통한 모든 변경은 트랜잭션 안에서 이루어져야 한다.
- 트랜잭션 없이 엔티티를 변경하고 영속성 컨텍스트를 플러시하면
javax.persistence.TransactionRequiredException
이 발생한다.
- 단순히 조회하는 것은 트랜잭션 없이 가능
지연로딩도 조회이므로 가능
</aside>
<aside>
❗
스프링 OSIV 정리
- 영속성 컨텍스트를 프레젠테이션 계층까지 유지한다.
- 프리젠테이션 계층에는 트랜잭션이 없으므로 엔티티를 수정할 수 없다.
- 프리젠테이션 계층에는 트랜잭션이 없지만 트랜잭션 없이 읽기를 사용해서
지연 로딩을 할 수 있다.
</aside>
<aside>
❗
스프링 OSIV 주의사항
- 스프링 OSIV를 사용하면 프리젠테이션 계층에서 엔티티를 수정해도 수정 내용을
데이터베이스에 반영하지 않는다.
- 하지만 한 가지 예외가 있다.
- 프리젠테이션 계층에서 **
엔티티를 수정한 직후 트랜잭션을 시작하는 서비스 계층을 호출
**하면
변경감지를 수행하여 데이터가 변경될 수 있다.

- 이를 해결하려면 **
모든 비즈니스 로직을 호출하고 나서 엔티티를 변경
**한 후 뷰를 호출하면 된다.
- 스프링 OSIV는 같은 영속성 컨텍스트를 여러 트랜잭션이 공유할 수 있으므로 이런 문제 발생
</aside>
<aside>
❗
</aside>