본문 바로가기
스프링

스프링 - OSIV

by 개발자 포비 2024. 12. 22.

OSIV (Open Session In View)

개념

  • 영속성 컨텍스트와 데이터베이스 커넥션을 View 렌더링이 끝나는 시점까지 유지하는 패턴

사용 고려사항

OSIV true

  • 장점: 지연 로딩이 가능하여 엔티티 그래프 탐색이 자유로움
  • 단점: 데이터베이스 커넥션을 오래 점유
  • 사용: 실시간 트래픽이 적은 ADMIN 페이지

OSIV false

  • 장점: 데이터베이스 커넥션 사용 시간 최소화
  • 단점: View 렌더링 시점에 지연 로딩 불가능
  • 사용: 실시간 트래픽이 많은 API 서버

서버 분리 운영

API 서버 (Production)

  • OSIV=false 설정
  • 무중단 배포
  • 높은 가용성 유지

ADMIN 서버

  • OSIV=true 설정
  • 일시적인 중단 허용
  • 복잡한 화면 구성에 용이

OSIV false 시 코드 패턴

// 비즈니스 로직용 서비스
@Service
@Transactional
public class OrderService {
    private final OrderRepository orderRepository;

    public void createOrder(OrderCreateDTO dto) {
        Order order = new Order(dto);
        orderRepository.save(order);
    }

    public void cancelOrder(Long orderId) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(OrderNotFoundException::new);
        order.cancel();
    }
}

// 쿼리용 Repository
@Repository
@RequiredArgsConstructor
public class OrderQueryRepository {
    private final EntityManager em;

    public OrderDetailsDTO findOrderDetails(Long orderId) {
        return em.createQuery(
            """
            select new com.example.dto.OrderDetailsDTO(
                o.id,
                o.orderDate,
                o.status,
                c.name,
                d.address,
                sum(oi.price * oi.quantity)
            )
            from Order o
            join o.customer c
            join o.delivery d
            join o.orderItems oi
            where o.id = :orderId
            group by o.id, o.orderDate, o.status, c.name, d.address
            """, OrderDetailsDTO.class)
            .setParameter("orderId", orderId)
            .getSingleResult();
    }

    public List<OrderItemDTO> findOrderItems(Long orderId) {
        return em.createQuery(
            """
            select new com.example.dto.OrderItemDTO(
                oi.id,
                p.name,
                oi.price,
                oi.quantity
            )
            from OrderItem oi
            join oi.product p
            where oi.order.id = :orderId
            """, OrderItemDTO.class)
            .setParameter("orderId", orderId)
            .getResultList();
    }
}

// 조회용 서비스
@Service
@Transactional(readOnly = true)
public class OrderQueryService {
    private final OrderQueryRepository orderQueryRepository;

    public OrderDetailsDTO getOrderDetails(Long orderId) {
        OrderDetailsDTO orderDetails = orderQueryRepository.findOrderDetails(orderId);
        List<OrderItemDTO> orderItems = orderQueryRepository.findOrderItems(orderId);
        return new OrderDetailsWithItemsDTO(orderDetails, orderItems);
    }
}

// Controller
@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {
    private final OrderService orderService;
    private final OrderQueryService orderQueryService;

    @PostMapping
    public ResponseEntity<Void> createOrder(@RequestBody OrderCreateDTO dto) {
        orderService.createOrder(dto);
        return ResponseEntity.ok().build();
    }

    @GetMapping("/{orderId}")
    public ResponseEntity<OrderDetailsDTO> getOrderDetails(@PathVariable Long orderId) {
        OrderDetailsDTO dto = orderQueryService.getOrderDetails(orderId);
        return ResponseEntity.ok(dto);
    }
}

댓글