@Service
@EnableScheduling
@RequiredArgsConstructor
public class OrderStatusScheduler {
private final OrderRepository orderRepository;
@Scheduled(fixedRate = 60000) //1분마다 실행
public void updateOrderStatus(){
LocalDateTime now = LocalDateTime.now();
//ORDER_RECEIVED 상태에서 1일 지난 주문 -> PAYMENT_COMPLETED
updateStatusForOrders(OrderStatus.ORDER_RECEIVED, OrderStatus.PAYMENT_COMPLETED, now.minusDays(1));
// PAYMENT_COMPLETED 상태에서 1일 지난 주문들 -> PRODUCT_PREPARING
updateStatusForOrders(OrderStatus.PAYMENT_COMPLETED, OrderStatus.PRODUCT_PREPARING, now.minusDays(1));
//PRODUCT_PREPARING 상태에서 1일 지난 주문들 -> SHIPMENT_START
updateStatusForOrders(OrderStatus.PRODUCT_PREPARING, OrderStatus.SHIPMENT_STARTED, now.minusDays(1));
//SHIPMENT_STARTED 상태에서 1일 지난 주문들 -> SHIPMENT_PROCESSING
updateStatusForOrders(OrderStatus.SHIPMENT_STARTED, OrderStatus.SHIPMENT_PROCESSING, now.minusDays(1));
//SHIPMENT_PROCESSING 상태에서 1일 지난 주문들 -> DELIVERED
updateStatusForOrders(OrderStatus.SHIPMENT_PROCESSING, OrderStatus.DELIVERED, now.minusDays(1));
}
private void updateStatusForOrders(OrderStatus currentOrderStatus, OrderStatus newOrderStatus, LocalDateTime beforeDate){
List<Order> orders = orderRepository.findByStatusAndCreatedAtBefore(OrderStatus.PAYMENT_COMPLETED, beforeDate);
for(Order order : orders){
order.updateStatus(newOrderStatus);
orderRepository.save(order);
}
}
}
OrderStatusScheduler.java
List<Order> findByStatusAndCreatedAtBefore(OrderStatus status, LocalDateTime createdAt);
OrderRepository.java
Hibernate: select o1_0.id,o1_0.address,o1_0.created_at,o1_0.delete_status,o1_0.detail_address,o1_0.order_number,o1_0.payment_id,o1_0.phone,o1_0.recipient,o1_0.status,o1_0.total_price,o1_0.updated_at,o1_0.user_id,o1_0.zonecode from orders o1_0 where o1_0.status=? and o1_0.created_at<?
Hibernate: select o1_0.id,o1_0.address,o1_0.created_at,o1_0.delete_status,o1_0.detail_address,o1_0.order_number,o1_0.payment_id,o1_0.phone,o1_0.recipient,o1_0.status,o1_0.total_price,o1_0.updated_at,o1_0.user_id,o1_0.zonecode from orders o1_0 where o1_0.status=? and o1_0.created_at<?
Hibernate: select o1_0.id,o1_0.address,o1_0.created_at,o1_0.delete_status,o1_0.detail_address,o1_0.order_number,o1_0.payment_id,o1_0.phone,o1_0.recipient,o1_0.status,o1_0.total_price,o1_0.updated_at,o1_0.user_id,o1_0.zonecode from orders o1_0 where o1_0.status=? and o1_0.created_at<?
Hibernate: select o1_0.id,o1_0.address,o1_0.created_at,o1_0.delete_status,o1_0.detail_address,o1_0.order_number,o1_0.payment_id,o1_0.phone,o1_0.recipient,o1_0.status,o1_0.total_price,o1_0.updated_at,o1_0.user_id,o1_0.zonecode from orders o1_0 where o1_0.status=? and o1_0.created_at<?
Hibernate: select o1_0.id,o1_0.address,o1_0.created_at,o1_0.delete_status,o1_0.detail_address,o1_0.order_number,o1_0.payment_id,o1_0.phone,o1_0.recipient,o1_0.status,o1_0.total_price,o1_0.updated_at,o1_0.user_id,o1_0.zonecode from orders o1_0 where o1_0.status=? and o1_0.created_at<?
🔹 1분에 300회 발생하던 쿼리 오노우,,,
🔹 5개 SELECT 쿼리 + N개 UPDATE 쿼리
@Service
@EnableScheduling
@RequiredArgsConstructor
public class OrderStatusScheduler {
private final OrderRepository orderRepository;
@Scheduled(fixedRate = 60000) //1분마다 실행
public void updateOrderStatus(){
LocalDateTime now = LocalDateTime.now();
//ORDER_RECEIVED 상태에서 1일 지난 주문 -> PAYMENT_COMPLETED
updateStatusForOrders(OrderStatus.ORDER_RECEIVED, OrderStatus.PAYMENT_COMPLETED, now.minusDays(1));
// PAYMENT_COMPLETED 상태에서 1일 지난 주문들 -> PRODUCT_PREPARING
updateStatusForOrders(OrderStatus.PAYMENT_COMPLETED, OrderStatus.PRODUCT_PREPARING, now.minusDays(1));
//PRODUCT_PREPARING 상태에서 1일 지난 주문들 -> SHIPMENT_START
updateStatusForOrders(OrderStatus.PRODUCT_PREPARING, OrderStatus.SHIPMENT_STARTED, now.minusDays(1));
//SHIPMENT_STARTED 상태에서 1일 지난 주문들 -> SHIPMENT_PROCESSING
updateStatusForOrders(OrderStatus.SHIPMENT_STARTED, OrderStatus.SHIPMENT_PROCESSING, now.minusDays(1));
//SHIPMENT_PROCESSING 상태에서 1일 지난 주문들 -> DELIVERED
updateStatusForOrders(OrderStatus.SHIPMENT_PROCESSING, OrderStatus.DELIVERED, now.minusDays(1));
}
private void updateStatusForOrders(OrderStatus currentStatus,
OrderStatus newStatus,
LocalDateTime cutoff) {
orderRepository.bulkUpdateStatus(
currentStatus,
newStatus,
cutoff
);
}
}
OrderStatusScheduler.java
public interface OrderRepository extends JpaRepository<Order, Long> {
@Modifying
@Query("UPDATE Order o SET o.status = :newStatus WHERE o.status = :currentStatus AND o.createdAt <= :cutoff")
int bulkUpdateStatus(@Param("currentStatus") OrderStatus currentStatus,
@Param("newStatus") OrderStatus newStatus,
@Param("cutoff") LocalDateTime cutoff);
}
OrderRepository.java
Hibernate: update orders o1_0 set status=? where o1_0.status=? and o1_0.created_at<=?
Hibernate: update orders o1_0 set status=? where o1_0.status=? and o1_0.created_at<=?
Hibernate: update orders o1_0 set status=? where o1_0.status=? and o1_0.created_at<=?
Hibernate: update orders o1_0 set status=? where o1_0.status=? and o1_0.created_at<=?
Hibernate: update orders o1_0 set status=? where o1_0.status=? and o1_0.created_at<=?
🔹 5개 UPDATE 쿼리만 실행 (SELECT 없이 바로 상태 변경)
🔹 하루 1회로 감소
🔹 데이터베이스 부하가 99.6% 이상 줄어드는 효과
🔹 테스트 환경에서 10만 건 데이터 기준 처리 시간이 8.2초 → 0.3초로 개선된 사례