Quartz 를 잘 모르시는 분들은 아래 링크를 참고해주세요~
https://velog.io/@dailylifecoding/how-to-use-java-quartz
작성된 코드는 모두 저의 github 에서 확인 가능합니다.
참고사항:
spring boot (ver 3.3.4) 사용
jdk : temurin-21 사용
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 자기 상황에 맞게 Database jdbc 구현체를 지정해주세요~ -->
<!-- 저는 일한는 곳에서 Postgresql 써서 이렇게 했습니다. -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
spring boot 의 설정 파일인 application.yaml 을 열고 다음처럼 작성합니다.
spring:
application:
name: my-batch
batch:
jdbc:
platform: postgresql
initialize-schema: always
job:
enabled: false # 중요
datasource:
url: jdbc:postgresql://localhost:5432/postgres
driver-class-name: org.postgresql.Driver
password: postgres
username: postgres
logging:
level:
root: info
spring.batch.job.enabled: false
는 꼭 설정해주세요!Bean
으로 등록된 Job
의 자동 실행을 방지합니다.Spring Boot
의 자동실행을 통한 Job 실행이 아닌,
DBMS
가 다르신 분들은 아래 설정들을 잘 바꿔주세요.
spring.batch.jdbc.platform
spring.datasource.[url, driver-class-name, username, password]
실행할 Batch Job 에 대한 설정을 작성합니다.
@Configuration
@RequiredArgsConstructor
public class SimpleBatchJobConfig {
private final JobRepository jobRepository;
private final PlatformTransactionManager txManager;
@Bean
public Job quartzJobConfigJob() {
return new JobBuilder("quartzJobConfigJob", jobRepository)
.start(quartzJobConfigStep())
.incrementer(new RunIdIncrementer())
.build();
}
@Bean
public Step quartzJobConfigStep() {
return new StepBuilder("quartzJobConfigStep", jobRepository)
.tasklet((contribution, chunkContext) -> {
System.out.println("quartzJobConfigStep RAN!");
return RepeatStatus.FINISHED;
}, txManager)
.build()
;
}
}
QuartzJobBean
추상클래스는 executeInternal
라는
추상 메소드를 제공합니다. 이 메소드는 상속 클래스에서 overwrite 를 하면,
이후에 등록할 트리거에 의해서 메소드 안의 내용이 주기적으로 실행됩니다.
package coding.toast.batch.job.quartz;
import org.quartz.JobExecutionContext;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.quartz.QuartzJobBean;
// @Component 같은 빈 등록용 어노테이션을 안하셔도 됩니다.
public class BatchScheduledJob extends QuartzJobBean {
// 앞서 생성한 quartzJobConfigJob 빈을 DI 받습니다.
private final Job job;
// 자동 생성된 JobExplorer, JobLauncher 를 DI 받습니다.
private final JobExplorer jobExplorer;
private final JobLauncher jobLauncher;
public BatchScheduledJob(@Qualifier("quartzJobConfigJob") Job job,
JobExplorer jobExplorer,
JobLauncher jobLauncher) {
this.job = job;
this.jobExplorer = jobExplorer;
this.jobLauncher = jobLauncher;
}
@Override
protected void executeInternal(@NonNull JobExecutionContext context) {
// 아주 간단한 JobLauncher 실행 코드입니다.
// Job 생성시 사용되는 new RunIdIncrementer() 의
// 신규 run.id 값을 읽어오기 위해 jobExplorer 인자값을 부여하고,
// getNextJobParameters 메소드를 호출했습니다.
JobParameters jobParameters = new JobParametersBuilder(this.jobExplorer)
.getNextJobParameters(this.job)
.toJobParameters();
try {
this.jobLauncher.run(this.job, jobParameters);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
주의!
참고로QuartzJobBean
상속 클래스는 componentScan 대상임을 알리는
애노테이션인 (ex:Component
,Service
등) 붙이지 않습니다.이 상속 클래스는 아래처럼
org.quartz.JobBuilder
에 의해서 한번 wrapping 되며,
이러면 상속 클래스 내부의 의존 인스턴스들이 자동으로 auto wire 됩니다.@Bean public JobDetail quartzJobDetail() { return org.quartz.JobBuilder .newJob(BatchScheduledJob.class) .storeDurably() build(); }
이번에는 Quartz
설정을 해줍니다.
package coding.toast.batch.job.quartz._01;
import coding.toast.batch.job.quartz.BatchScheduledJob;
import lombok.RequiredArgsConstructor;
import org.quartz.JobDetail;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QuartzJobConfig {
@Bean
public JobDetail quartzJobDetail() {
return org.quartz.JobBuilder.newJob(BatchScheduledJob.class)
.storeDurably()
.build();
}
@Bean
public Trigger jobTrigger() {
// 간단하게 하려면 simpleBuilder 를 사용하세요!
SimpleScheduleBuilder simpleBuilder
= SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5) // 매 5초마다
.withRepeatCount(4); // 최초 1회 이후 4번 반복 (= 총 5회 동작)
// cron 이 편하신 분들은 아래 방법 사용.
/* CronScheduleBuilder cronBuilder
= CronScheduleBuilder.cronSchedule("0/5 * * * * ?");*/ // 매 5초마다.
return TriggerBuilder
.newTrigger()
.forJob(quartzJobDetail())
.withSchedule(simpleBuilder)
// .withSchedule(cronBuilder)
.build();
}
}
지금까지 작성한 코드를 기반으로 Spring Boot Application 을 실행하면
Spring boot 가 자동으로 QuartzScheduler
를 생성하고
빈으로 등록한 Trigger
, JobDetail
을 사용하게 됩니다.
Spring Boot 애플리케이션을 실행해서 진짜 그런지 봅시다.
로그(quartzJobConfigStep 빈에 작성한 로그)에 설정했던 대로
5초 간격으로, 총 5번 Job 이 실행된 것을 확인할 수 있습니다.
이번에는 spring-boot 에 의한 자동 실행이 아닌,
저희가 등록하는 코드에서 실행하는 방법을 알아봅시다.
주의사항!
🍞 Quartz 설정
목차에서 작성한 설정 클래스는 비활성화해줘야 합니다!// 주석처리해주세요! // @Configuration public class QuartzJobConfig { ...생략 ... }
기존
🍞 Batch Job 설정
목차에 있는 설정 클래스를
사용했기 여기서는 따로 스프링 배칭 설정 클래스는 또 작성하지 않겠습니다.
package coding.toast.batch.job.quartz._02;
import coding.toast.batch.job.quartz.BatchScheduledJob;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.converter.DefaultJobParametersConverter;
import org.springframework.batch.core.converter.JobParametersConverter;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Properties;
@Slf4j
@Component
@RequiredArgsConstructor
public class BatchQuartzJobRunner implements ApplicationRunner {
private final JobParametersConverter converter
= new DefaultJobParametersConverter();
private final Scheduler scheduler;
@Override
public void run(ApplicationArguments args) throws Exception {
// JobDetail 인스턴스 생성
JobDetail jobDetail = quartzJobDetail();
// Trigger 인스턴스 생성
Trigger trigger = jobTrigger(jobDetail);
// args 에 있는 파리미터 값들을 JobParameters 로 변환합니다.
JobParameters jobParameter = createJobParameter(args);
// BatchScheduledJob.executeInternal 메소드 내에서 사용하기 위해
// JobDataMap 에 JobParameters 를 넣는 과정입니다.
JobDataMap jobDataMap = jobDetail.getJobDataMap();
jobDataMap.put("jobParamFromArguments", jobParameter);
// 스케줄러를 실행합니다!
scheduler.scheduleJob(jobDetail, trigger);
}
/**
* arguments 를 JobParameter 로 변환한다.
*/
private JobParameters createJobParameter(ApplicationArguments args) {
String[] jobArguments = args.getNonOptionArgs().toArray(new String[0]);
Properties properties =
StringUtils.splitArrayElementsIntoProperties(jobArguments, "=");
return converter.getJobParameters(properties);
}
/**
* Quartz 의 JobDetail 생성
*/
public JobDetail quartzJobDetail() {
return org.quartz.JobBuilder
.newJob(BatchScheduledJob.class)
.storeDurably()
.build();
}
/**
* JobTrigger 생성
*/
public Trigger jobTrigger(JobDetail jobDetail) {
// 간단하게 하려면 simpleBuilder 를 사용하세요!
SimpleScheduleBuilder simpleBuilder
= SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5) // 매 5초마다
.withRepeatCount(4); // 최초 1회 이후 4번 반복 (= 총 5회 동작)
return TriggerBuilder
.newTrigger()
.forJob(jobDetail)
.withSchedule(simpleBuilder)
.build();
}
}
기존에 작성했던 QuartzJobBean 상속 클래스
의 내용을 살짝 수정해서 작성해봤습니다.
package coding.toast.batch.job.quartz;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.quartz.QuartzJobBean;
public class BatchScheduledJob extends QuartzJobBean {
private final Job job;
private final JobExplorer jobExplorer;
private final JobLauncher jobLauncher;
public BatchScheduledJob(@Qualifier("quartzJobConfigJob") Job job,
JobExplorer jobExplorer,
JobLauncher jobLauncher) {
this.job = job;
this.jobExplorer = jobExplorer;
this.jobLauncher = jobLauncher;
}
@Override
protected void executeInternal(@NonNull JobExecutionContext context) {
JobDataMap dataMap = context.getMergedJobDataMap();
JobParameters jobParameters = new JobParametersBuilder(this.jobExplorer)
.getNextJobParameters(this.job)
.addJobParameters((JobParameters)dataMap.get("jobParamFromArguments"))
.toJobParameters();
try {
this.jobLauncher.run(this.job, jobParameters);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
스프링 부트를 실행해보면 이전과 마찬가지로 잘 실행되는 것을 알 수 있습니다.