JPA/JPA Basic

[JPA] OSIV ( Open Session In View )

IT록흐 2023. 6. 30. 16:21
반응형

 

 

 

 

 

Spring은 3계층으로 나뉜다. 

 

JPA는 ORM 프레임워크로 트랜잭션이 시작되는 서비스계층과 데이터 엑세스 계층을 위해 존재한다. 그러나 엔티티(Entity)는 DB로부터 CRUD 데이터를 전달하기 위해, 프레젠테이션 계층에도 존재할 수 있다.

 

이를 위해, 트랜잭션과 영속성 컨텍스트의 생존범위를 달리 할 필요가 있다.

 

데이터 엑세스 계층에서 엔티티A를 로드했는데, 프레젠테이션 계층에서 엔티티A를 DTO로 변환하는 과정에서 엔티티A와 연관된 엔티티B가 필요하여 LAZY 전략으로 로딩할 수가 있다. 그러므로 엔티티 로딩을 담당하는 영속성 컨텍스트는 트랜잭션이 끝나도 프레젠테이션 계층(View)의 호출이 종료될 때까지 살아있어야 한다.

 

DB는 단순조회의 경우, 트랜잭션 없이도 조회가 가능하기 때문이다.

 

 

 

 

 

이와 같이, 트랜잭션이 끝났음에도 화면구현이 종료될 때까지 영속성 컨텍스트를 종료시키지 않는 설정을 OSIV, Open Session In View라 부른다. 

 

 

OSIV, Open Session In View

 

Controller 조회API

    @GetMapping("api/v2/orders")
    public List<OrderDto> ordersV2(){
        System.out.println("OrderApiController.ordersV2");
        return orderRepository.findAllByString(new OrderSearch()).stream()
                .map(OrderDto::new)
                .collect(toList());
    }

 

위 조회API는 Repository에서 엔티티를 조회하여 DTO로 변환 후 화면으로 전송하는 API이다. 엔티티 -> DTO 변환시 Lazy 로딩이 발생한다. 만약 OSIV 설정이 꺼져있다면 어떻게 될까?

 

application.yml

spring:
  jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
       format_sql: true
    open-in-view: false # OSIV 설정 끄기

 

 

open-in-view를 false로 설정해보았다. ( OSIV 설정이 없는 경우 디폴트로 True이다. )  그리고 조회API를 포스트맨으로 호출해보겠다. 

 

 

 

 

LazyInitializationException이 발생했다. OSIV는 영속성컨텍스트의 생존범위를 프레젠테이션 계층까지 늘려놓는다. 그런데 OSIV 설정이 false이니 영속성 컨텍스트도 트랜잭션과 함께 종료된 것이다. 엔티티는 영속성 컨텍스트가 없으니 준영속 엔티티가 되어 Lazy 로딩에 실패한다.

 

 

OSIV의 장단점 

 

데이터 생성 및 변경 같이 민감한 작업은 트랜잭션이 필요하다. 프레젠테이션 계층은 특정화면에만 종속된 로직이기 때문에 데이터를 함부로 변경하면 안된다. 그래서 OSIV는 트랜잭션은 비즈니스 로직까지만, 영속성 컨텍스트는 프레젠테이션 계층까지 생존시켜, 화면로직이 데이터를 함부로 변경 못하도록 막는다. 이와 더불어, 단순 '조회' 작업은 모든 계층에서 일어날 수 있도록 유연성을 제공한다. 

 

그러나 단점이 하나 있다. 

 

트랜잭션이 없어도 단순조회 또한 DB와의 통신이기에, 커넥션 객체를 영속성 컨텍스트가 물고 있는다.  커넥션은 커넥션풀에 일정량이 생성되어 관리되는데, 영속성 컨텍스트의 생존기간이 늘어나면 커넥셕 반납이 이루어지지 않아, 커넥션이 말라버리는 현상이 발생할 수 있다. 

 

만약 실시간API인 경우, OSIV가 켜져있다면 치명적일 수 있다. 실시간API는 실시간으로 데이터가 변경및조회된다. 화면이 구현될 때까지 영속성 컨텍스트가 커넥션을 물고 있다면 낭비이다. 영속성 컨텍스트가 빠르게 커넥션을 반납할 수 있도록 OSIV 설정을 꺼놓는 것이 좋다. 

 

 

