JPA 기본 - OSIV

1. OSIV란?


OSIV(Open Session In View)는 영속성 컨텍스트를 뷰까지 열어준다는 뜻입니다. 영속성 컨텍스트가 살아있으면 엔티티는 영속 상태로 유지됩니다. 따라서 뷰에서도 지연 로딩을 할 수 있습니다. 참고로 OSIV는 하이버네이트에서 사용하는 용어이고, JPA에서는 OEIV(Open EntityManager In View)라고 하지만 관계상 모두 OSIV라고 부릅니다.



2. 과거 OSIV: 요청 당 트랜잭션


그림1

OSIV의 핵심은 뷰에서도 지연로딩이 가능하도록 하는 것입니다. 가장 단순한 구현 방법은 클라이언트의 요청이 들어오자마자 서블릿 필터나 스프링 인터셉터에서 트랜잭션을 시작하고 요청이 끝날 때 트랜잭션도 끝내는 것입니다. 이것을 요청 당 트랜잭션 방식의 OSIV라고 합니다. 이렇게 하면 영속성 컨텍스트가 처음부터 끝까지 살아있으므로 조회한 엔티티도 영속 상태를 유지합니다. 따라서 뷰에서도 지연 로딩을 할 수 있으므로 엔티티를 미리 초기화할 필요가 없습니다. 그리고 뷰에서도 지연 로딩을 할 수 있게 되면서 FACADE 계층 없이도 뷰에서 독립적인 서비스 계층을 유지할 수 있습니다.

요청 당 트랜잭션 방식의 문제점

컨트롤러나 뷰 같은 프레젠테이션 계층이 엔티티를 변경할 수 있다는 점이 가장 큰 문제입니다. 해결책은 다음과 같습니다.

  • 엔티티를 읽기 전용 인터페이스로 제공
  • 엔티티 레핑
  • DTO만 반환

세 가지 모두 다 코드량이 상당히 증가한다는 단점이 있기 때문에 차라리 프레젠테이션 계층에서 엔티티를 수정하면 안 된다고 개발자들끼리 합의하는 것이 더 실용적일 수 있습니다. 이러한 문제들 때문에 최근에는 요청 당 트랜잭션 방식의 OSIV 방식을 사용하지 않고, 비즈니스 계층에서만 트랜잭션을 유지하는 방식의 OSIV를 사용합니다. 스프링 프레임워크가 제공하는 OSIV가 바로 이 방식입니다.



3. 스프링 OSIV: 비즈니스 계층 트랜잭션


제공하는 OSIV 클래스

스프링에서는 다음과 같은 OSIV 클래스를 제공합니다. OSIV를 서블릿 필터에 적용할지, 스프링 인터셉터에서 적용할지에 따라 원하는 클래스를 선택해서 사용하면 됩니다.

  • 하이버네이트 OSIV 서블릿 필터 : org.springframework.com.hibernate4.support.OpenSessionInViewInterceptor
  • 하이버네이트 OSIV 스프링 인터셉터 : org.springframework.orm.hibernate4.support.OpenSessionInViewInterceptor
  • JPA OEIV 서블릿 필터 : org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
  • JPA OEIV 스프링 인터셉터 : org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor

예를 들어 JPA를 사용하면서 서블릿 필터에 OSIV를 적용하려면 OpenEntityManagerInViewFilter를 서블릿 필터에 등록하면 됩니다.

동작 원리

그림2

  1. 클라이언트의 요청이 오면 서블릿 필터나 스프링 인터셉터에서 영속성 컨텍스트를 생성합니다. 트랜잭션을 시작하지는 않습니다.
  2. 서비스 계층에서 @Transational로 트랜잭션이 시작하면 앞에 생성해둔 영속성 컨텍스트를 찾아와서 트랜잭션을 시작합니다.
  3. 비즈니스 로직을 실행하고 서비스 계층이 끝나면 트랜잭션을 커밋하면서 영속성 컨텍스트를 플러시합니다.
  4. 트랜잭션을 종료하고 영속성 컨텍스트는 살려둡니다.
  5. 서블릿 필터나 스프링 인터셉터로 요청이 돌아오면(클라이언트 요청이 끝나면) 영속성 컨텍스트를 종료합니다. 이때는 플러시를 호출하지 않고 바로 종료합니다.



cf) 영속성 컨텍스트
영속성 컨텐스트란 엔티티를 영구 저장하는 환경 이라는 뜻입니다. 애플리케이션과 데이터베이스 사이에서 객체를 보관하는 가상의 데이터베이스 같은 역할을 합니다. 영속성 컨텍스트는 엔티티 매니저를 생성할 때 하나가 만들어 지고, 엔티티 매니저를 통해서 영속성 컨텍스트에 접근하고 관리할 수 있다.

트랜잭션 없이 읽기(Nontransactional reads)

