Home 자바 ORM 표준 JPA 프로그래밍 - 웹 애플리케이션과 영속성 관리
Post
Cancel

자바 ORM 표준 JPA 프로그래밍 - 웹 애플리케이션과 영속성 관리

Table of Contents

  1. 트랜잭션 범위의 영속성 컨텍스트
    1. 스프링 컨테이너의 기본 전략
      1. 트랜잭션이 같으면 같은 영속성 컨텍스트를 사용한다.
      2. 트랜잭션이 다르면 다른 영속성 컨텍스트를 사용한다.
  2. 준영속 상태와 지연 로딩
    1. 글로벌 페치 전략 수정
      1. 단점
    2. JPQL 페치 조인
      1. 단점
    3. 강제로 초기화
    4. FACADE 계층
      1. FACADE 계층의 역할과 특징
  3. OSIV
    1. 과거 OSIV: 요청 당 트랜잭션
      1. 문제점
    2. 스프링 OSIV: 비즈니스 계층 트랜잭션
      1. 스프링 프레임워크가 제공하는 OSIV 라이브러리
      2. 스프링 OSIV 분석
      3. 트랜잭션 없이 읽기
    3. 스프링 OSIV 주의사항
    4. OSIV 정리
      1. 스프링 OSIV의 특징
      2. 스프링 OSIV의 단점
      3. OSIV vs FACADE vs DTO
      4. OSIV를 사용하는 방법이 만능은 아니다
      5. OSIV는 같은 JVM을 벗어난 원격 상황에서는 사용할 수 없다.

트랜잭션 범위의 영속성 컨텍스트

순수하게 J2EE 환경에서 JPA를 사용하면 개발자가 직접 엔티티 매니저를 생성하고 트랜잭션도 관리해야 한다.
하지만 스프링이나 J2EE 컨테이너 환경에서 JPA를 사용하면 컨테이너가 제공하는 전략을 따라야 한다.

스프링 컨테이너의 기본 전략

스프링 컨테이너는 트랜잭션 범위의 영속성 컨텍스트 전략을 기본으로 사용한다.
이 전략은 이름 그대로 트랜잭션의 범위와 영속성 컨텍스트의 생존 범위가 같다는 뜻이다.

img

PersistenceContext 어노테이션을 사용하면 스프링 컨테이너가 엔티티 매니저를 주입해준다.

1
2
    @PersistenceContext
    EntityManager em;

트랜잭션이 같으면 같은 영속성 컨텍스트를 사용한다.

트랜잭션 범위의 영속성 컨텍스트 전략은 다양한 위치에서 엔티티 매니저를 주입받아 사용해도 트랜잭션이 같으면 항상 같은 영속성 컨텍스트를 사용한다.

img

트랜잭션이 다르면 다른 영속성 컨텍스트를 사용한다.

같은 엔티티 매니저를 사용해도 트랜잭션에 따라 접근하는 영속성 컨텍스트가 다르다.

img

준영속 상태와 지연 로딩

스프링이나 J2EE 컨테이너는 트랜잭션 범위의 영속성 컨텍스트 전략을 기본으로 사용한다.
지연 로딩의 경우, 영속성 상태에서 동작한다. 하지만 준영속 상태에서는 변경 감지와 지연로딩이 동작하지 않는다.

준영속 상태의 지연 로딩 문제를 해결하는 방법은 크게 2가지다.

  1. 뷰가 필요한 엔티티를 미리 로딩해두는 방법
  2. OSIV를 사용해서 엔티티를 항상 영속 상태로 유지하는 방법

미리 로딩해두는 방법은 어디서 미리 로딩하느냐에 따라 3가지 방법이 있다.

  1. 글로벌 페치 전략 수정
  2. JPQL 페치 조인
  3. 강제로 초기화

글로벌 페치 전략 수정

1
2
    @ManyToOne(fetch = FetchType.EAGER)
    private Member member;

단점

  1. 사용하지 않는 엔티티를 로딩한다.
  2. N+1 문제가 발생한다.

JPQL 페치 조인

페치 조인은 N+1 문제를 해결하면서 화면에 필요한 엔티티를 미리 로딩하는 현실적인 방법이다.

1
2
3
4
5
6
7
    select o
    from Order o
    join fetch o.member
    
    select o.*, m.*
    from Order o
    join Member m on o.MEMBER_ID=m.MEMBER_ID

단점

무분별하게 사용하면 화면에 맞춘 리포지토리 메소드가 증가할 수 있다. 결국 프리젠테이션 계층이 알게 모르게 데이터 접근 계층을 침범하는 것 이다.
메소드를 각각 만들면 최적화는 할 수 있지만 뷰와 리포지토리 간에 논리적인 의존관계가 발생한다.
결국 적절한 선에서 타협점을 찾는 것이 합리적

강제로 초기화

하이버네이트를 사용하면 initialize() 메소드를 사용해서 프록시를 강제로 초기화할 수 있다.

1
    org.hibernate.Hibernate.initialize(order.getMember()); // 프록시 초기화

JPA 표준에서는 프록시 초기화 메소드가 없다. JPA 표준은 단지 초기화 여부만 확인할 수 있다.

