Programming/Spring Boot

OSIV(Open Session In View) 개념 정리 및 예시

Jan92 2023. 10. 6. 23:25
반응형

OSIV(Open Session In View) 개념 정리 및 예시

 

spring.jpa.open-in-view=false

application.properties(또는 yml) 파일의 내용을 항상 복사 붙여 넣기 식으로 사용하고, 또 동작에 문제가 없었기 때문에 'spring.jpa.open-in-view' 옵션이 어떤 역할을 하는지 모르는 상태로 계속 사용해 왔었는데요.

 

최근 프로젝트에서 해당 옵션 값으로 인해 문제가 발생하면서 'OSIV(Open Session In View)'가 무엇인지 찾아보고 정리하게 되었습니다.

 


OSIV란?

먼저 JPA의 EntityManager가 Hibernate에서는 Session이라고 불리는데요. OSIV의 Session은 이 세션을 의미합니다.

때문에 사실 JPA에서는 OEIV(Open EntityManager In View)가 되고, Hibernate에서는 OSIV(Open Session In View)가 되지만 관례상 둘 다 'OSIV'라고 이야기합니다.

 

JPA의 EntityManager는 내부에 '영속성 컨텍스트'가 있으며, 이를 통해 엔티티를 관리하는 역할을 하는데요.

즉, 'Open Session In View'영속성 컨텍스트(Session)가 View(뷰 렌더링 시점)까지 열려있다(Open)는 것이 됩니다.

(API인 경우 결과를 응답할 때까지 영속성 컨텍스트가 열려있다는 것이 됩니다.)

 

이 내용을 이미지를 통해 살펴보면 아래와 같은데요.

 

 

OSIV 활성화

(spring.jpa.open-in-view=true / OSIV 활성화 상태)

 

 

OSIV 비활성화

(spring.jpa.open-in-view=false / OSIV 비활성화 상태)

 

각 설정에 따른 상세한 내용은 아래 코드 예시와 함께 다시 한번 살펴보겠습니다.

 

 

JpaWebConfiguration class 내부 Bean 등록 메서드

추가로 스프링 부트는 기본적으로 'Open EntityManager In View'를 활성화해 주는데요.

때문에 JpaWebConfiguration class에서 보는 것과 같이 open-in-view 옵션을 명시하지 않았을 때 아래와 같은 경고 로그를 출력하게 됩니다.

 

spring.jpa.open-in-view 옵션을 명시하지 않았을 때 경고 메세지

 


적용 예시 코드 및 내용

@Getter
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Product {

    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    @Column(name = "id", nullable = false)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_fk", nullable = false)
    private Category category;

    @Column(name = "name", nullable = false)
    private String name;
}

(Product class)

 

@Getter
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Category {

    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    @Column(name = "id", nullable = false)
    private Long id;

    @Column(name = "name", nullable = false)
    private String name;
}

(Category class)

 

예시에 사용되는 Product, Category Entity이며, Product와 Category는 다대일의 관계를 가지고 있습니다.

 

 

spring.jpa.open-in-view=false

@RestController
@RequiredArgsConstructor
public class ProductController {

    private final ProductService productService;

    @GetMapping("/product/{productName}")
    public void getProduct(@PathVariable String productName) {
        Product product = productService.getProduct(productName);
        Category category = product.getCategory();
        String cate = category.getName();
    }
}

(ProductController class)

 

@Service
@RequiredArgsConstructor
public class ProductService {

    private final ProductRepository productRepository;

    @Transactional
    public Product getProduct(String productName) {
        return productRepository.findByName(productName);
    }
}

(ProductService class)

 

위의 이미지에서 살펴본 것처럼 open-in-view=false 상태에서는 트랜잭션이 시작해서 끝날 때까지만 영속성 컨텍스트가 유지되는데요.

 

때문에 다음과 같이 트랜잭션 범위 밖인 Controller 단에서 Product, Category의 연관 관계를 통해 Category Entity의 필드에 접근하게 되면 아래와 같이 'LazyInitializationException: cloud not initialize proxy' 예외가 발생하게 됩니다.

 