영속성 컨텍스트를 통한 모든 변경은 트랜잭션 안에서 이루어져야 합니다. 만약 트랜잭션 없이 엔티티를 변경하고 영속성 컨텍스트를 플러시하면 javax.persistence.TransactionRequiredException 예외가 발생합니다. 엔티티를 변경하지 않고 단순히 조회만 할 때는 트랜잭션이 없어도 되는데 이것을 트랜잭션 없이 읽기라고 합니다. 프록시를 초기화하는 지연 로딩도 조회 기능이므로 트랜잭션 없이 읽기가 가능합니다. 정리하면 다음과 같습니다.

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


기존 OSIV와의 차이점

스프링이 제공하는 OSIV는 프레젠테이션 계층에서는 트랜잭션이 없으므로 엔티티를 수정할 수 없게하여 기존 OSIV의 단점을 보완했습니다. 그리고 트랜잭션 없이 읽기를 사용해서 프레젠테이션 계층에서 지연 로딩 기능을 사용할 수 있게 만들었습니다.


정리

  • 영속성 컨텍스트를 프레젠테이션 계층까지 유지합니다.
  • 프레젠테이션 계층에는 트랜잭션이 없으므로 엔티티를 수정할 수 없습니다.
  • 프레젠테이션 계층에는 트랜잭션이 없지만 트랜잭션 없이 읽기를 사용해서 지연 로딩이 가능합니다.



4. 스프링 OSIV 주의사항


스프링 OSIV를 사용하면 프레젠테이션 계층에서 엔티티를 수정해도 수정 내용을 데이터베이스에 반영하지 않습니다. 하지만 여기 한 가지 예외가 있습니다. 프레젠테이션 계층에서 엔티티를 수정한 직후에 트랜잭션을 시작하는 서비스 계층을 호출하면 문제가 발생합니다.
그림3

  1. 컨트롤러에서 회원 엔티티를 조회하고 이름을 수정합니다.
  2. biz() 메서드를 실행해서 트랜잭션이 있는 비즈니스 로직을 실행합니다.
  3. 트랜잭션 AOP가 동작하면서 영속성 컨텍스트에 트랜잭션을 시작합니다. 그리고 biz 메서드를 실행합니다.
  4. biz() 메서드가 끝나면 트랜잭션 AOP는 트랜잭션을 커밋하고 영속성 컨텍스트를 플러시 합니다. 이때 변경 감지가 동작하면서 회원 엔티티의 수정 사항을 데이터베이스에 반영합니다.

컨트롤러에서 엔티티를 수정하고 즉시 뷰를 호출한 것이 아니라 트랜잭션이 동작하는 비즈니스 로직을 호출했으므로 이런 문제가 발생합니다. 문제를 해결하는 단순한 방법은 트랜잭션이 있는 비즈니스 로직을 모두 호출하고 나서 엔티티를 변경하면 됩니다. 스프링 OSIV는 같은 영속성 컨텍스트를 여러 트랜잭션이 공유할 수 있으므로 위의 문제가 발생합니다.
OSIV를 사용하지 않는 트랜잭션 범위의 영속성 컨텍스트 전략은 트랜잭션의 생명주기와 영속성 컨텍스트의 생명주기가 같으므로 해당 문제가 발생하지 않습니다.



5. 정리


스프링 OSIV 특징

  • 클라이언트 요청이 들어올 때 영속성 컨텍스트틀 생성해서 요청이 끝날 때까지 같은 영속성 컨텍스트틀 유지하기 때문에 한 번 조회한 엔티티는 요청이 끝날 때까지 영속 상태를 유지
  • 엔티티 수정은 트랜잭션이 있는 계층에서만 동작하며, 트랜잭션이 없는 프레젠테이션 계층은 지연 로딩을 포함해서 조회만 가능


스프링 OSIV 단점

  • 같은 영속성 컨택스트를 여러 트랜잭션이 공유할 수 있다는 점을 주의
  • 프레젠테이션 계층에서 엔티티를 수정하고나서 비즈니스 로직을 수행하면 엔티티가 수정된다는 점
  • 프레젠테이션 계층에서 지연 로딩에 의한 SQL이 실행되기 때문에 성능 튜닝 시 확인해야할 부분이 넓음


OSIV가 만능은 아니다.

  • OSIV를 사용시 화면을 출력할 때 엔티티를 유지하면서 객체 그래프를 마음껏 탐색할 수 있지만 복잡환 화면을 구성할 때는 효과적이지 않은 경우가 많음
  • 복잡한 화면의 경우 엔티티를 조회하기보다는 처음부터 구상하기 위한 JPQL을 작성하여 DTO로 조회하는 것이 효과적



본 포스팅은 인프런 김영한님의 ‘자바 ORM 표준 JPA 프로그래밍 - 기본편’ 강의를 듣고 정리한 내용을 바탕으로 복습을 위해 작성하였습니다. [강의 링크]


© 2021. By Backtony