[JPA] OSIV(Open Session In View)

JPA OSIV

OSIV

영속성 컨텍스트를 뷰까지 열어둔다는 뜻

영속성 컨텍스트가 살아있으면 엔티티는 영속 상태로 유지된다. 따라서 뷰에서도 지연 로딩 사용 가능.
OSIV는 하이버네이트 상에서 사용하는 용어. JPA에서는 OEIV(Open EntityManager In View)라고 함. 관례상 통칭 OSIV라고 함.
스프링이나 J2EE 컨테이너 환경에서 JPA를 사용하면 트랜잭션 범위의 영속성 컨텍스트 전략이 적용됨. 이 전략은 트랜잭션 범위와 영속성 컨텍스트의 생존 범위 동일. 같은 트랜잭션 안에서는 항상 같은 영속성 컨텍스트에 접근. 트랜잭션이라는 단위로 영속성 컨텍스트를 관리하므로 트랜잭션을 커밋하거나 롤백할 때 문제가 없다. 이 전략의 단점은 프레젠테이션 계층에서 엔티티가 준영속 상태가 되므로 지연 로딩 사용 불가. OSIV를 사용하면 이러한 문제를 해결할 수 있음.

과거 OSIV : 요청 당 트랜잭션

OSIV 핵심 : 뷰에서도 지연 로딩이 가능하도록 하는 것
가장 단순한 구현 방법 : 클라이언트의 요청이 들어오자마자 서블릿 필터나 스프링 인터셉터에서 트랜잭션을 시작하고 요청이 끝날 때 트랜잭션도 끝내는 것. → 요청 당 트랜잭션 방식의 OSIV

osiv

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

컨트롤러나 뷰 같은 프레젠테이션 계층이 엔티티를 변경할 수 있다. 서비스 계층 처럼 비즈니스 로직을 실행하는 곳에서 데이터를 변경하는 것은 당연하지만 프레젠테이션 계층에서 데이터를 잠시 변경했다고 실제 데이터베이스까지 변경 내용이 반영되면 애플리케이션의 유지보수가 어려워짐.

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

1. 엔티티를 읽기 전용 인터페이스로 제공

엔티티를 직접 노출하는 대신 읽기 전용 메소드만 제공하는 인터페이스를 프레젠테이션 계층에 제공하는 방법.
프레젠테이션 계층은 읽기 전용 메소드만 있는 인터페이스를 사용하므로 엔티티를 수정할 수 없다.

interface MemberView {
	public String getName();
}

@Entity
class Member implements MemberView {
	// ..생략..
}

class MemberService {
	public MemberView getMember(String id) {
		return memberRepository.findById(id);
	}
}

2. 엔티티 매핑

엔티티의 읽기 전용 메소드만 가지고 있는 엔티티를 감싼 객체를 만들고 이것을 프레젠테이션 계층에 반환하는 방법.

class MemberWrapper {
	private Member member;

	public MemberWrapper (Member member) {
		this.member = member;
	}

	// 읽기 전용 메소드만 제공
	public String getName() {
		return member.getName();
	}
}

3. DTO만 반환

가장 전통적인 방법. 프레젠테이션 계층에 엔티티 대신에 단순히 데이터만 전달하는 객체인 DTO를 생성해서 반환. 하지만 이 방법은 OSIV를 사용하는 장점을 살릴 수 없고 엔티티를 거의 복사한 듯한 DTO 클래스도 하나 더 생성해야 함.

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

spring-orm.jar는 다양한 OSIV 클래스 제공. OSIV를 서블릿 필터에서 적용할지 스프링 인터셉터에서 적용할지에 따라 원하는 클래스를 선택해서 사용.

  • 하이버네이트 OSIV 서블릿 필터 : OpenSessionInViewFilter
  • 하이버네이트 OSIV 스프링 인터셉터 : OpenSessionInViewInterceptor
  • JPA OEIV 서블릿 필터 : OpenEntityManagerInViewFilter
  • JPA OEIV 스프링 인터셉터 : OpenEntityManagerInViewInterceptor

JPA를 사용하면서 서블릿 필터에 OSIV를 적용하려면 OpenEntityManagerInViewFilter를 서블릿 필터에 등록하면 되고, 스프링 인터셉터에 OSIV를 적용하려면 OpenEntityManagerInViewInterceptor를 스프링 인터셉터에 등록하면 됨.

