package com.snp.batch.scheduler; import com.snp.batch.global.model.JobScheduleEntity; import com.snp.batch.global.repository.JobScheduleRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.quartz.*; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import java.util.List; /** * 애플리케이션 시작 시 DB에 저장된 스케줄을 Quartz에 자동 로드 * ApplicationReadyEvent를 수신하여 모든 빈 초기화 후 실행 */ @Slf4j @Component @RequiredArgsConstructor public class SchedulerInitializer { private final JobScheduleRepository scheduleRepository; private final Scheduler scheduler; /** * 애플리케이션 준비 완료 시 호출 * DB의 활성화된 스케줄을 Quartz에 로드 */ @EventListener(ApplicationReadyEvent.class) public void initializeSchedules() { log.info("========================================"); log.info("스케줄러 초기화 시작"); log.info("========================================"); try { // DB에서 활성화된 스케줄 조회 List activeSchedules = scheduleRepository.findAllActive(); if (activeSchedules.isEmpty()) { log.info("활성화된 스케줄이 없습니다."); return; } log.info("총 {}개의 활성 스케줄을 로드합니다.", activeSchedules.size()); int successCount = 0; int failCount = 0; // 각 스케줄을 Quartz에 등록 for (JobScheduleEntity schedule : activeSchedules) { try { registerSchedule(schedule); successCount++; log.info("✓ 스케줄 로드 성공: {} (Cron: {})", schedule.getJobName(), schedule.getCronExpression()); } catch (Exception e) { failCount++; log.error("✗ 스케줄 로드 실패: {}", schedule.getJobName(), e); } } log.info("========================================"); log.info("스케줄러 초기화 완료"); log.info("성공: {}개, 실패: {}개", successCount, failCount); log.info("========================================"); // Quartz 스케줄러 시작 if (!scheduler.isStarted()) { scheduler.start(); log.info("Quartz 스케줄러 시작됨"); } } catch (Exception e) { log.error("스케줄러 초기화 중 에러 발생", e); } } /** * 개별 스케줄을 Quartz에 등록 * * @param schedule JobScheduleEntity * @throws SchedulerException Quartz 스케줄러 예외 */ private void registerSchedule(JobScheduleEntity schedule) throws SchedulerException { String jobName = schedule.getJobName(); JobKey jobKey = new JobKey(jobName, "batch-jobs"); TriggerKey triggerKey = new TriggerKey(jobName + "-trigger", "batch-triggers"); // 기존 스케줄 확인 및 삭제 if (scheduler.checkExists(jobKey)) { scheduler.deleteJob(jobKey); log.debug("기존 Quartz Job 삭제: {}", jobName); } // JobDetail 생성 JobDetail jobDetail = JobBuilder.newJob(QuartzBatchJob.class) .withIdentity(jobKey) .usingJobData("jobName", jobName) .withDescription(schedule.getDescription()) .storeDurably(true) .build(); // CronTrigger 생성 CronTrigger trigger = TriggerBuilder.newTrigger() .withIdentity(triggerKey) .withSchedule(CronScheduleBuilder.cronSchedule(schedule.getCronExpression()) .withMisfireHandlingInstructionDoNothing()) .forJob(jobKey) .build(); // Quartz에 스케줄 등록 scheduler.scheduleJob(jobDetail, trigger); // 다음 실행 시간 로깅 if (trigger.getNextFireTime() != null) { log.debug(" → 다음 실행 예정: {}", trigger.getNextFireTime()); } } }