Chap 13. 웹 애플리케이션과 영속성 관리
이 글은 김영한님의 JPA 프로그래밍을 공부한 흔적입니다.
스프링이나 J2EE 컨테이너 환경에서 JPA를 사용하면 컨테이너가 트랜잭션과 영속성 컨텍스트를 관리해주므로 애플리케이션을 손쉽게 개발할 수 있다.
JPA의 내부 동작 방식을 이해하지 못하면 문제가 발생했을 때 해결하기 쉽지 않다.
Why?
트랜잭션 범위의 영속성 컨텍스트
순수하게 J2SE 환경에서 JPA를 사용하면 개발자가 직접 엔티티 매니저를 생성하고 트랜잭션도 관리해야 한다. 하지만 스프링이나 J2EE 컨테이너 환경에서 JPA를 사용하면 컨테이너가 제공하는 전략에 따라야 한다.
? 컨테이너가 제공하는 전략은 무엇일까?
스프링 컨테이너의 기본 전략
스프링 컨테이너는 트랜잭션 범위의 영속성 컨텍스트 전략을 기본으로 사용한다.
트랜잭션 범위의 영속성 컨텍스트 전략이란?
트랜잭션의 범위와 영속성 컨텍스트의 생존 범위가 같다는 뜻.
트랜잭션을 시작할 때 영속성 컨텍스트를 생성하고 트랜잭션이 끝날 때 영속성 컨텍스트를 종료한다.
트랜잭션이 같으면 같은 영속성 컨텍스트를 사용한다.
다양한 위치에서 엔티티 매니저를 주입받아 사용해도 트랜잭션이 같으면 항상 같은 영속성 컨텍스트를 사용한다.
트랜잭션이 다르면 다른 영속성 컨텍스트를 사용한다.
여러 스레드에서 동시에 요청이 와서 같은 엔티티 매니저를 사용해도 트랜잭션에 따라 접근하는 영속성 컨텍스트가 다르다.
즉 스프링 컨테이너는 스레드마다 각각 다른 트랜잭션을 할당한다. 따라서 같은 엔티티 매니저를 호출해도 접근하는 영속성 컨텍스트가 다르므로 멀티스레드 상황에 안전하다.
이유 :
트랜잭션과 복잡한 멀티 스레드 상황을 컨테이너가 처리해준다.
이러한 이유로 개발자는 싱글 스레드 애플리케이션처럼 단순하게 개발할 수 있고 비지니스 로직 개발에 집중 할 수 있다.
스프링 프레임워크를 사용하면 Service Layer에서 @Transactional 어노테이션을 선언해서 트랜잭션을 시작한다.
--> @Transactional 어노테이션이 있으면 호출한 메소드를 실행하기 직전에 스프링의 트랜잭션 AOP가 먼저 동작한다.
단 Jpa가 제공하는 RepositoryMethod들은 모두 @Transactional 처리가 되어있다.
근데 왜?? 전에 LazyLoading이 일어났지?
대상 메소드를 호출하기 직전에 트랜잭션을 시작하고, 대상 메소드가 정상 종료되면 트랜잭션을 커밋하면서 종료한다.
--> 이때 트랜잭션을 커밋하면 JPA는 먼저 영속성 컨텍스트를 플러시해서 변경 내용을 데이터베이스에 반영한 후에 데이터베이스 트랜잭션을 커밋한다.
결과적으로 영속성 컨텍스트의 변경 내용이 데이터베이스에 정상 반영된다. 만약 예외가 발생하면 트랜잭션을 롤백하고 종료하는데 이때는 플러시를 호출하지 않는다.
<JPA 트랜잭션 롤백에 대한 글>
준영속 상태와 지연 로딩
스프링 컨테이너를 이용하여 조회 된 엔티티는 서비스와 리포지토리 계층에서 영속성 컨텍스트에 관리되면서 영속 상태를 유지하지만 컨트롤러나 뷰 같은 프리젠테이션 계층에서는 준영속 상태(트랜잭션이 종료되면서 영속성 컨텍스트도 함께 종료되었기 때문에 준영속 상태로 변환)가 된다.
준영속 상태
변경감지와 지연 로딩이 동작하지 않게된다.
변경감지 : 영속성 컨텍스트가 살아 있는 서비스 계층(트랜잭션 범위)까지만 동작하고 영속성 컨텍스트가 종료된 프리젠테이션 계층에서는 동작하지 않는다.
지연로딩 : 준영속성 상태에서는 지연 로딩 기능이 동작하지 않는다.
그 이유는 바로 준영속성 상태는 영속성 컨텍스트가 없기 때문이다.
해결법 : 뷰가 필요한 엔티티를 미리 로딩해두는법, OSIV를 사용해서 엔티티를 항상 영속 상태로 유지하는 방법
뷰가 필요한 엔티티를 미리 로딩해두는법
글로벌 페치전략 수정 엔티티에 있는 fetch 타입을 지연로딩에서 즉시로딩으로 변경하는 방법.
단점 :
사용하지 않는 엔티티를 로딩한다.
N+1 문제가 발생한다.
JPQL 페치 조인 JPQL 조인 명령어 마지막에 fetch를 넣어주면 된다. 단점 :
무분별하게 사용하면 화면에 맞춘 리포지토리 메소드가 증가한다.
강제로 초기화
영속성 컨텍스트가 살아있을 때 프리젠테이션 계층이 필요한 엔티티를 강제로 초기화해서 반환하는 방법
FACADE 계층 추가
프레젠테이션 계층과 서비스 계층 사이에 FACADE 계층을 하나 더 두는 방법
FACADE 계층의 역할과 특징
프리젠테이션 게층과 도메인 모델 계층 간의 논리적 의존성을 분리
프리젠테이션 계층에서 필요한 프록시 객체를 초기화한다.
서비스 계층을 호출해서 비즈니스 로직을 실행한다.
리포지토리를 직접 호출해서 뷰가 요구하는 엔티티를 찾는다.
OSIV(Open Session In View)
영속성 컨텍스트를 뷰까지 열어준다는 뜻이다.
영속성 컨텍스트가 살아있으면 엔티티는 영속 상태로 유지된다. 즉, 뷰에서도 지연 로딩을 사용 할 수 있다.
스프링 OSIV : 비즈니스 계층 트랜잭션
과거의 OSIV는 모든 계층에서 수정이 가능했다.
하지만 스프링 프레임워크에 와서는 OSIV가 많은 변화가 되었다. 이제부터 무엇이 변화되었는지 보려고 한다.
스프링 프레임워크가 제공하는 OSIV는 비즈니스 계층에서 트랜잭션을 사용하는 OSIV이다.
즉 OSIV를 사용하기는 하지만 트랜잭션은 비즈니스 계층에서만 사용한다는 뜻이다.
클라이언트의 요청이 들어오면 서블릿 필터나, 스프링 인터셉터에서 영속성 컨텍스트를 생성한다. 이때는 트랜잭션은 시작하지는 않는다.
서비스 계층에서 @Transactional로 트랜잭션을 시작할 떄 1번에서 미리 생성해둔 영속성 컨텍스트를 찾아와서 트랜잭션을 시작한다.
서비스 계층이 끝나면 트랜잭션을 커밋하고 영속성 컨텍스트를 플러시한다. 이때 트랜잭션은 끝나지만 영속성 컨텍스트는 종료하지 않는다.
컨트롤러와 뷰까지 영속성 컨텍스트가 유지되므로 조회한 엔티티는 영속 상태를 유지한다.
서블릿 필터나, 스프링 인터셉터로 요청이 돌아오면 영속성 컨테스트를 종료한다. 이때 플러시를 호출하지 않고 바로 종료한다.
즉, 영속성 컨텍스트를 통한 모든 변경은 트랜잭션 안에서 이루어져야 한다. 트랜잭션 없이 엔티티를 변경하고 영속성 컨텍스트를 플러시하면 TransactionRequeiredException 예외가 발생한다.
? 그러면... 그 후에 조회는 어떻게 되는거죠..?
트랜잭션 없이 읽기로 통하여 조회를 한다.
엔티티를 변경하지 않고 단순히 조회만 할 때는 트랜잭션이 없어도 된다.
결과적으로 스프링 OSIV는
영속성 컨텍스트는 트랜잭션 범위 안에서 엔티티를 조회하고 수정.
영속성 컨텍스트는 트랜잭션 범위 밖에서 엔티티를 조회.
트랜잭션 없이 읽기를 사용해서 프리젠테이션 계층에서 지연 로딩 기능을 사용할 수 있다.
프리젠테이션 계층에서 엔티티를 수정할 수 있는 기존 OSIV의 단점을 보완했다.
OSIV 정리
스프링 OSIV의 특징
요청이 끝날 때까지 같은 영속성 컨텍스트를 유지한다.
엔티티 수정은 트랜잭션이 있는 계층에서만 동작한다.
트랜잭션이 없는 프리젠테이션 계층은 지연 로딩을 포함해서 조회만 할 수 있다.
스프링 OSIV의 단점
OSIV를 적용하면 같은 영속성 컨텍스트를 여러 트랜잭션이 공유할 수 있다.
프리젠테이션 계층에서 엔티티를 수정하고나서 비즈니스 로직을 수행하면 엔티티가 수정될 수 있다. (flush)
프리젠티에션 계층에서 지연 로딩에 의한 SQL이 실행된다. 따라서 성능 튜닝시에 확인해야 할 부분이 넓어진다.
Last updated