Spring Boot JPA에서 자주 등장하는 Open Session In View(OSIV) 패턴을 정리하고 흐름에 따라 이해한 내용을 정리했습니다.
spring:
jpa:
open-in-view: true | false
1. 개요
OSIV 패턴은 JPA의 영속성 콘텍스트(Persistence Context)를 HTTP 요청부터 응답까지 열어두어, 뷰 레이어에서도 지연 로딩(Lazy Loading)을 안전하게 수행할 수 있도록 하는 방식이다.
이 패턴의 주된 목적은 지연 로딩 (Lazy Loading) 문제를 해결하기 위함이다. 트랜잭션이 종료된 후에도 뷰 레이어에서 지연 로딩을 수행할 수 있도록 한다. OSIV 패턴을 사용하면 데이터베이스 커넥션을 오래 점유하게 되어, 고성능을 요구하는 애플리케이션에서는 문제가 될 수 있다.
이때 영속성 컨텍스를 열어두는 것이 데이터베이스 커넥션의 부족 문제가 왜 일어나는 것인가? 용어 정리를 명확하게 하여 영속성 콘텍스트와 데이터베이스 커넥션 간의 연관관계에 대해서 알아보자.
다음의 내용을 정확하게 알고 있는가요?
- OSIV 패턴은 데이터베이스 세션을 트랜잭션을 시작하고 끝까지 열어 두는 방식을 말한다.
- OSIV는 영속성 콘텍스트를 뷰까지 열어주는 기능이다. 영속성 콘텍스트가 유지되면 엔티티도 영속 상태로 유지된다. 뷰에서도 지연 로딩을 사용하고 데이터베이스 데이터를 변경할 수 있다.
JPA에서는 OEIV (Open EntityManager In View), 하이버네이트에선 OSIV라고 한다. 이에 관습적으로 OSIV라고 부른다.
2. 핵심 용어 정리
데이터베이스 세션 / 커넥션 (DB Connection)
- 정의 : 애플리케이션과 데이터베이스 사이의 물리적 네트워크 연결
- 관리 주체 : 보통 JDBC 커넥션 풀 (HikariCP 등)
- 역할 : SQL을 전송하고 결과를 받아오는 물리적 통로이다.
- 생명 주기
- Spring Transaction이 시작되면 커넥션을 얻어 트랜잭션에 바인딩
- 트랜잭션이 시작될 때 (또는 필요할 때) 커넥션 풀에서 할당받고, 트랜잭션이 끝나면 풀에 반납한다.
영속성 콘텍스트 (Persistence Context)
- 정의 : JPA가 관리하는 1차 캐시로 엔티티 객체를 관리하는 메모리 공간 (First-Level Cache)
- 관리 주체 : EntityManager 혹은 Hibernate의 Session
- 역할
- Entity 식별자 기준으로 로딩을 한 번만 수행한다. (1차 캐시에 저장)
- 변경 감지 (Dirty Checking)
- 연관 관계 탐색 및 flush 시점에 SQL을 생성한다.
- 생명 주기
- OSIV 활성화 시 : HTTP 요청 (Request)부터 응답 (Response) 범위까지 연장
- @Transactional 범위 내에서 생성 소멸
- EntityManager 가 생성될 때 내부적으로 만들어지고, 트랜잭션 또는 OSIV 범위 동안 유지
- 바로 이 시점에 DB 커넥션을 반드시 열지는 않는다. 엔티티를 다루는 호출(조회, 변경 등)이 오면 필요할 때 커넥션을 가져온다.
- 기본적으로 트랜잭션 범위 안에서 엔티티를 조회하고 수정할 수 있다.
구분 | DB 세션 | 영속성 컨텍스트 |
레벨 | 물리적 네트워크 연결 (JDBC) | JPA/Hibernate 추상화된 1차 캐시 |
주체 | 커넥션 풀(HikariCP 등) | EntityManager / Hibernate Session |
역할 | SQL 송수신, 트랜잭션 경계 관리 | Entity 상태 관리, 변경 감지, 연관 탐색 |
3. OSIV 동작 흐름
왜 OSIV 가 켜져 있으면 커넥션 풀이 고갈되는가에 대해 동작 흐름을 통해서 이해할 수 있다.
- HTTP 요청 시작
- OpenEntityManagerInViewInterceptor (또는 OpenSessionInViewFilte )가 호출되어 EntityManager 가 생성되고 영속성 콘텍스트가 열린다.
- 아직 DB 커넥션은 가지고 있지 않음
- 서비스 계층 진입 (@Transactional )
- 트랜잭션 시작 시 커넥션 풀에서 DB 세션을 할당받는다.
- 비즈니스 로직 수행
- find() 같은 조회 메서드는 필요할 때 (즉시) SQL을 날려 DB에서 데이터를 가져오고, 그 결과를 영속성 콘텍스트 (1차 캐시)에 저장한다.
- persist() , merge() 같은 변경 메서드는 엔티티 상태만 영속성 컨텍스트 (1차 캐시)에 보관한다. (실제 SQL을 발생하지 않음)
- 트랜잭션 커밋 직전
- JPA가 영송석 컨텍스트 (1차 캐시)의 변경사항을 flush()
- 이때 비로소 SQL INSERT , UPDATE , DELETE 가 실행
- 커밋 (또는 롤백)
- 커밋 후에도 커넥션은 반환되지 않고 그대로 유지
- OSIV 없이 트랜잭션 범위만 사용했다면 여기서 바로 커넥션을 반환한다
- 컨트롤러 뷰 레이어 (Lazy 로딩 가능)
- 뷰에서 엔티티의 Lazy 필드를 접근할 때 여전히 EntityManager와 데이터베이스 커넥션이 살아 있으면 SQL 발생 (N+1 쿼리)
- 영속성 콘텍스트와 커넥션이 살아 있어 지연 로딩 수행
- 요청 완료
- HTTP 요청 시에 필터나 인터셉터로 요청에 대한 응답이 돌아오면 영속성 콘텍스트를 종료한다.
- 응답 반환 시점에 EntityManager 닫기 → 영속성 컨텍스트 해제
- entityManager.close()
- DB 세션 풀에 반납
4. OSIV 장단점
장점
- 지연 로딩 편의성 : 컨트롤러/뷰에서 필요한 연관 엔티티를 지연 로딩을 통해 조회 가능하다.
단점
- 성능 이슈
- 뷰 렌더링 또는 응답 중에도 DB 쿼리 발생 → N+1 문제 유발
- 요청 (Request) 당 커넥션 장기 점유 → 커넥션 풀 고갈 위험
- 응집도 저하
- 뷰/컨트롤러에 데이터 접근 로직 분산 → 유지보수 어려움
- 트랜잭션 경계 모호화
- 사실상 트랜잭션 외부에서도 DB 접근하여 조회, 수정 가능
- 서비스 계층 책임 원칙 위배
5. OSIV 비활성화 (false 설정)
spring:
jpa:
open-in-view: false
EntityManager 영속성 콘텍스트 (1차 캐시) 생성 시점
- OSIV 비활성화 시, HTTP 요청 시작 지점에 EntityManager를 열지 않습니다.
- @Transactional 서비스 메서드에 진입할 때 트랜잭션 전용 EntityManager가 생성된다.
트랜잭션 종료 시점
- 비즈니스 로직이 끝나고 트랜잭션이 종료 시점 (커밋 or 롤백)
- JPA가 flush()를 수행해 변경 사항을 DB에 반영한다.
- 즉시 JDBC 커넥션을 반납한다.
- EntityManager 도 close 되어 영속성 콘텍스트(1차 캐시)가 해제된다.
컨트롤러 뷰 레이어
- 이 시점 이후에는 영속성 콘텍스트가 닫힌 상태이므로 컨트롤러에서 Lazy 로딩을 시도하면 LazyInitializationException이 발생한다.
- 따라서 필요한 연관 관계 데이터는 서비스 계층에서 미리 조회 (지연 로딩을 fetch) 해두어야 한다.
- DTO Projection
- Fetch Join
- Entity Graph
결론
OSIV 패턴은 Lazy Loading 문제를 손쉽게 해결하지만, 성능과 응집도 관점에서 단점도 크다. 서비스 계층 책임을 명확히 하고, 성능 요구사항에 맞춰 OSIV 설정을 끄거나 켜야 한다.
이제 그림으로 이해한 내용을 바탕으로 직관적으로 이해할 수 있다.
OSIV off
사진 제공 : 자바 ORM 표준 JPA 프로그래밍