[JPA] 웹 애플리케이션과 영속성 관리

들어가며

해당 글은 자바 ORM 표준 프로그래밍을 정리한 글입니다.

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

  • 보통 Service 계층에서 @Transaction 어노테이션을 선언해서 트랜잭션을 시작한다.
  • 스프링 트랜잭션 AOP는 대상 메소드 호출하기 직전에 트랜잭션을 시작하고, 대상 메소드가 정상 종료되면 트랜잭션을 커밋하면서 종료한다.
  • 트랜잭션을 커밋하면 JPA는 먼저 영속성 컨텍스트를 플러시해서 변경 내용을 데이터베이스에 반영한 후에 데이터베이스 트랜잭션을 커밋한다.
  • 트랜잭션이 같으면 같은 영속성 컨텍스트를 사용한다.
  • 같은 엔티티 매니저를 사용하더라도 트랜잭션이 다르면 다른 영속성 컨텍스트를 사용한다.

준영속 상태와 지연 로딩

  • 스프링이나 J2EE 컨테이너는 트랜잭션 범위의 영속성 컨텍스트 전략을 기본으로 사용하므로 서비스와 레포지토리 계층에서는 영속성 컨텍스트에 관리되면서 영속 상태를 유지하지만 컨트롤러나 뷰 같은 프리젠테이션 계층에서는 준영속 상태가 된다. 따라서 변경감지와 지연 로딩이 동작하지 않는다.

준영속 상태의 지연 로딩 문제 해결 방법

  • 뷰가 필요한 엔티티를 미리 로딩해두는 방법
    • 글로벌 페치 전략 수정
    • JPQL 페치 조인
    • 강제로 초기화
  • OSIV를 사용해서 엔티티를 항상 영속 상태로 유지하는 방법

글로벌 페치 전략 수정

  • fetch type을 LAZY → EAGER로 변경해서 글로벌 페치 전략을 수정한다.

글로벌 페치 전략에 즉시 로딩 사용시 단점

  • 사용하지 않는 엔티티를 로딩한다.
  • N+1 문제가 발생한다.
    • JPQL을 이용해서 list 단위로 조회하는 경우 글로벌 페치전략을 참고하지 않고 오직 JPQL 자체만 사용하기에 1번의 전체 조회 후 조회된 N개에 대해서 select하는 N번의 SQL이 실행 된다.

JPQL 페치 조인

  • 페치 조인은 조인 명령어 마지막에 fetch를 넣어 주면 SQL JOIN을 사용해서 페치 조인 대상까지 함께 조회하기에 N+1 문제가 발생하지 않는다.

JPQL 페치 조인의 단점

  • 필요한 데이터에 맞게 페치 조인 메소드를 만들던지 전체 페치조인하는 메소드를 사용해서 다른 필드를 사용하지 않는 방법을 사용해야 하기에 사용하는 곳에서 타협점을 찾아야 한다.

강제로 초기화

  • 프록시 객체로 조회한 엔티티를 서비스 계층에서 초기화하여 프레젠테이션 계층에서 초기화된 엔티티를 넘겨주는 방식
  • FACADE 계층
    • 프록시 계층에 데이터를 초기화 하는 계층으로 프레젠테이션과 서비스 계층의 중간 단계라고 생각하면 된다.
    • 의존성을 분리할 수 있지만 더 많은 코드를 작성해야 한다.

OSIV

  • Open Session In View는 영속성 컨텍스트를 뷰까지 열어둔다는 뜻이다. JPA에서는 OEIV(Open EntityManager In View)라 한다.

과거 OSIV: 요청 당 트랜잭션

  • 클라이언트 요청이 들어오자마자 서블릿 필터나 스프링 인터셉터에서 트랜잭션을 시작하고 요청이 끝날 때 트랜잭션도 끝내는 방식

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

  • 컨트롤러나 뷰 같은 프리젠테이션 계층이 엔티티를 변경할 수 있다는 점이다.

프리젠테이션 계층에서 엔티티를 수정하지 못하게 막는 방법

  • 엔티티를 읽기 전용 인터페이스로 제공
    • 인터페이스의 View를 상속해서 getter만 갖고 있는 인터페이스를 프레젠테이션에 넘겨주는 방식
  • 엔티티 레핑
    • 엔티티 객체를 멤버 변수로 갖고 있는 Wrapper 객체를 만들어 읽기 전용 메소드만 제공
  • DTO만 반환(가장 많이 사용)
    • Data Transfer Object를 만들어서 프레젠테이션 계층에 전달하는 방식
    • 필드 값 복사 과정이 필요
  • 엔티티를 수정 하지 못하게 막는 방법은 코드량이 상당히 증가하는 단점이 있다.

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

  • 스프링 프레임워크가 제공하는 OSIV, 필터, 인터셉터에서 적용할지에 따라 원하는 클래스를 제공
  • 영속성 컨텍스트의 생존 범위는 요청이 들어올 때 부터 요청이 종료될 때까지 생존하나 수정이 가능한 시점은 서비스 계층, 트랜잭션 범위에서만 엔티티 수정가능 하다
  • 엔티티를 변경하지 않고 단순히 조회만 할 때는 트랜잭션이 없어도 되는데 이것을 트랜잭션 없이 읽기(Nontransactional reads)라 한다.

특징

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

스프링 OSIV 주의사항

  • 프레젠테이션 계층에서 서비스 계층 호출 후 엔티티를 수정하고 다시 서비스 계층을 호출하게 되면 영속 상태의 객체들이 플러시 되어 데이터가 반영되는 문제가 있다.
  • 스프링 OSIV는 같은 영속성 컨텍스트를 여러 트랜잭션이 공유 할 수 있으므로 주의가 필요

너무 엄격한 계층

  • OSIV를 사용하게 되면 영속성 컨텍스트를 공유하기에 컨트롤러에서 레포지토리를 직접 호출해도 문제가 되지 않는다. 따라서 OSIV를 사용하면 좀 더 유연하고 실용적인 관점으로 접근하는 것도 좋은 방법이라 생각한다.


Leave a comment