LazyInitializationException

 

즉, OSIV를 활성화하지 않으면 모든 지연 로딩을 트랜잭션 안에서 처리해야 한다는 특징이 있습니다.

(또는 fetch join을 사용하여 연관 관계 데이터를 한 번에 가져올 수도 있습니다.)

 

 

spring.jpa.open-in-view=true

위 코드 그대로 open-in-view 옵션 값만 true로 변경하고 다시 해당 요청을 보냈을 때, 이번에는 지연 로딩(Lazy)이 동작하며 Category Entity의 필드에 접근할 수 있게 됩니다.

 

서비스 계층에서 트랜잭션은 끝났지만 API 응답 시점까지 영속성 컨텍스트가 유지되기 때문에 Controller 단에서 지연 로딩을 통한 조회가 정상적으로 이뤄지는 것인데요.

 

***

단순하게 결과만 봤을 때는 트랜잭션 범위 밖에서도 영속성 컨텍스트를 통한 연관 관계 조회가 가능하기 때문에 OSIV를 활성화하는 것이 좋은 것 같기도 합니다.

하지만 OSIV를 활성화하는 것은 데이터베이스 커넥션 리소스와 관련하여 크리티컬 한 문제가 발생할 수 있는데요.

 

영속성 컨텍스트가 View(API의 경우 응답 시점)까지 살아있다는 것은 다시 말해서 그 시점까지 데이터베이스 커넥션이 유지된다는 것입니다.

따라서 각 요청에 대해 데이터베이스 커넥션이 사용되는 시간이 길어지기 때문에 실시간 트래픽이 많이 발생하는 서비스의 경우 커넥션이 부족해져서 만일의 경우 결국 서비스 장애로도 이어질 수 있습니다.

(OSIV가 비활성화된 경우는 트랜잭션 종료 시점에서 데이터베이스 커넥션이 반납되기 때문에 활성화 상태에 비해 커넥션 유지 시간이 짧습니다.)

 

 

+++

추가적으로 이 경우 트랜잭션이 끝나도 영속성 컨텍스트가 살아있기 때문에 지연 로딩(Lazy)은 가능했지만, 영속성 컨텍스트의 변경 감지에 의한 데이터 수정(더티 체킹, Dirty Checking)은 동작하지 않는다는 특징이 있습니다.

 


사용하는 것이 좋을까? 사용하지 않는 것이 좋을까?

(해당 내용은 개인적인 의견이 상당히 포함되어 있다는 점 미리 참고 부탁드립니다.)

 

개인적으로는 OSIV를 항상 비활성화해 두고 개발을 진행했었고, LazyInitializationException으로 인해 서비스 단에서(트랜잭션 안에서) 지연 로딩이 필요한 부분을 모두 처리하고 결과 데이터를 반환하는 것을 당연하게 생각해 왔기 때문에(이 방식이 무조건 맞다는 것은 아닙니다.) 앞으로도 동일하게 해당 기능을 사용하지 않을 것 같습니다.

 

또한 OSIV를 활성화한 상태에서 개발했다가 나중에 데이터베이스 커넥션 리소스 관련 문제로 인해 비활성화를 하게 되는 경우, 트랜잭션 범위 밖에서 지연 로딩으로 데이터를 조회하는 부분은 모두 런타임 시에 LazyInitializationException이 발생하게 되는데요.

 

이 경우 해당 오류가 발생할 수 있는 부분을 모두 찾아 변경해야 하는 번거로움이 생길 수 있으며, 혹시나 놓치는 부분으로 인해 예외가 발생하는 경우도 있을 수 있다고 생각하기 때문에 '굳이? 꼭 사용해야 할까?'라는 개인적인 생각입니다.

 

 

 

< 참고 자료 >

https://ykh6242.tistory.com/entry/JPA-OSIVOpen-Session-In-View%EC%99%80-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94
https://sudo-minz.tistory.com/158

반응형