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에 대비하는 방법에 대해서는 아래와 같은 방법과 함께 고민해보기로 하였다.
- Eager Loding
- @Transactional(readOnly=true),
- 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로 셋팅 된다.
다중 datasource 환경에서는 @**Primary **이 없으면 왜 해당 셋팅이 false로 풀릴까?
- SpringBoot 는 여러가지 자동 설정을 제공하는데, Jpa관련 자동 설정은 HibernateJpaAutoConfiguration을 통해 제공된다.
-
HibernateJpaAutoConfiguration에는 @ConditionalOnSingleCandidate 설정이 있기 때문에 다중 datasource이고 @Primary가 없는 경우 설정이 무시된다.
- HibernateJpaAutoConfiguration에는 직접적인 OSIV 설정이 없으므로 부모 클래스인 JpaWebConfiguration를 확인하여 무시되는 설정 중 OSIV 설정 있는지 확인한다.
- 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
4) DefaultListableBeanFactory.resolveNamedBean(ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull)
마지막 팩토리 클래스의 private 메소드 안에 있는 protected 메소드인 determinePrimaryCandidate에서 식별 및 셋팅한다.