OSIV를 OFF할 때 구현방법

 

OSIV를 꺼놓으면 영속성컨텍스트는 트랜잭션 범위에서만 생존한다. 그러므로 트랜잭션이 종료되기 전에 모든 Lazy로딩은 강제로 호출되어야 한다. Lazy로딩 강제호출은 주로 조회에서 일어나므로 조회전용 Service를 따로 분리하는 것이 좋다. 다시 말해서, 트랜잭션은 Service에서 시작되므로, 데이터를 변경및생성하는 Service와 단순조회하는 Service를 분리한다. 

 

 

 

 

 

데이터를 변경 및 생성하는 로직은 핵심 비즈니스 로직이다. 핵심 비즈니스 로직을 처리하는 Service와 화면이 요구하는 데이터를 조회하는 Service를 분리한다.  조회Service는 읽기전용 트랜잭션으로 데이터를 조회한다. 화면에 필요한 데이터는 DTO로 변환되어 반환되므로 엔티티가 프레젠테이션 계층에서 Lazy로딩 할 일이 사라진다. 이렇게 관심사를 분리하면 OSIV OFF일때 API를 효율적으로 처리할 수 있다. 

 

 

 

OSIV 주의사항

 

마지막으로 OSIV 사용시 주의사항을 알아보겠다. OSIV는 프레젠테이션 계층까지 영속성컨텍스트가 살아있다. 그래서 엔티티가 수정되면 변경감지(DirtyChecking)가 일어나 UPDATE문이 자동생성되지만 트랜잭션이 없기에 실제로 DB에 쿼리가 실행될 일은 없다. 

 

그런데 만약, 트랜잭션이 다시 살아난다면 어떻게 될까?

 

Controller API

    @GetMapping("api/v1/simple-orders")
    public List<Order> orderV1(){
        List<Order> all =  orderRepository.findAll(new OrderSearch()); // Order 엔티티 조회
        
        for (Order order : all ) {
            order.getMember().setName("변경된 이름!"); // Member 엔티티 수정!!
        }
        
        // Member와 전혀 관련 없는 ITEM 저장 로직 수행
        Item item = new Book();
        item.setName("노인과바다");
        itemService.saveItem(item); // 트랜잭션 생성!!

        return all;
    }

 

위 API를 보자. Order(주문) 엔티티 리스트를 조회하였다. 그리고 Order 엔티티와 연관된 Member(회원) 엔티티의 이름을 Setter 메소드로 수정하였다.  엔티티의 속성이 변경되면 변경감지가 일어나 UPDATE문이 영속성 컨텍스트 SQL 저장소에 저장된다. 쿼리문이 생성되어도 트랜잭션이 없으면 DB에 실행되지 않는다. 

 

그런데 Item 엔티티를 새로 생성하는 로직이 등장한다. itemService.saveItem(item)은 핵심 비즈니스 로직으로 트랜잭션을 생성한다. Item 엔티티 관련 핵심 비즈니스 로직을 수행하면 영속성 컨텍스트의 SQL 저장소에 저장된 쿼리문이 실행된다. 여기서 앞에 변경감지로 생성된 UPDATE문도 같이 실행된다.  위 API를 실행해보자. 

 

 

API 호출 전, Member 테이블

 

API 호출 후, Member 테이블

 

name 컬럼의 데이터가 모두 변경된 것을 확인 할 수 있다. 이는 영속성 컨텍스트와 트랜잭션 생존범위가 달라서 발생하는 문제이다.  이처럼 OSVI 설정이 되어있다고 프레젠테이션 계층에서 데이터변경이 안 일어나는 것이 아니다. 영속성 컨텍스트가 살아있으면 언제든 트랜잭션이 다시 살아나 데이터가 변경될 가능성이 있다. 그러므로 이런 문제가 있음을 주의하고 적절한 코드를 짜는 것이 중요하다. 

 

 

 


 

 

참고자료

 

실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화 - 인프런 | 강의

스프링 부트와 JPA를 활용해서 API를 개발합니다. 그리고 JPA 극한의 성능 최적화 방법을 학습할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

반응형