정산 기능 구현

로직

  1. Scheduling 활성화

    Application 이 아닌, Config에서 관리하여 코드 가독성을 상승

    @Configuration
    @EnableScheduling
    public class SchedulerConfig {
    
    }
    
  2. 정산을 완료하면, 구매내역 테이블의 ‘settlement’ 칼럼을 0에서 1로 변경

  3. 매일, 매달, 매년 자정에 업데이트를 하기 위한 크론 표현식을 표현

    private final String CRON_EXPRESSION = "0 0 0 * * ?";
    
    @Scheduled(cron = CRON_EXPRESSION)
        public void settlementPaymentHistorySchedule() {
    

리팩토링 | 벌크 연산

벌크 연산 도입 이유

정산 상태 업데이트를 벌크 연산으로 최적화 하기 · SCBJ-7/SCBJ-BE · Discussion #246

벌크 연산 도입 전

@Service
@RequiredArgsConstructor
public class SettlementService {

    private final String CRON_EXPRESSION = "0 0 0 * * ?";

    private final PaymentHistoryRepository paymentHistoryRepository;
    private final AlarmService alarmService;

    @Scheduled(cron = CRON_EXPRESSION)
    public void settlementPaymentHistorySchedule() {
        List<PaymentHistory> targetPaymentHistoryList
            = paymentHistoryRepository.findPaymentHistoriesWithNotSettlement();

        for (PaymentHistory paymentHistory : targetPaymentHistoryList) {
            paymentHistory.processSettlement();
            Long memberId = paymentHistory.getProduct().getMember().getId();

            alarmService.createAlarm(memberId, paymentHistory.getId(),
                new Data("정산 완료", paymentHistory.getProductName() + "의 정산이 완료되었습니다.",
                    LocalDateTime.now()));
        }
    }

}

벌크 연산 도입 후

@Service
@RequiredArgsConstructor
public class SettlementService {

    private final PaymentHistoryRepository paymentHistoryRepository;
    private final AlarmService alarmService;
    private final EntityManager entityManager;

    @Scheduled(cron = "${schedule.cron}")
    @Transactional
    public void settlementPaymentHistorySchedule() {
        List<PaymentHistory> targetPaymentHistoryList
            = paymentHistoryRepository.findPaymentHistoriesWithNotSettlement();

        String updateQuery =
            "update PaymentHistory ph " +
                "set ph.settlement = true " +
                "where ph.settlement = false";

        entityManager.createQuery(updateQuery)
            .executeUpdate();

        for (PaymentHistory paymentHistory : targetPaymentHistoryList) {
            Long memberId = paymentHistory.getProduct().getMember().getId();
            alarmService.createAlarm(memberId, paymentHistory.getId(),
                new Data("정산 완료", paymentHistory.getProductName() + "의 정산이 완료되었습니다.",
                    LocalDateTime.now()));
        }
    }
}

벌크 연산 도입 전 성능 테스트