스프링 OSIV 분석

비즈니스 계층에서 트랜잭션을 사용하는 OSIV
OSIV를 사용하지만 트랜잭션은 비즈니스 계층에서만 사용

spring osiv

  1. 클라이언트의 요청이 들어오면 서블릿 필터나 스프링 인터셉터에서 영속성 컨텍스트를 생성. 단, 이 때 트랜잭션은 시작하지 않음.
  2. 서비스 계층에서 @Transactional 로 트랜잭션을 시작할 때 1번에서 미리 생성해둔 영속성 컨텍스트를 찾아와서 트랜잭션을 시작
  3. 서비스 계층이 끝나면 트랜잭션을 커밋하고 영속성 컨텍스트를 플러시한다. 이때 트랜잭션은 끝내지만 영속성 컨텍스트는 종료하지 않음.
  4. 컨트롤러와 뷰까지 영속성 컨텍스트가 유지되므로 조회한 엔티티는 영속 상태를 유지
  5. 서블릿 필터나 스프링 인터셉터로 요청이 돌아오면 영속성 컨텍스트를 종료. 이때 플러시를 호출하지 않고 바로 종료.

스프링의 비즈니스 계층 트랜잭션 특징

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

⭐️참고
영속성 컨텍스트를 통한 모든 변경은 트랜잭션 안에서 이루어져야 한다. 만약 트랜잭션 없이 엔티티를 변경하고 영속성 컨텍스트를 플러시하면 javax.persistence.TransactionRequiredException 예외 발생.

스프링 OSIV 주의사항

프레젠테이션 계층에서 엔티티를 수정한 직후에 트랜잭션을 시작하는 서비스 계층을 호출하면 문제가 발생.

class memberController {
	public String viewMember(Long id) {
		Member member = memberService.getMember(id);
		member.setName("XXX"); // 보안상의 이유로 고객 이름을 XXX로 변경

		memberservice.biz(); // 비즈니스 로직
		return "view";
	}
}

class MemberService {
	@Transactional
	public void biz() {
		// ... 비즈니스 로직 실행
	}
}

spring osiv watch out

컨트롤러에서 엔티티를 수정하고 즉시 뷰를 호출하는 것이 아닌, 트랜잭션이 동작하는 비즈니스 로직을 호출할 경우 문제가 발생한다. 해결 방법은 트랜잭션이 있는 비즈니스 로직을 모두 호출하고 나서 엔티티를 변경.

정리

Spring OSIV 특징

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

Spring OSIV 단점

  • 같은 영속성 컨텍스트를 여러 트랜잭션이 공유할 수 있다는 점을 주의해야 함. 특히 트랜잭션 롤백 시 주의해야 함.
  • 프레젠테이션 계층에서 엔티티를 수정하고나서 비즈니스 로직을 수행하면 엔티티가 수정될 수 있음
  • 프레젠테이션 계층에서 지연 로딩에 의한 SQL이 실행됨. 성태 튜닝 시 확인해야 할 부분이 넓음

OSIV vs FACADE vs DTO

OSIV 대안으로 FACADE 계층 혹은 조금 변경해서 사용. 이러한 대안은 결국 준영속 상태가 되기 전에 프록시를 초기화해야함. 다른 방법은 엔티티를 직접 노출하지 않는 DTO 객체 활용. 어떠한 방법이든 OSIV에 비해 코드를 추가적으로 작성해야 하는 단점이 있음.

OSIV는 만능이 아니다

복잡한 화면을 구성할 때는 OSIV를 통해 엔티티를 유지하는 것이 효과적이지 않은 경우가 많음. JPQL을 활용해 DTO 객체를 반환하는 것이 더 나은 해결책일 수 있음.

OSIV는 같은 JVM을 벗어난 원격 상황에서는 사용 불가

외부 API는 엔티티를 직접 노출하기보다는 엔티티를 변경해도 완충 역할을 할 수 있는 DTO로 변환해서 노출하는 것이 안전. 내부 API는 엔티티를 변경해도 클라이언트와 서버를 동시에 수정할 수 있어서 실용적인 관점에서 엔티티를 직접 노출하는 방법도 괜찮음.

References

  • 김영한님의 자바 ORM 표준 JPA 프로그래밍 : 13.3 OSIV