OSIV

(작성자: 한의희)


OSIV 이슈 발생 유형과 발생원인 및 해결 방안


OSIV 이슈가 발생하는 유형은


해당 프로젝트 중

  • layer에 별도의 Transaction이 걸려있지 않으면서
  • DB 작업 이후 Lazy loding을 한 모든 API

에서 Lazyinitializationexception이 발생하기 시작했다.

그 이유는,

Spring의 트랜잭션 관리는 주로 @Transactional 어노테이션을 통해 이루어지는데, 이 어노테이션이 없을 경우

1) EntityManager는 각 작업을 독립 적인 단위로 처리하며 JDBC의 기본 동작 방식인 auto-commit 모드에 의존한다.

2) JpaRespository의 구현체인 SimpleJpaRepository에 @Transactional 이 걸려있다. 고로, 부모 트랜잭션이 없으면 CRUD 작업이후 트랜잭션은 종료된다.

즉, OSIV 가 false인 경우는 별도의 @Transactional 설정이 없는 query 이후에 지연로딩을 사용하게 되면 exception을 맞이하게 된다.


OSIV 이슈가 발생한 원인은


우리의 자사몰 프로젝트는 총 3개의 datasource를 사용하고 있었다.

그러나 해당 프로젝트 DB connection pool이 제대로 적용되지 않은 이슈가 발생하여 해당 이슈를 해결하고자 datasource를 변경했는데,

이 과정에서 Spring이 default로 사용할 EntityManager를 지정하는 @Primary 설정이 누락,

이 때문에 Spring에서 default로 OSIV = true 설정이 무시되면서 false로 변경되어

**위와 같은 이슈가 발생하였다. **



OSIV 이슈를 해결한 방법

사실 OSIV 와 @Primary를 붙이는 것은

언뜻보면 별개의 카테고리지만,
Spring이 default 설정에 OSIV true를 제공하기 때문에 영향도가 생긴 것이라고 볼 수 있다.


그래서

multi datasource 환경에

자동 주입 및 Spring에서의 default 선택시 예외가 발생할 수 있는 default datasource 설정이 누락된 코드에

default 설정인 @Primary를 붙여주어, 예외 발생 가능성을 제거하였고,

이에 따라 default 설정이 살아남에 따라 OSIV 가 true로 자동 설정되면서 이슈는 해결되었다.


그렇지만

OSIV의 이슈는 끝날 때까지 끝난게 아니라고

해당 프로젝트에서 osiv false로 변환하면 문제가 발생하는 부분을 전부 수정 후

추후에 false로 변경하기로 하였고,


그리고 앞으로 OSIV false에 대비하는 방법에 대해서는 아래와 같은 방법과 함께 고민해보기로 하였다.

  1. Eager Loding
  2. @Transactional(readOnly=true),
  3. Lazy Loding은 DB Transaction 내부에서만 처리



OSIV (Open Session In View) 란?

Spring Boot에서 default를 true로 지원하는 기능이며 [참조], JPA를 사용할 때에, 영속성 컨텍스트 라이프 사이클 범위를 정하는 것이다.

지연 로딩은 영속성 컨텍스트 생존 범위안에서만 가능하다.


  • OSIV : TRUE이면, web 요청 전체 수명 동안 영속성 컨텍스트를 유지

출처 : https://nomoreft.tistory.com/58



  • OSIV : FALSE이면, 1) 트랜잭션 경계 내에서만 영속성 컨텍스트를 유지, 2) 트랜잭션이 따로 정의 되어있지 않다면, auto-commit 모드로 인해 사실상 영속성 컨텍스트 무의미.

출처 : https://nomoreft.tistory.com/58


그렇기 때문에, 아무데서나 지연로딩을 사용할 수 있다는 장점을 제외하고는 없기 때문에 장점보다는 단점이 명확하고, 내 기준 가장 큰 단점은 지연 로딩을 위해 DB 연결을 계속 유지해야하므로, 대규모 트래픽이 발생하는 시점에서는 DB connection pool이 고갈되어 성능 저하 될 수 있다는 점이다.



@Primary 설정이 OSIV에 미치는 영향

Default OSIV : 단일 datasource 환경에서는 SpringBoot의 자동 설정이 적용되어 OSIV는 TRUE로 셋팅 된다.

[출처 : https://docs.spring.io/spring-boot/reference/data/sql.html#data.sql.jpa-and-spring-data.open-entity-manager-in-view ]


다중 datasource 환경에서는 @**Primary **이 없으면 왜 해당 셋팅이 false로 풀릴까?


  1. SpringBoot 는 여러가지 자동 설정을 제공하는데, Jpa관련 자동 설정은 HibernateJpaAutoConfiguration을 통해 제공된다.
  2. HibernateJpaAutoConfiguration에는 @ConditionalOnSingleCandidate 설정이 있기 때문에 다중 datasource이고 @Primary가 없는 경우 설정이 무시된다.

  3. HibernateJpaAutoConfiguration에는 직접적인 OSIV 설정이 없으므로 부모 클래스인 JpaWebConfiguration를 확인하여 무시되는 설정 중 OSIV 설정 있는지 확인한다.
  4. JpaWebConfiguration에서 OSIV에 직접적인 설정이 아래와 같이 존재한다.
    • @ConditionalOnMissingBean,@ConditionalOnMissingFilterBean
      수동으로 OpenEntityManagerInViewInterceptor나 OpenEntityManagerInViewFilter를 이미 등록하지 않은 경우만 이 설정이 적용.
    • @ConditionalOnProperty(prefix = “spring.jpa”, name = {“open-in-view”}, havingValue = “true”, matchIfMissing = true)
      spring.jpa.open-in-view가 true로 설정되어 있거나, 설정이 아예 없을 경우 OSIV가 true됨
    • openEntityManagerInViewInterceptor()
      공식문서에 나와있는 OpenEntityManagerInViewInterceptor가 여기서 빈등록을 함


고로, OSIV true 자동 설정 값이 존재하지만 **@ConditionalOnSingleCandidate 설정** + 다중 datasource에 @Primary가 없어 해당 설정이 무시되어 false 처리된다.



다중 datasource 환경에서의 OpenEntityManagerInViewInterceptor에서는 @Primary 붙은 entityManager를 어떻게 식별해서 셋팅함?


1) OpenEntityManagerInViewInterceptor가 상속 받는 **EntityManagerFactoryAccessor **확인

2) EntityManagerFactoryUtils.findEntityManagerFactory()

3) DefaultListableBeanFactory.getBean(Class requiredType, Object... args)

4) DefaultListableBeanFactory.resolveNamedBean(ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull)


마지막 팩토리 클래스의 private 메소드 안에 있는 protected 메소드인 determinePrimaryCandidate에서 식별 및 셋팅한다.