OSIV Basics

KMS·2022년 4월 20일
0

SpringBoot + JPA

목록 보기
14/14

OSIV?

OSIV: Open Session in View
Spring에서 기본으로 OSIV On 상태로 애플리케이션을 실행합니다.
OSIV On일때는 API 응답이 끝나거나, View가 랜더링이 끝날때까지 영속성 컨텍스트와 DB와의 커넥션을 유지 시켜줍니다.
덕분에 지금까지 지연 로딩일때 연관관계의 엔티티들을 가져오는 것이 가능했던 이유입니다.

지연 로딩 연관관계 엔티티를 가져오기 위해서는 DB와의 커넥션이 필요한데요, On 상태일때는 어디서든 DB와의 커넥션이 가능했기 때문입니다.

OSIV On or Off?

OSIV가 On 상태이면 개발하는 입장에서는 굉장히 편리한데, Off 상태로 개발할 필요가 있습니다. 왜냐하면, 실시간 트래픽이 많아지면 서비스에 장애가 발생하기 때문입니다.
On 상태이면 API 요청 또는 View가 랜더링이 끝날때까지는 DB와의 커넥션이 유지되기 때문에 커넥션이 필요 없는 상황에서도 커넥션이 유지되며, 결국 실시간 트래픽이 많아져서 DB와의 커넥션이 더 필요해지는 경우에 가능한 커넥션이 없게 되며, 결국 서비스 장애가 일어납니다.

OSIV Off

Off 상태로 만들기 위해서는 application.yml에서 spring.jpa.open-in-view: false 로 설정하면 됩니다. 이럴 경우, 트랜잭션일때 영속성 컨텍스트랑 DB와의 커넥션을 유지하고, 트랜잭션이 종료되면 커넥션을 커넥션 Pool로 반환합니다. 즉, 영속성 컨텍스트랑 DB와의 커넥션이 필요 없는 경우에는 커넥션이 유지되어 있지 않기 때문에 커넥션 리소르의 낭비를 최소화 할 수 있습니다.
하지만, Off 상태일 경우에는 지연 로딩이 트랜잭션 안에서 이루어지도록 개발해야 하며, 트랜잭션 밖에서는 JOIN FETCH로 연관관계의 엔티티들을 가져와야 합니다.

OSIV Off 상태로 개발해보기

OSIV Off 상태로 개발할때는 핵심 비즈니스 로직과 API/View에 맞춰서 트랜잭션이 필요한 로직을 따로 두고 개발하는 것이 추천됩니다.

Service에서 OrderService에는 핵심 비즈니스 로직을 두고, Service.Query에는 그 외 API에 맞춰서 트랜잭션이 필요한 로직과 필요한 DTO 들을 두고 개발 해봤습니다.
(실시간 트래픽이 많지 않은 서비스의 경우 OSVI On 상태로 개발해도 문제 없습니다.)

API

	@GetMapping("api/v3_3/collection-orders")
    private Result ordersV3_3(@RequestParam(value = "offset", defaultValue = "0") int offset,
                              @RequestParam(value = "limit", defaultValue = "100") int limit) {
        // /api/v3_2/collection-orders?offset=1&limit=1
        List<OrderQueryServiceCollectionDTO> orderDTOS = orderQueryService.orderV3_2(offset, limit);

        return new Result(orderDTOS);
    }

V3_3은 V3_2과 동일한 로직을 사용하는데, OSIV Off 상태에서도 작동하도록 변경한 것입니다. V3_2는 orderItems를 가져올때 지연 로딩이므로, 다시 한번 DB와의 커넥션이 필요합니다. OSIV On 일때는 어디서든 DB와의 컨넥션이 가능했지만, Off 일때는 트랜젝션 안에서만 가능함으로 V3_2 실행시 오류가 발생합니다.

OrderQueryService

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OrderQueryService {

    private final OrderRepository orderRepository;

    public List<OrderQueryServiceCollectionDTO> orderV3_2(int offset, int limit) {
        List<Order> orders = orderRepository.findALlWithOrderMemberPaging(offset, limit); //ManyToOne, OneToOne 관계 모두 JOIN FETCH && Paging
        List<OrderQueryServiceCollectionDTO> orderDTOS = orders.stream()
                .map(o -> new OrderQueryServiceCollectionDTO(o))
                .collect(Collectors.toList());

        return orderDTOS;
    }

}

DTO

@Data
public class OrderQueryServiceCollectionDTO {
    private Long orderId;
    private String username; //주문자 이름
    private LocalDateTime orderDate;
    private OrderStatus orderStatus;
    private Address address; //배송지
    private List<OrderItemQueryServiceCollectionDTO> orderItems;

    public OrderQueryServiceCollectionDTO(Order order) {
        this.orderId = order.getId();
        this.username = order.getMember().getUsername();
        this.orderDate = order.getOrderDate();
        this.orderStatus = order.getStatus();
        this.address = order.getDelivery().getAddress();
        this.orderItems = order.getOrderItems().stream()
                .map((o) -> new OrderItemQueryServiceCollectionDTO(o))
                .collect(Collectors.toList());
    }
}
@Data
public class OrderItemQueryServiceCollectionDTO {
    private String itemName;
    private int orderPrice;
    private int count;

    public OrderItemQueryServiceCollectionDTO(OrderItem orderItem) {
        this.itemName = orderItem.getItem().getName();
        this.orderPrice = orderItem.getOrderPrice();
        this.count = orderItem.getCount();
    }
}

기존 OrderService와의 차이점은 OrderQueryService에 @Transactional 애노테이션을 추가함으로써 트랜젝션이라는 것을 알려준다는 것 입니다. 덕분에 영속성 컨텍스트와 DB와의 커넥션이 이루어집니다.

profile
Student at Sejong University Department of Software

0개의 댓글