본문 바로가기
  • Where there is a will there is a way.
개발/spring

JPA 애플리케이션 영속성 관리

by 소확행개발자 2020. 4. 13.
처음 멘땅에 개발을 하면서 @Service Layer 에 @Transcational 을 본 기억이 많다. 그냥 아무 생각없이 붙이거나
붙여져 있는걸 보면서 개발했던 것 같다. 뭐 여러가지로 시간이 지나면서 어느정도 알게 되었지만 이참에 책에 나온내용을 토대로 정리해볼까 한다.

 

내가 정리할 내용은 스프링 + JPA 환경에서의 영속성 관리에 대해서이다.

 

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

 

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

 

즉 , 트랜잭션을 시작할 때 영속성 컨텍스트를 생성하고 트랜잭션이 끝날 때 영속성 컨텍스트를 종료한다. 

그리고 같은 트랜잭션 안에서는 항상 같은 영속성 컨텍스트에 접근한다.

 

이게 위에서 내가 이야기 했던 스프링 프레임워크를 사용하다보면 보통 비즈니스 로직을 사용하는 계층인 서비스에서 보게되는 @Transactional 어노테이션이다. 

 

해당 어노테이션이 붙은 서비스를 호출하게 되면 스프링의 트랜잭션 AOP 가 먼저 동작한다.

 

스프링 트랜잭션 AOP 는 대상 메소드를 호출하기 직전에 트랜잭션을 시작하고, 대상 메소드가 정상 종료되면 트랜잭션을 커밋하면서 종료한다. 

 

트랜잭션을 커밋하면 entity manager 가 영속성 컨텍스트를 flush 해서 변경 감지후 변경 내용을 데이터베이스에 반영한 우에 데이터베이스 트랜잭션을 커밋 한다.

 

해당 트랜잭션에서 예외가 발생하면 트랜잭션을 롤백하고 종료하고 이 때는 플러시를 호출하지 않는다.

 

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

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

 

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

스프링 컨테이너는 스레드마다 각각 다른 트랜잭션을 할당한다. 따라서 만약 같은 엔티티 매니저를 호출해도 접근하는 영속성 컨텍스트가 다르므로 멀티스레드 환경에서 안전하다.

-> 스프링 컨테이너의 가장 큰 장점은 트랜잭션과 복잡한 멀티 스레드 상황을 컨테이너가 처리해주기 때문에 개발자는 싱글 스레드 애플리케이션 처럼 단순하게 개발할 수 있다. 즉 비즈니스에 집중할 수 있다는 것이다. 

 

2.1 준영속 상태와 지연 로딩

 

1 에서 이야기한 대로 스프링 컨테이너는 트랜잭션 범위의 영속성 컨텍스트 전략을 기본으로 사용한다. 그리고 트랜잭션이 종료되면서 영속선 컨섹스트도 함께 종료된다. 

 

  • 준영속 상태와 지연로딩

준영속 상태 -> ex Service 에서 @Transactional 이 끝난 뒤 Controller 계층으로 나와있을 때

 

  • 준영속 상태와 변경감지

마찬가지로 Controller layer 에서 영속성 컨텍스트가 살아 있는 서비스 계층까지만 동작하고 영속성 컨텍스트가 종료된 프리젠테이션 계층에서는 동작하지 않는다. 

 

 

2.2 글로벌 페치 전략 수정

 

준영속 상태에서 프록시를 호출하면 예외가 발생하는걸 어떻게 방지할까 ?

 

1. 글로벌 페치전략을 수정한다.

  • 하지만 LAZY 를 EAGER 로 수정할때 조심해야할 상황이 있다. 
    • 사용하지 않는 엔티티를 로딩한다 ( 즉 불필요한 데이터를 불러오는 경우가 생긴다 )
    • N+1 문제가 생긴다
      • N+1 는 즉시로딩 ( EAGER ) 일 경우에 JOIN 해서 한번에 가져오지만 JPQL 을 사용하게 되면 발생하게 된다.
      • 이유는 JPA 가 JPQL 을 분석해서 SQL 을 생성할 떄는 글로벌 페치 전략을 참고하지 않고 오직 JPQL 자체만 이용하기 때문이다. 
      • 이런 N+1 문제는 Fectch join 으로 해결할 수 있다.

2. JPQL 페치조인

3. FACADE 계층을 추가로 두고 강제 초기화

 

 

3. OSIV ( open session in view )

 

글로벌 페치전략을 수정하여 EAGER 를 사용하거나 특정 환경에서 JPQL fectch join 을 사용한다거나 어찌됬든 새로운 계층을 추가하는 경우도 모두 트랜잭션이 종료된 상황에서 영속성 컨텍스트도 없어지기 때문에 발생하는 문제이다.

 

그렇다면 트랜잭션이 종료되어도 영속성 컨텍스트를 뷰 계층 ( controller ) 에도 열어두면 좋지 않을까?

그래서 나온 개념이 OSIV 이다.

 

과거 OSIV 는 요청 당 트랜잭션 방식의 OSIV 였다. 

 

 하지만 뷰 계층에도 트랜잭션이 생존해 있다면 의도치 않게 데이터가 변경되어 수정이 일어나는 경우가 생길 수 있다 ( 클라이언트에 노출되는 데이터가 변경이 필요한 경우 )

 물론 이런 문제점을 해결할 수 있는방법이 있긴 하다 DTO 가 대표적인 방법

 

현재의 스프링 OSIV 는 

 

 

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

 

3.1 트랜잭션 롤백시 OSIV

 

보통 스프링 컨테이너에서 트랜잭션을 종료하게 되면 영속성 컨텍스트도 종료된다.

 하지만 OSIV 에서는 영속성 컨텍스트가 살아있기 때문에 트랜잭션 롤백시 문제가 되는 엔티티가 그대로 영속성 컨텍스트에 존재할 위험이 생기게 된다. 

스프링에서는 이를 방지하기 위해서 트랜잭션 롤백하면 영속성 컨텍스트를 clear() 하게 해준다.

'개발 > spring' 카테고리의 다른 글

java spring retrofit2 gson custom serializer 구현  (0) 2020.05.07
spring transaction 이란  (0) 2020.04.23
JPA 연관관계  (0) 2020.03.16
JPA delete 방법과 심화 delete All transactional 문제  (1) 2020.02.15
스프링 DI  (0) 2020.02.09

댓글