Compare commits

...

2 커밋

3개의 변경된 파일76개의 추가작업 그리고 23개의 파일을 삭제

파일 보기

@ -29,7 +29,9 @@ public class QuartzConfig {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setJobFactory(springBeanJobFactory(applicationContext));
factory.setOverwriteExistingJobs(true);
factory.setAutoStartup(true);
// SchedulerInitializer에서 직접 start() 호출하므로 자동 시작 비활성화
// 자동 시작 JDBC Store의 기존 trigger가 로드되어 중복 실행 발생 가능
factory.setAutoStartup(false);
// DataSource는 Spring Boot가 자동 주입 (application.yml의 spring.datasource 사용)
return factory;

파일 보기

@ -10,6 +10,7 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Set;
/**
* 애플리케이션 시작 DB에 저장된 스케줄을 Quartz에 자동 로드
@ -34,11 +35,15 @@ public class SchedulerInitializer {
log.info("========================================");
try {
// 기존 orphan trigger 전체 정리 (이전 실행에서 남은 잔여 trigger 제거)
cleanupOrphanTriggers();
// DB에서 활성화된 스케줄 조회
List<JobScheduleEntity> activeSchedules = scheduleRepository.findAllActive();
if (activeSchedules.isEmpty()) {
log.info("활성화된 스케줄이 없습니다.");
startSchedulerIfNeeded();
return;
}
@ -67,10 +72,7 @@ public class SchedulerInitializer {
log.info("========================================");
// Quartz 스케줄러 시작
if (!scheduler.isStarted()) {
scheduler.start();
log.info("Quartz 스케줄러 시작됨");
}
startSchedulerIfNeeded();
} catch (Exception e) {
log.error("스케줄러 초기화 중 에러 발생", e);
@ -79,6 +81,7 @@ public class SchedulerInitializer {
/**
* 개별 스케줄을 Quartz에 등록
* Trigger를 먼저 명시적으로 제거한 Job을 삭제하여 orphan trigger 방지
*
* @param schedule JobScheduleEntity
* @throws SchedulerException Quartz 스케줄러 예외
@ -88,7 +91,13 @@ public class SchedulerInitializer {
JobKey jobKey = new JobKey(jobName, "batch-jobs");
TriggerKey triggerKey = new TriggerKey(jobName + "-trigger", "batch-triggers");
// 기존 스케줄 확인 삭제
// 1. 기존 Trigger 명시적 제거 (orphan trigger 방지)
if (scheduler.checkExists(triggerKey)) {
scheduler.unscheduleJob(triggerKey);
log.debug("기존 Quartz Trigger 제거: {}", triggerKey);
}
// 2. 기존 Job 삭제 (연결된 trigger도 함께 삭제됨)
if (scheduler.checkExists(jobKey)) {
scheduler.deleteJob(jobKey);
log.debug("기존 Quartz Job 삭제: {}", jobName);
@ -118,4 +127,47 @@ public class SchedulerInitializer {
log.debug(" → 다음 실행 예정: {}", trigger.getNextFireTime());
}
}
/**
* Quartz 스케줄러가 아직 시작되지 않았으면 시작
*/
private void startSchedulerIfNeeded() throws SchedulerException {
if (!scheduler.isStarted()) {
scheduler.start();
log.info("Quartz 스케줄러 시작됨");
}
}
/**
* 시작 batch-triggers 그룹의 모든 기존 trigger와 batch-jobs 그룹의 모든 기존 job을 제거
* JDBC Store에 잔존하는 orphan trigger로 인한 중복 실행 방지
*/
private void cleanupOrphanTriggers() throws SchedulerException {
// batch-triggers 그룹의 모든 trigger 제거
Set<TriggerKey> triggerKeys = scheduler.getTriggerKeys(
org.quartz.impl.matchers.GroupMatcher.triggerGroupEquals("batch-triggers"));
if (!triggerKeys.isEmpty()) {
log.info("기존 trigger {} 개 정리 시작 (batch-triggers 그룹)", triggerKeys.size());
for (TriggerKey tk : triggerKeys) {
scheduler.unscheduleJob(tk);
log.debug(" Trigger 제거: {}", tk);
}
}
// batch-jobs 그룹의 모든 job 제거
Set<JobKey> jobKeys = scheduler.getJobKeys(
org.quartz.impl.matchers.GroupMatcher.jobGroupEquals("batch-jobs"));
if (!jobKeys.isEmpty()) {
log.info("기존 job {} 개 정리 시작 (batch-jobs 그룹)", jobKeys.size());
for (JobKey jk : jobKeys) {
scheduler.deleteJob(jk);
log.debug(" Job 제거: {}", jk);
}
}
if (!triggerKeys.isEmpty() || !jobKeys.isEmpty()) {
log.info("기존 스케줄 정리 완료: trigger {} 개, job {} 개 제거",
triggerKeys.size(), jobKeys.size());
}
}
}

파일 보기

@ -253,6 +253,7 @@ public class ScheduleService {
/**
* Quartz에 Job 등록
* Trigger Job 순서로 명시적 제거 새로 등록하여 orphan trigger 방지
*
* @param entity JobScheduleEntity
* @throws SchedulerException Quartz 스케줄러 예외
@ -262,6 +263,18 @@ public class ScheduleService {
JobKey jobKey = new JobKey(jobName, "batch-jobs");
TriggerKey triggerKey = new TriggerKey(jobName + "-trigger", "batch-triggers");
// 1. 기존 Trigger 명시적 제거 (orphan trigger 방지)
if (scheduler.checkExists(triggerKey)) {
scheduler.unscheduleJob(triggerKey);
log.debug("기존 Quartz Trigger 제거: {}", triggerKey);
}
// 2. 기존 Job 삭제 (연결된 trigger도 함께 삭제됨)
if (scheduler.checkExists(jobKey)) {
scheduler.deleteJob(jobKey);
log.debug("기존 Quartz Job 삭제: {}", jobName);
}
// JobDetail 생성
JobDetail jobDetail = JobBuilder.newJob(QuartzBatchJob.class)
.withIdentity(jobKey)
@ -277,23 +290,9 @@ public class ScheduleService {
.forJob(jobKey)
.build();
// 기존 Job 삭제 등록
try {
scheduler.deleteJob(jobKey);
} catch (Exception e) {
log.debug("기존 Job 삭제 시도: {}", jobName);
}
// Job 등록
try {
scheduler.scheduleJob(jobDetail, trigger);
log.info("Quartz에 스케줄 등록 완료: {} (Cron: {})", jobName, entity.getCronExpression());
} catch (ObjectAlreadyExistsException e) {
log.warn("Job이 이미 존재함, 재시도: {}", jobName);
scheduler.deleteJob(jobKey);
scheduler.scheduleJob(jobDetail, trigger);
log.info("Quartz에 스케줄 재등록 완료: {} (Cron: {})", jobName, entity.getCronExpression());
}
// Quartz에 스케줄 등록
scheduler.scheduleJob(jobDetail, trigger);
log.info("Quartz에 스케줄 등록 완료: {} (Cron: {})", jobName, entity.getCronExpression());
}
/**