13장 웹 애플리케이션과 영속성 관리
트랜잭션 범위의 영속성 컨텍스트
스프링 기본 전략
스프링 컨테이너는 트랜잭션 범위의 영속성 컨텍스트 전략을 기본으로 사용한다.
트랜잭션이 시작되면 영속성 컨텍스트를 생성하고 트랜잭션이 끝나면 영속성 컨텍스트를 종료한다.
같은 트랜잭션 안에는 항상 같은 영속성 컨텍스트에 접근한다.
고로 트랜잭션이 같으면 같은 영속성 컨텍스트를 사용하고 트랜잭션이 다르면 영속성 컨텍스트도 다르다.
준영속 상태와 지연로딩
스프링, J2EE 컨테이너는 트랜잭션이 보통 서비스 계층에서 시작하고 서비스 계층이 끝나면 트랜잭션도 종료된다. 트랜잭션이 종료됨에 따라 영속성 컨텍스트도 종료되기에 조회한 엔티티는 준영속 상태가 된다.
이럴때 Controller, View 단에서 준영속 상태가된 엔티티를 사용하면 지연로딩 시 에러가 발생한다.
만약 이렇게 서비스단이 아닌 다른 단에서 서비스단이 가지는 책임을 다른 단에서 가져가 버리면 책임이 모호해지고 어디서 어떻게 변경했는지 다른 단까지 모두 찾아봐야하기에 애플리케이션 유지보수에 어려움이 더해진다.
준영속 상태 지연 로딩 해결 전략
준영속 상태의 지연 로딩 문제를 해결하는 방법은 크게 2가지가 있다.
뷰가 필요한 엔티티를 미리 로딩 해 놓는 방법
OSIV 를 사용해서 엔티티를 트랜잭션이 종료되고 나서도 영속 상태로 유지하는 방법
뷰가 필요한 엔티티를 미리 로딩 해 놓는 방법은 위치에 따라 3가지 방법이 있다.
글로벌 페치 전략 수정
JPQL 페치조인
강제로 초기화
필자는 주로 2번 을 사용하였다.
글로벌 페치 전략 수정
말 그대로 글로벌 설정을 지연 로딩이 아닌 FetchType.EAGER (즉시 수정) 으로 바꾸는 것이다.
단점
사용하지 않는 엔티티도 모두 로딩된다.
N+1 문제가 발생한다.
JPQL 페치 조인
페치 조인을 사용하면 N+1 문제를 해결할수있다. 하지만 페치 조인은 각각 메서드 마다 쿼리문을 설정하는것이기에 필요한 화면 별로 너무 많은 메서드가 생겨나 뷰와 리포지터리 간에 의존관계가 발생할수있다.
강제로 초기화
class OrderService {
@Transactional
public Order findOrder(id) {
Order order = orderRepository.findOrder(id);
order.getMember().getName(); // 프록시 객체를 강제로 초기화한다.
return order;
}
}위에 코드 처럼 영속성 컨텍스트가 살아있을때 뷰단에 필요한 엔티티를 강제로 초기화 해서 반환하는 방법이다. 이것은 계층 끼리 서로 침범하는 상황이기에 적절치 못한 방법이다.
이를 보완하기 위해 FACADE 계층을 추가하는 방법이있다.
FACADE 계층의 역할과 특징
프리젠테이션 계층과 도메인 모델 계층 간의 논리적 의존성을 분리해준다.
프리젠테이션 계층에서 필요한 프록시 객체를 초기화 한다.
서비스 계층을 호출해서 비즈니스 로직을 실행한다.
리포지터리를 직접 호출해서 뷰가 요구하는 엔티티를 찾는다.
하지만 일단 계층이 하나 더 늘어난다는 점 과 화면 별로 엔티티 마다 일일히 다 초기화를 해줘야되기에 굉장히 번거롭다.
준영속 상태와 지연 로딩의 문제점
FACADE를 이용해서 준영속 상태의 지연로딩 문제를 어느 정도 해소할 수는 있지만 상당히 번거롭다. 예를 들어 주문 엔티티와 연관된 회원 엔티티를 조회할 때 화면별로 최적화된 엔티티를 딱딱 맞아 떨어지게 초기화해서 조회하려면 FACEDE 계층에 여러 종류의 조회 메소드가 필요하다.
결국 모든 문제는 엔티티가 프리젠테이션 계층에서 준영속 상태이기 때문에 발생한다.
OSIV
Open Session In VIew 의 약자로 영속성 컨텍스트를 뷰 까지 열어준다 라는 뜻이다.

과거 :
과거에는 요청 당 트랜잭션을 생성한다. 그렇다는건 요청이 들어오자마자 서비스단 부터가 아닌 필터 인터셉터 부분부터 트랜잭션, 영속성 컨텍스트가 시작되는 것이다. 전 과정에 걸쳐 트랜잭션이 이루어지기에 뷰, 컨트롤러 등 서비스단을 제외한 부분에서도 엔티티 수정 가능성이 존재한다는게 문제점 이다.
이를 방지하기 위해 엔티티를 읽기 전용 인터페이스로 제공, 엔티티 래핑, DTO만 반환 등 방법이 있지만 해당 코드를 또 작성해야되는 부담이 존재했다.
현재 (스프링) :
클라이언트 요청이 들어오면 영속성컨텍스트를 생성하지만 트랜잭션을 시작하지는 않는다.
서비스 계층에서 @Transcational 로 트랜잭션이 시작할때 미리 생성해둔 영속성 컨텍스트를 찾아와 트랜잭션을 시작한다.
그리고 서비스 계층이 끝나면 커밋 한 뒤 영속성 컨텍스트를 플러시, 하지만 트랜잭션만 종료되고 영속성 컨텍스트는 종료하지않고 서블릿 필터, 스프링 인터셉터로 요청이 오면 그때 종료한다.
트랜잭션 없이 읽기
OSIV 는 트랜잭션은 서비스 계층에서 종료되지만 영속성 컨텍스트는 프레젠테이션 계층 까지 유지 되기때문에 트랜잭션 없이 읽기가 가능하다.
OSIV 정리
스프링 OSIV 특징
엔티티의 영속성이 트랜잭션이 종료되도 유지, 종료 요청이 오면 그때 영속성 종료
엔티티 수정은 트랜잭션이 있는 비즈니스 계층에서만 가능하고 뷰 에서는 지연로딩, 조회만 가능
스프링 OSIV 단점
OSIV 사용 시 같은 영속성 컨텍스트를 여러 트랜잭션이 공유할수있다.
프레젠테이션 계층에서 지연로딩에 의한 SQL 실행, 고로 성능 튜닝시 확인해야될 부분 넓어짐
오랜 시간 DB 커넥션 리소스를 사용하기에 실시간 트래픽이 중요한 애플리케이션에는 부적합 할수있음
프레젠테이션 계층에서 엔티티 수정 후 비즈니스 로직을 실행하면 엔티티가 수정될수있음
권장하는 사용 방법
고객 서비스 기반의 트래픽이 많은 실시간 API : OSIV를 끈다.
ADMIN 처럼 커넥션을 많이 사용하지 않는 곳 : OSIV를 켠다.
Last updated