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);
}
}
댓글