1
2
    PersistenceUnitUtil = persistenceUnitUtil = em.getEntityManagerFactory().getPersistenceUnitUtil();
    boolean isLoaded = persistenceUnitUtil.isLoaded(order.getMember());

프록시를 초기화하는 역활을 서비스 계층이 담당하면 뷰가 필요한 엔티티에 따라 서비스 계층의 로직을 변경해야 한다.
프리젠테이션 계층이 서비스 계층을 침범하는 상황이다.

FACADE 계층

프록시를 초기화하려면 영속성 컨텍스트가 필요하므로 FACADE에서 트랜잭션을 시작해야 한다.

FACADE 계층의 역할과 특징

  • 프리젠테이션 계층과 도메인 모델 계층 간의 논리적 의존성을 분리해준다.
  • 프리젠테이션 계층에서 필요한 프록시 객체를 초기화한다.
  • 서비스 계층을 호출해서 비즈니스 로직을 실행한다.
  • 리포지토리를 직접 호출해서 뷰가 요구하는 엔티티를 찾는다.

OSIV

Open Session In View의 약자로 영속성 컨텍스트를 뷰까지 열어둔다는 뜻이다.

과거 OSIV: 요청 당 트랜잭션

요청이 들어오자마자 서블릿 필터나 스프링 인터셉터에서 영속성 컨텍스트를 만들면서 트랜잭션을 시작하고 요청이 끝날 때 트랜잭션과 영속성 컨텍스트를 함께 종료한다.

문제점

프리젠테이션 계층에서 데이터를 변경했다고 실제 데이터베이스까지 변경 내용이 반영되면 애플리케이션을 유지보수하기 상당히 힘들어진다.

프리젠테이션 계층에서 엔티티를 수정하지 못하게 막는 방법은 다음 3가지가 있다.
이러한 방법 모두 코드량이 상당히 증가한다는 단점이 있다.

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

    프리젠테이션 계층에 읽기 전용 메소드만 제공하는 인터페이스를 프리젠테이션 계층에 제공하는 방법

  2. 엔티티 레핑

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

  3. DTO만 반환

    프리젠테이션 계층에 엔티티 대신에 단순한 데이터만 전달하는 객체인 DTO를 생성해서 반환하는 방법

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

스프링 프레임워크가 제공하는 OSIV 라이브러리

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

스프링 OSIV 분석

서블릿 필터나, 스프링 인터셉터에서 영속성 컨텍스트를 생성한다. 하지만 트랜잭션은 시작하지 않는다.
서비스 계층에서 @Transactional로 트랜잭션을 시작할때 미리 생성해둔 영속성 컨텍스트를 찾아와서 트랜잭션을 시작한다.
서비스 계층이 끝나면 트랜잭션을 커밋하고 영속성 컨텍스트를 플러시 한다.
이후 서블릿 필터나 스프링 인터셉터로 요청이 돌아오면 플러시를 호출하지 않고 종료한다.

img

트랜잭션 없이 읽기

영속성 컨테스트를 통한 모든 변경은 트랜잭션 안에서 이루어져야 한다. 만약 그렇지 않다면, TransactionRequiredException이 발생

스프링이 제공하는 OSIV를 사용하면 프리젠테이션 계층에서는 트랜잭션 없으므로 엔티티를 수정할 수 없다.
따라서 프리젠테이션 계층에서 엔티티를 수정할 수 있는 기존 OSIV의 단점을 보완했다.

스프링 OSIV 주의사항

프리젠테이션 계층에서 엔티티를 수정한 직후에 트랜잭션을 시작하는 서비스 계층을 호출하면 문제가 발생
따라서 트랜잭션이 있는 비즈니스 로직을 모두 호출하고 엔티티를 변경하면 된다.

OSIV 정리

스프링 OSIV의 특징

  • OSIV는 클라이언트의 요청이 들어올 때 영속성 컨텍스트를 생성해서 요청이 끝날 때까지 같은 영속성 컨텍스트를 유지한다.
  • 엔티티 수정은 트랜잭션이 있는 계층에서만 동작한다. 그 외 계층에서는 조회만 지원

스프링 OSIV의 단점

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

OSIV vs FACADE vs DTO

결국에 준영속 상태가 되기 전에 프록시를 초기화하는 방법이다.
OSIV와 비교해서 다른 것들은 코드를 많이 작성해야 한다.

OSIV를 사용하는 방법이 만능은 아니다

OSIV를 사용하면 화면에 출력할 때 엔티티를 유지하면서 객체 그래프를 마음껏 탐색할 수 있다.
하지만 복잡한 화면을 구성할 때는 이 방법이 효과적이지 않은 경우가 많다.
이때는 엔티티를 직접 조회하기보다는 JPQL로 필요한 데이터들만 조회해서 DTO로 반환하는 것이 더 나은 해결책이 될수 있다.

OSIV는 같은 JVM을 벗어난 원격 상황에서는 사용할 수 없다.

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

This post is licensed under CC BY 4.0 by the author.

자바 ORM 표준 JPA 프로그래밍 - 스프링 데이터 JPA

자바 ORM 표준 JPA 프로그래밍 - 컬렉션과 부가 기능