Technology/JPA

JPA 사용 시 스프링 트랜잭션 애노테이션 누락을 유의해야되는 경우들

ikjo 2024. 4. 25. 01:04

JPA 에서의 영속성 컨텍스트와 스프링 트랜잭션

JPA 에서 영속성 컨텍스트는 엔티티를 영구 저장하는 환경으로서 엔티티 매니저로 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다. 이때 영속성 컨텍스트가 엔티티를 관리하면 여러가지 이점이 있다. 대표적으로 1차 캐시, 트랜잭션을 지원하는 쓰기 지연, 변경 감지, 지연 로딩 등이 있다.

 

스프링이나 J2EE 컨테이너 환경에서 JPA 를 사용하면 영속성 컨텍스트의 생존 범위가 스프링  트랜잭션의 범위와 같다. 즉 스프링에서 지원하는 트랜잭션을 시작할 때 영속성 컨텍스트가 생성되고 해당 트랜잭션이 끝날 때 영속성 컨텍스트를 종료하는 것이다. 참고로 같은 트랜잭션 안에서는 항상 같은 영속성 컨텍스트에 접근한다.

 

 

스프링 트랜잭션 애노테이션이 적용되어있지 않다면?

일반적으로 레이어드 아키텍처 기반의 스프링 부트 애플리케이션을 개발할 때에는 필요 시 서비스 계층 상 메서드 단위에 스프링 트랜잭션 애노테이션을 붙여주곤 한다. 하지만 모든 경우에 트랜잭션 애노테이션을 붙여야만 하는 것은 아니기에 간혹 빠져있는 경우도 있는데, 이 경우 해당 메서드 내에서 JPA 기술에 의존하다가 요구사항 변경 시 내부 로직을 수정하는 과정에서 예상치 못한 에러를 만나게 될 가능성이 높다.

 

fetch type 이 Lazy Loading 으로 바뀌는 경우

JPA 사용 시 한 엔티티가 다른 엔티티와 XXXToOne 관계로 매핑되어 있는 경우 fetch type 은 기본적으로 Eager Loading 으로 설정된다. 하지만  Eager Loading 의 경우 성능 문제(불필요한 데이터 조회)라든가 추후 조인 관계가 복잡해질 때 예상하기 힘든 쿼리들이 나가는 경우가 많아 일반적으로 XXXToOne 관계인 경우 명시적으로 fetch type 을 Lazy Loading 으로 바꿔준다.

 

하지만 (개발상 모종의 이유로) Eager Loading 으로 사용하다가 뒤늦게 Lazy Loading 으로 바꾸는 경우도 있다. Lazy Loading 으로 설정하면 DB 로부터 연관된 엔티티의 데이터를 즉시 조회하는 것이 아니라 먼저 프록시를 주입한 후 해당 프록시를 실제 사용할 때 DB 로부터 데이터를 조회한다. 이때 이러한 지연 로딩을 위한 전제 조건은 해당 엔티티가 영속성 컨텍스트의 관리를 받는 상태여야 한다는 것이다. 따라서 해당 엔티티에 접근하여 프록시를 사용하는 시점에는 스프링 트랜잭션의 범위 내에 있어야 한다는 것이다.

 

이때, 영속성 컨텍스트의 관리를 받지 않는 상태에서 프록시 객체를 사용하려고 할 경우 (지연 로딩을 시도하려는 경우) LazyInitializationException 예외가 발생하게 된다. 그리하여 서비스 운영 상 예상치 못한 에러를 발생시킬 수 있게 되기에 혹시 Eager Loading 을 Lazy Loading 으로 바꿀 때에는 이러한 점들을 유의해야한다.

 

또 다른 경우도 있다. N + 1 문제를 방지하고자 JPQL 사용 시 fetch join 을 사용하는 경우가 많은데 이때 paging 처리를 병행하게 되는 경우 paging 처리를 DB 에서 하는 것이 아니라 모든 데이터들을 애플리케이션으로 가져와 paging 처리를 하게 된다. 이는 OOME 를 유발할 수도 있기에 일반적으로 fetch join 대신 일반 join 을 사용한 후 @BatchSize 애노테이션 등을 활용하면서 Lazy Loading 으로 연관된 데이터를 지연 로딩 처리한다. 즉, 이 경우에도 영속성 컨텍스트의 관리를 받지 않는 상태에서 프록시 객체를 사용하려는 경우 LazyInitializationException 예외가 발생할 수 있기에 유의해야한다.

 

JPA 에서 제공하는 Lock 추상화 기능을 사용하는 경우

JPA 가 제공하는 락 옵션(LockModeType)을 활용하면 낙관적 락이나 비관적 락을 비교적 쉽게 사용할 수 있다. 이때 기존 로직 상 JPA 의 락 옵션을 활용하여 락을 도입하는 경우도 있다. 만일 이 경우 스프링 트랜잭션 범위 밖에 있다면 "no transaction is in progress" 라는 메시지와 함께 TransactionRequiredException 예외가 발생하는데 이는 영속성 컨텍스트에 의해 발생하는 예외로서 현재 진행 중인 작업이 트랜잭션을 필요로 하는 작업임에도 불구하고 트랜잭션 내에 존재하지 않다는 것이다.

 

그리하여, JPA  락 옵션을 활용하여 신규 로직을  작성하거나 기존 로직에 락을 도입하는 경우에는 해당 로직이 스프링 트랜잭션 범위 내에 있는지 확인해볼 필요가 있다. 이러한 부분을 확인(또는 테스트)하지 않고 배포를 하게 되면 운영 상 의도치 않은 에러를 만나게 될 수 있어 각별히 유의해야한다.

 

 

참고자료

  • 에이콘 "자바 ORM 표준 JPA 프로그래밍"

 

 

 

'Technology > JPA' 카테고리의 다른 글

PageImpl 의 역할 제대로 이해하기  (0) 2024.01.08