JPA OSIV 란 무엇이고, 왜 데이터베이스 커넥션 생명주기와 관련있을까?

Posted by , August 05, 2023
JPAOSIV

OSIV(Open Session In View)

스프링부트 애플리케이션을 실행해보면, 누구나 WARN 경고문을 확인한 적이 있을겁니다. 이것은 OSIV 와 관련하여 이슈가 충분히 발생할 수 있기 떄문에, 경고문이 기본으로 표시되는 것입니다.

과거 OSIV

과거 OSIV 을 먼저 이해할 필요가 있습니다. 클라이언트의 요청이 들어오면 서블릿 필터, 스프링 인터셉터에서 영속성 컨텍스트와 트랜잭션 및 커넥션을 생성하고, 요청을 최초로 받고나서 완전히 응답하기 전까지 끊지않고 계속해서 유지하는 방식이였습니다. 이로써 한 요청에 대해 엔티티가 끝까지 영속성 상태로 살아있고, DB 와의 커넥션도 끊어지지 않습니다. 때문에 지연로딩(Lazy Loading) 도 사용이 가능해졌습니다.

다시 해석해보면, 이는 곧 컨트롤러(Controller), 뷰(View) 에서도 엔티티의 영속상태가 유지된다는 것을 의미하게 됩니다.

전 계층 레이어에서 활성화시 문제점

하지만 이렇게 모든 레이어 계층에서 활성화되는 것은 문제점이 존재했습니다. 바로 프레젠테이션(presentation) 계층 을 포함한 전 범위의 레이어 계층에서 영속성 컨텍스트가 살아있고 유지된다는 것입니다. 이는 곧 프레젠테이션 계층에서도 엔티티를 변경 및 다양한 조작이 가능하다는 말이 됩니다.

과거 OSIV 는 여기서 문제가 발생합니다. 뷰가 랜더링되면 영속성 컨텍스트가 플러시(flush) 되고 DB 에 트랜잭션이 커밋이 날라간다는 특성이 있습니다. 따라서 JPA 의 더티체킹(Dirty Checking) 과 같은 속성으로 인해 setter 를 호출하면서 뷰가 랜더링되면, 무조건 DB 에 커밋이 날라가게 되는 문제가 발생하게 됩니다. 즉, 한 요청당 하나의 트랜잭션 단위로 묶여서 처리된다는 특성으로 인해 문제가 발생합니다.

최신 OSIV 동작방식

위와 같은 문제점을 보완하도록, 최신 OSIV 는 프레젠테이션 계층에서 엔티티를 수정 못하도록 개선되었습니다. 즉, 프레젠테이션 계층을 제외한 비즈니스 계층에서만 트랜잭션을 사용 가능하도록 개선된 것입니다. 이러면 뷰가 렌더링 될때 잘못된 엔티티 변경사항에 대한 트랜잭션 커밋이 실제 DB 에 날라가는 상황을 방지할 수 있게됩니다.

스프링부트에선 기본값으로 OSIV 가 활성화되어 있습니다.

spring.jpa.open-in-view:true    // 디폴트 옵션:true

@Transactinoal 등으로 서비스단에 존재하는 메소드에 트랜잭션을 생성하면 그 즉시 데이터베이스와 커넥션을 생성하고, 해당 메소드를 벗어나도 커넥션을 계속 유지하게 됩니다. DB 트랜잭션을 시작될때, 곧바로 영속성 컨텍스트가 DB 커넥션을 가져오는 방식인것입니다.

OSIV 의 주요 특징

스프링부트에서 제공하는 OSIV 의 특징을 정리해보면 다음과 같습니다. 우선 API가 호출되고 응답을 주면서 화면이 렌더링 되는 전체 과정에서 영속성 컨텍스트는 살아있습니다. 이 때문에 비즈니스 계층외에 프레젠테이션 계층에서도 지연 로딩으로 데이터를 가져오는 것이 가능한 것입니다.

또한 @Transactional 어노테이션 등으로 비즈니스 계층에서 트랜잭션을 시작하는 즉시 DB 커넥션을 생성하고, 해당 트랜잭션이 끝나더라도 DB 커넥션은 끊어지지않고 클라이언트에게 완전히 Response 하기 전까지 하나로 계속 유지됩니다.

이 외애도 다음과 같은 특성이 존재합니다.


OSIV 를 비활성화 한다면?

반대로 OSIV 를 비활성화 해야 하는 상황도 자주 발생합니다. 어떤 상황에서 비활성화 해야 하는지를 이해하기위해, 그 전에 먼저 OSIV 를 비활성화 했을때의 특징을 정리해봅시다.

spring.jpa.open-in-view:false

가장 큰 특징은, 트랜잭션을 종료할 때 영속성 컨텍스트를 닫고 DB 커넥션을 반환한다는 것입니다. 즉, 영속성 컨텍스트와 DB 커넥션의 생존범위는 비즈니스 계층에만 존재하게 됩니다. 떄문에 커넥션 리소스를 낭비할 일이 없으며, 동시간대에 여러 API 를 호출할때 마다 ( = 프레젠테이션을 접근할 때 마다) 매번 다른 커넥션을 획득하게 되는 것이죠.

따라서 이 방식을 활용하는 상황에선, 모든 지연로딩에 대한 처리를 하나의 트랜잭션 안에서 해결해야 합니다. 영속성 컨텍스트의 생명주기는 비즈니스 계층의 하나의 트랜잭션에서만 살아있기 때문에, 컨트롤러가 여러번 호출되는 경우 각기 다른 영속성 컨텍스트와 엔티티를 보유한 상태이기 떄문입니다. 따라서 트랜잭션이 끝나기 전에 지연 로딩을 강제로 호출해두거나 fetch join을 사용해야 합니다.


참고