- Spring Quartz(쿼츠)란?
스프링 배치 쿼츠는 스프링 배치와 쿼츠(Quartz)를 결합하여 스케줄링된 작업을 관리하고 실행하는 방식이다.
스프링 배치는 데이터 처리 및 대용량 작업을 처리하기 위한 프레임워크이고, 쿼츠는 자바 기반의 스케줄링 프레임워크이다.
이 두 가지를 결합하면 특정 시간에 스프링 배치 작업(Job)을 실행하도록 스케줄링할 수 있다.
스프링 배치 자체는 데이터 처리의 배치 작업을 담당하지만, 작업 실행 시점을 정하는 기능은 기본 제공하지 않는데
이를 보완하기 위해, 쿼츠를 사용하여 특정 시간에 배치 작업을 실행할 수 있다.
- 쿼츠와 스케줄링의 차이
쿼츠와 스케줄링은 유사한 개념으로 보이지만, 세부적으로는 다음과 같은 차이가 있다.
- 쿼츠(Quartz):
- 자바에서 사용할 수 있는 강력한 스케줄링 라이브러리
- 정교한 트리거 설정이 가능하며, Job과 Trigger를 통해 작업의 실행 시간과 주기를 매우 유연하게 정의할 수 있다.
- 데이터베이스를 이용하여 작업의 상태를 저장하고, 중단된 스케줄의 복구나 클러스터링 환경에서 작업 관리가 가능합니다.
- 스케줄링(Scheduling):
- 일반적으로 자바에서 사용되는 스케줄링 기능은 대표적으로 @Scheduled 어노테이션 같은 간단한 방식으로 사용된다.
예를 들어, @Scheduled(fixedRate = 5000)를 사용하면 5초마다 작업을 실행할 수 있다. - 쿼츠에 비해 설정이 간단하고 빠르게 사용할 수 있지만, 복잡한 스케줄링이나 클러스터 환경에서의 작업 관리에는 적합하지 않다.
- 주로 단순한 주기적 작업을 처리하는 데 사용된다.
- 일반적으로 자바에서 사용되는 스케줄링 기능은 대표적으로 @Scheduled 어노테이션 같은 간단한 방식으로 사용된다.
- 쿼츠의 장단점
장점:
- 정교한 스케줄 관리: Cron 표현식 등을 사용해 매우 정밀한 스케줄을 설정할 수 있는데, 예를 들어 특정 시간, 주기, 요일, 월 등 다양한 조건에 맞춰 작업을 예약할 수 있다.
- 클러스터링 지원: 쿼츠는 클러스터링을 지원하여 여러 서버에서 작업을 분산 처리하거나 하나의 노드에서만 실행되도록 할 수 있다.
이 기능은 고가용성을 필요로 하는 환경에서 유용하다. - 작업 상태 관리: 쿼츠는 데이터베이스를 사용하여 작업의 실행 상태(예: 성공, 실패, 재시도)를 관리할 수 있어, 중단된 작업의 재시도나 상태 모니터링이 가능합니다.
- 복구 기능: 서버 재시작 후에도 이전의 스케줄 상태를 유지할 수 있으며, 예상치 못한 중단 상황에서도 스케줄의 지속성이 보장됩니다.
단점:
- 설정의 복잡성: 쿼츠의 강력한 기능은 곧 복잡한 설정을 요구합니다. 여러 설정 파일과 객체를 다루어야 하고, 트리거 및 잡 설정을 정교하게 다루어야 하기 때문에 간단한 작업을 처리하기엔 오히려 복잡할 수 있습니다.
- 리소스 사용량: 내부적으로 데이터베이스와 연동하여 상태를 관리하기 때문에, 대규모 트리거나 작업 스케줄링이 빈번한 경우 데이터베이스 및 시스템 자원을 많이 소모할 수 있습니다.
- 관리의 어려움: 클러스터링과 같은 기능을 활용할 때는 설정 및 관리가 까다로울 수 있습니다. 여러 서버 간의 동기화 문제나 스케줄 조정 등에서 관리 비용이 증가할 수 있습니다.
쿼츠는 단순히 일정한 주기로 작업을 실행하는 것을 넘어서, 정밀하고 복잡한 스케줄 관리가 필요한 경우에 적합합니다. 반면, 간단한 스케줄링이 필요한 경우에는 스프링의 @Scheduled 같은 간단한 방법이 더 나을 수 있습니다.
이처럼 스프링 배치와 쿼츠를 결합하여 사용하면, 복잡한 스케줄링과 대용량 데이터 처리 작업을 효율적으로 관리할 수 있습니다.
쿼츠의 클러스터링 지원
쿼츠의 클러스터링은 여러 서버에서 하나의 스케줄링 작업을 공유하며 실행할 수 있는 기능이다.
이 기능은 특히 고가용성과 부하 분산이 필요한 환경에서 유용하다.
예를들어
두 개의 서버 A와 B가 있고, 이 서버들이 동일한 쿼츠 스케줄러와 동일한 데이터베이스를 사용하고 있고,
서버 A와 B 모두 QuartzScheduler를 설정할 때
yaml에 org.quartz.jobStore.isClustered=true 속성을 활성화하여 클러스터링 모드로 설정한다.
클러스터 모드에서는 Job 실행 시 두 서버가 동시에 동일한 작업을 실행하지 않고, 오직 하나의 서버에서만 Job이 실행된다.
예를 들어, 매일 자정에 실행되는 배치 작업이 있을 때, 서버 A와 B 중 하나에서만 실행되고. 만약 서버 A가 중단되면 서버 B가 그 작업을 이어서 실행하게 된다.
이런 클러스터링 설정은 주로 JobStore를 데이터베이스에 두고, 두 서버가 동일한 JobStore를 공유하는 구조에서 이루어지며
이를 통해 각 서버는 현재 실행 중인 작업 상태를 데이터베이스를 통해 공유하게 되어, 중복된 작업 실행을 방지할 수 있다.
JDBCJobStore를 사용하면, 모든 서버가 동일한 데이터베이스에 접근하여 현재 실행 중인 작업, 예약된 작업, 트리거 상태 등을 데이터베이스에 저장하고 관리할 수 있다.
이번에는 클러스터링 기능을 테스트를 진행하지 않을 것이기 때문에 쿼츠의 클러스터링에 대한 내용은 간단하게만 소개하고 넘어간다.
- 쿼츠의 핵심 클래스 및 인터페이스
- Scheduler: 스케줄링 작업을 관리하는 가장 중요한 인터페이스
Scheduler는 스케줄러를 시작, 중지, 일시 정지하거나 재개할 수 있으며, Job 및 Trigger를 등록하고 관리할 수 있다.
쿼츠의 Scheduler 인스턴스는 SchedulerFactory를 통해 생성된다. - JobDetail: Job 클래스와 관련된 메타데이터를 포함하는 클래스
JobDetail은 Job 인스턴스와 관련된 정보를 담고 있으며, Job이 실행될 때 어떤 구성을 사용할지를 정의합니다.
JobDetail은 주로 JobBuilder를 사용해 생성한다. - Trigger: Job이 언제 실행될지를 정의하는 인터페이스
Trigger는 SimpleTrigger와 CronTrigger가 대표적인 구현체.- SimpleTrigger: 특정 시간에 한 번 실행하거나, 일정한 간격으로 반복 실행할 때 사용.
- CronTrigger: 크론 표현식을 사용하여 매우 정밀하게 시간 설정을 할 수 있다.
예를 들어, "매주 월요일 오전 9시에 실행"과 같은 스케줄을 정의할 수 있습니다.
- SchedulerFactory: Scheduler 인스턴스를 생성하는 역할
StdSchedulerFactory가 가장 많이 사용되는 구현체로. 설정 파일이나 프로그래밍 방식으로 Scheduler의 설정을 관리할 수 있다. - JobStore: 스케줄러가 Job 및 Trigger의 상태를 저장하는 방식을 정의하는 인터페이스
JobStore는 쿼츠는 두 가지 주요 JobStore를 제공한다.- RAMJobStore: 메모리에 작업 상태를 저장 -> 영속성이 없고, 클러스터링이 불가능하다.
- JDBCJobStore: 데이터베이스를 사용하여 작업의 상태를 저장 -> 클러스터링 환경에서는 이 JDBCJobStore를 사용하여
서버 간 작업 상태를 공유한다.
JDBCJobStore를 사용하면, 모든 서버가 동일한 데이터베이스에 접근하여 현재 실행 중인 작업, 예약된 작업, 트리거 상태 등을 데이터베이스에 저장하고 관리할 수 있다.
- TriggerListener : 트리거의 상태 변화나 실행 이벤트를 감지하고, 이에 따른 콜백 메서드를 제공한다
트리거가 실행되기 전이나 후, 또는 트리거가 완료되었을 때의 이벤트를 처리할 수 있다.
이를 통해 트리거 실행의 흐름을 제어하거나 추가적인 로깅이나 알림 기능을 구현할 수 있다.
- JDBCJobStore 를 사용할때 생성되는 Job과 Trigger 정보를 저장하는 쿼츠의 테이블들
쿼츠의 기본 테이블들은 QRTZ_로 시작하며, 각 테이블의 이름과 기능은 다음과 같다
- QRTZ_JOB_DETAILS
- Job의 정의와 메타데이터를 저장하는 테이블로, 실제 Job의 실행 내용이 아닌 Job의 기본 정보가 담겨 있다.
- 주요 컬럼:
- JOB_NAME, JOB_GROUP: Job의 이름과 그룹. 동일한 그룹 내에서는 Job 이름이 유일해야 한다.
- JOB_CLASS_NAME: 실행할 Job의 클래스 이름을 저장한다.
- IS_DURABLE: 잡이 트리거 없이도 유지될 수 있는지 여부를 나타낸다.
- QRTZ_TRIGGERS
- 스케줄러가 트리거를 관리하기 위해 필요한 메타 정보를 저장하며, 트리거와 관련된 실행 상태를 추적한다.
- 주요 컬럼:
- TRIGGER_NAME, TRIGGER_GROUP: 트리거의 이름과 그룹. 동일한 그룹 내에서는 트리거 이름이 유일해야 한다.
- TRIGGER_STATE: 트리거의 현재 상태 (예: WAITING, ACQUIRED, PAUSED 등)
- NEXT_FIRE_TIME, PREV_FIRE_TIME: 트리거의 다음 실행 시간과 마지막 실행 시간을 저장한다.
- QRTZ_SIMPLE_TRIGGERS
- QRTZ_TRIGGERS 테이블과 조인하여 사용되며, 반복 실행 횟수나 주기를 설정하는
SimpleTrigger에 특화된 정보를 저장한다. - 주요 컬럼:
- REPEAT_COUNT: 반복 실행 횟수.
- REPEAT_INTERVAL: 반복 주기 (밀리초).
- QRTZ_TRIGGERS 테이블과 조인하여 사용되며, 반복 실행 횟수나 주기를 설정하는
- QRTZ_CRON_TRIGGERS
- CronTrigger 유형의 트리거에 대한 추가 정보를 저장한다.
QRTZ_TRIGGERS 테이블과 조인하여 사용되며, 크론 표현식 기반의 트리거 스케줄을 정의할 때 사용됨. - 주요 컬럼:
- CRON_EXPRESSION: 크론 표현식. 주기적인 스케줄링을 위해 사용된다.
- CronTrigger 유형의 트리거에 대한 추가 정보를 저장한다.
- QRTZ_BLOB_TRIGGERS
- 복잡한 트리거의 데이터를 Blob 형태로 저장한다.
일반적인 텍스트 정보 외의 복잡한 객체 정보를 저장하는 용도로 사용됨 - 주요 컬럼:
- BLOB_DATA: 트리거와 관련된 직렬화된 데이터
- 복잡한 트리거의 데이터를 Blob 형태로 저장한다.
- QRTZ_CALENDARS
- 작업 스케줄을 조정하는 캘린더 정보를 저장합니다.
특정 시간대를 제외하고 작업을 실행하거나, 특정 날짜에만 작업을 수행하는 등 스케줄링 시간 조정을 위해 사용됨 - 주요 컬럼:
- CALENDAR_NAME: 캘린더의 이름.
- CALENDAR: 캘린더 객체를 직렬화한 Blob 데이터.
- 작업 스케줄을 조정하는 캘린더 정보를 저장합니다.
- QRTZ_FIRED_TRIGGERS
- 최근에 실행된 트리거의 상태를 저장한다.
트리거의 실행 히스토리와 관련된 정보를 저장하여 트리거의 실행 로그를 관리하는 데 사용됨 - 주요 컬럼:
- ENTRY_ID: 실행된 트리거의 고유 ID.
- INSTANCE_NAME: 실행한 스케줄러 인스턴스의 이름.
- FIRED_TIME: 트리거가 실행된 시간.
- 최근에 실행된 트리거의 상태를 저장한다.
- QRTZ_PAUSED_TRIGGER_GRPS
- 현재 일시 중지된 트리거 그룹을 저장한다.
특정 트리거 그룹이 일시 중지 상태인지 여부를 확인할 수 있다.
- 현재 일시 중지된 트리거 그룹을 저장한다.
- QRTZ_SCHEDULER_STATE
- 현재 스케줄러의 상태 정보를 저장하며, 클러스터링 환경에서 각 스케줄러 인스턴스의 상태를 추적하는 데 사용된다.
이를 통해 서버의 가동 상태를 모니터링하고, 장애가 발생했을 때 다른 인스턴스가 작업을 이어받을 수 있다. - 주요 컬럼:
- INSTANCE_NAME: 스케줄러 인스턴스의 이름.
- LAST_CHECKIN_TIME: 마지막 체크인 시간.
- CHECKIN_INTERVAL: 체크인 주기.
- 현재 스케줄러의 상태 정보를 저장하며, 클러스터링 환경에서 각 스케줄러 인스턴스의 상태를 추적하는 데 사용된다.
- QRTZ_LOCKS
- 트리거나 작업의 락 정보를 저장하여, 여러 인스턴스 간에 동일한 작업이 중복 실행되지 않도록 한다.
- 설명: 클러스터링 환경에서 작업 실행 중에 락을 걸어 다른 인스턴스가 동일한 트리거를 실행하지 못하게 함.
이는 데이터 정합성을 보장하는 데 중요한 역할을 한다.
의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-quartz'
application.yml
spring:
datasource:
driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
url: jdbc:log4jdbc:mysql://127.0.0.1:43306/TESTDB?allowMultiQueries=true
username: root
password: 1234
hikari:
connection-timeout: 3000
max-lifetime: 1740000
maximum-pool-size: 20
jpa:
hibernate:
ddl-auto: update
open-in-view: false
show-sql: false
properties:
hibernate:
dialect: org.hibernate.dialect.MySQLDialect
storage-engine: innodb
hbm2ddl-auto: none # must be none
show-sql: true
format_sql: true # 포매터 적용
use_sql_comments: true # jpql 출력
batch:
job:
enabled: false
jdbc:
initialize-schema: never # always: 배치 메타테이블 항상 생성 / never: 생성하지 않음 / embedded: H2 DB 인 경우 생성
# quartz Configuration
quartz:
scheduler-name: test-quartz
job-store-type: jdbc # 스케줄 정보를 jdbc로 관리하도록 설정
jdbc:
initialize-schema: always # Quartz 관련 테이블을 자동으로 생성할지 여부를 결정.
# - always: 애플리케이션이 실행될 때마다 Quartz의 테이블을 자동으로 생성. (임베디드 DB 또는 빈 DB에서만 유효)
# - never: 테이블을 생성하지 않음 (사용자가 직접 DDL 스크립트를 실행해야 함).
# - embedded: 임베디드 DB를 사용하는 경우에만 자동으로 테이블 생성.
properties:
org:
quartz:
scheduler:
instanceName: TestQuartzScheduler # 스케줄러의 이름을 지정. 클러스터링 시 각 인스턴스가 고유한 이름을 가질 수 있도록 설정.
instanceId: AUTO # 스케줄러 인스턴스의 고유 ID. 'AUTO'로 설정하면 Quartz가 자동으로 각 인스턴스에 고유 ID를 할당함.
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX # Quartz에서 사용하는 JobStore 클래스를 지정.
# JobStoreTX는 트랜잭션을 지원하는 데이터베이스 기반 JobStore로, 스케줄 정보가 데이터베이스에 저장됨.
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate # 사용하는 DB의 JDBC 드라이버에 맞는 Delegate 클래스를 지정.
# 기본적으로 표준 JDBC를 사용하며, 데이터베이스에 맞는 Delegate를 지정해야 할 수도 있음 (예: MySQL, PostgreSQL 등).
dataSource: batchDataSource # Quartz에서 사용할 데이터소스 지정.
tablePrefix: QRTZ_ # 데이터베이스 테이블의 이름에 사용할 접두어.
isClustered: false # Quartz가 클러스터 모드에서 실행될지 여부.
# true로 설정하면 여러 노드에서 스케줄러를 실행할 수 있고, 하나의 잡을 여러 인스턴스에서 동시에 실행하지 않도록 조율.
# false로 설정하면 단일 노드에서만 실행됨.
threadPool: # Quartz가 사용할 스레드 수를 지정. 잡을 처리할 때 동시에 실행할 수 있는 스레드의 수.
threadCount: 2
dataSource:
url: jdbc:log4jdbc:mysql://127.0.0.1:43306/TESTDB?allowMultiQueries=true
username: root
password: 1234
auto-startup: true # 서버가 시작될 때 스케줄러가 자동으로 시작됨
BatchScheduler.Java
@RequiredArgsConstructor
@Component
@Slf4j
public class BatchScheduler extends QuartzJobBean {
@Autowired
private JobLauncher jobLauncher;
@Autowired
private JobRegistry jobRegistry;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
String jobName = context.getJobDetail().getKey().getName(); // JobDetail에서 잡 이름을 가져옴
try {
Job job = jobRegistry.getJob(jobName); // JobRegistry에서 Job을 동적으로 조회
jobLauncher.run(job, new JobParametersBuilder().addLong("time", System.currentTimeMillis()).toJobParameters());
} catch (Exception e) {
log.error("fail to execute job : {}, {}", jobName, this.getClass().getSimpleName(), e);
throw new JobExecutionException(e);
}
}
}
QuartzJobBean를 상속받은 이유는 스프링과 쿼츠의 통합을 간소화하고, 트랜잭션 관리 및 스프링 컨텍스트와의 호환성을 높이기 위해
설정했다. 이를 통해 스프링 애플리케이션에서 쿼츠 잡을 정의할 때 간편하게 사용할 수 있다.
스프링에서 쿼츠를 사용할 때 QuartzJobBean을 상속받으면, ApplicationContext의 빈들을 자동으로 DI 할 수 있어,
애플리케이션 내에서 설정된 의존성 주입을 사용할 수 있다. (예: JobLauncher, JobRegistry)
또한, 배치 잡 실행 시 트랜잭션의 시작과 종료를 자동으로 관리할 수 있다.
QuartzConfig.Java
@Configuration
@Slf4j
public class QuartzConfig {
@Bean("TEST_QUARTZ_BEAN")
public SchedulerFactoryBean schedulerFactoryBean(@Qualifier(value = "batchDataSource") DataSource dataSource,
@Qualifier(value = "batchTransactionManager") PlatformTransactionManager transactionManager) throws Exception {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
// Spring의 DI(의존성 주입)를 사용하는 JobFactory 설정
schedulerFactoryBean.setJobFactory(new SpringBeanJobFactory());
// DataSource 및 트랜잭션 설정
schedulerFactoryBean.setDataSource(dataSource);
schedulerFactoryBean.setTransactionManager(transactionManager);
// 기존 잡을 덮어쓸지 여부
schedulerFactoryBean.setOverwriteExistingJobs(true);
return schedulerFactoryBean;
}
}
쿼츠 스케줄러가 스프링 컨텍스트 내의 빈과 데이터베이스를 사용하여 작업을 관리할 수 있도록 설정한다.
DataSourceConfigration.Java
@Configuration
public class DataSourceConfiguration {
@Value("${spring.datasource.driver-class-name}")
private String driverClassName;
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.hikari.connection-timeout}")
private String connectionTimeout;
@Value("${spring.datasource.hikari.max-lifetime}")
private String maxLifetime;
@Value("${spring.datasource.hikari.maximum-pool-size}")
private String maximumPoolSize;
@Bean(name = "batchDataSource")
@Primary
@BatchDataSource
public DataSource batchDataSource() {
HikariConfig config = new HikariConfig();
config.setDriverClassName(driverClassName);
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
config.setConnectionTimeout(Long.parseLong(connectionTimeout));
config.setMaxLifetime(Long.parseLong(maxLifetime));
config.setMaximumPoolSize(Integer.parseInt(maximumPoolSize));
config.setConnectionTestQuery("SELECT 1");
HikariDataSource paymentHikariDataSource = new HikariDataSource(config);
return new LazyConnectionDataSourceProxy(paymentHikariDataSource);
}
}
TransactionManagerConfig.Java
@Configuration
@EnableTransactionManagement // 트랜잭션 관리 활성화. @Transactional 을 사용해 선언적 트랜잭션으로 관리가 가능
@EnableJpaRepositories(
entityManagerFactoryRef = "batchEntityManagerFactory",
transactionManagerRef = "batchTransactionManager",
basePackages = "com.ani.study.domain"
)
public class TransactionManagerConfig {
@Value("${spring.jpa.properties.hibernate.dialect}")
private String dialect;
@Value("${spring.jpa.properties.hibernate.storage-engine}")
private String storageEngine;
@Value("${spring.jpa.properties.hibernate.show-sql}")
private String showSql;
@Value("${spring.jpa.properties.hibernate.hbm2ddl-auto}")
private String hbm2ddlAuto;
/**
* JPA 구현체에 대한 설정을 반환
* */
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setShowSql(true); // 로그 출력
return jpaVendorAdapter;
}
/**
* 엔티티 매니저 팩토리 빈 생성
* */
@Bean(name = "batchEntityManagerFactory")
@Primary // 기본 EntityManagerFactory 설정
public LocalContainerEntityManagerFactoryBean batchEntityManagerFactory(@Qualifier("batchDataSource") DataSource dataSource) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setJpaVendorAdapter(jpaVendorAdapter());
em.setJpaPropertyMap(hibernateJpaProperties());
em.setPackagesToScan("com.ani.study.domain.entity"); // JPA 엔티티 위치 설정
return em;
}
/**
* 하이버네이트 관련 설정 반환
* */
private Map<String, ?> hibernateJpaProperties() {
HashMap<String, String> properties = new HashMap<>();
properties.put("hibernate.dialect", dialect);
properties.put("hibernate.dialect.storage_engine", storageEngine);
properties.put("hibernate.show_sql", showSql);
properties.put("hibernate.format_sql", "true");
properties.put("hibernate.hbm2ddl.auto", hbm2ddlAuto);
return properties;
}
/**
* 트랜잭션 매니저 설정
* */
@Bean(name = "batchTransactionManager")
@Primary
public PlatformTransactionManager batchTransactionManager(@Qualifier("batchEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
DataSource 및 TransactionManager 설정을 추가해준다.
JDBC 기반 쿼츠를 사용할 예정이기 때문에 DB 설정은 필수이다.
JOB은 2개를 생성할 예정이다
하나는 5초에 한번씩 실행되는 Job과 또 하나는 10초에 한번씩 실행되는 Job를 생성하고 로그가 어떻게 찍히는지 확인 해본다.
QuartzTestJobConfiguration.Java
@Configuration
@RequiredArgsConstructor
@Slf4j
public class QuartzTestJobConfiguration {
private final JobRepository jobRepository;
private final PlatformTransactionManager batchTransactionManager;
private int itemReaderCnt = 0;
@Bean
public Job quartzTestJob() {
return new JobBuilder("quartzTestJob", jobRepository)
.start(quartzTestStep())
.build();
}
@Bean
public Step quartzTestStep() {
return new StepBuilder("quartzTestStep", jobRepository)
.<Integer, Integer>chunk(10, batchTransactionManager)
.reader(quartzTestItemReader())
.writer(quartzTestItemWriter())
.build();
}
@Bean
public ItemReader<Integer> quartzTestItemReader() {
return () -> {
itemReaderCnt++;
if(itemReaderCnt == 11) {
itemReaderCnt = 0;
return null;
};
int rtnCnt = itemReaderCnt * 2;
log.info("[quartzTestItemReader] rtnCnt : {}", rtnCnt);
return rtnCnt;
};
}
@Bean
public ItemWriter<Integer> quartzTestItemWriter() {
return chunk -> {
log.info("[quartzTestItemWriter] items : {}", chunk);
log.info("Hello Quartz!!");
};
}
// 스케줄러 설정
@Bean
public JobDetailFactoryBean QuartzTestJobDetail() {
JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
factoryBean.setJobClass(BatchScheduler.class);
factoryBean.setDescription("Invoke A Job");
factoryBean.setDurability(true);
factoryBean.setName("quartzTestJob");
return factoryBean;
}
@Bean
public CronTriggerFactoryBean QuartzTestTrigger(JobDetail QuartzTestJobDetail) {
CronTriggerFactoryBean trigger = new CronTriggerFactoryBean();
trigger.setJobDetail(QuartzTestJobDetail);
trigger.setCronExpression("*/10 * * * * ?"); // 10초마다 실행하는 Cron 표현식
return trigger;
}
@Bean
public Scheduler quartzTestScheduler(SchedulerFactoryBean factoryBean, JobDetail QuartzTestJobDetail, Trigger QuartzTestTrigger) throws Exception {
Scheduler scheduler = factoryBean.getScheduler();
scheduler.scheduleJob(QuartzTestJobDetail, QuartzTestTrigger); // 잡과 트리거를 스케줄러에 등록
scheduler.start(); // 스케줄러 시작
return scheduler;
}
}
QuartzSecondTestJobConfiguration.Java
@Configuration
@RequiredArgsConstructor
@Slf4j
public class QuartzSecondTestJobConfiguration {
private final JobRepository jobRepository;
private final PlatformTransactionManager batchTransactionManager;
private int itemReaderCnt = 0;
@Bean
public Job quartzSecondTestJob() {
return new JobBuilder("quartzSecondTestJob", jobRepository)
.start(quartzSecondTestStep())
.build();
}
@Bean
public Step quartzSecondTestStep() {
return new StepBuilder("quartzSecondTestStep", jobRepository)
.<Integer, Integer>chunk(10, batchTransactionManager)
.reader(quartzSecondTestItemReader())
.writer(quartzSecondTestItemWriter())
.build();
}
@Bean
public ItemReader<Integer> quartzSecondTestItemReader() {
return () -> {
itemReaderCnt++;
if(itemReaderCnt == 11) {
itemReaderCnt = 0;
return null;
};
int rtnCnt = itemReaderCnt;
log.info("[quartzSecondTestItemReader] rtnCnt : {}", rtnCnt);
return rtnCnt;
};
}
@Bean
public ItemWriter<Integer> quartzSecondTestItemWriter() {
return chunk -> {
log.info("[quartzSecondTestItemWriter] items : {}", chunk);
log.info("Hello Quartz22222!!");
};
}
// 스케줄러 설정
@Bean
public JobDetailFactoryBean QuartzSecondTestJobDetail() {
JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
factoryBean.setJobClass(BatchScheduler.class);
factoryBean.setDescription("Second Job");
factoryBean.setDurability(true);
factoryBean.setName("quartzSecondTestJob");
return factoryBean;
}
@Bean
public CronTriggerFactoryBean QuartzSecondTestTrigger(JobDetail QuartzSecondTestJobDetail) {
CronTriggerFactoryBean trigger = new CronTriggerFactoryBean();
trigger.setJobDetail(QuartzSecondTestJobDetail);
trigger.setCronExpression("*/5 * * * * ?"); // 5초마다 실행하는 Cron 표현식
return trigger;
}
@Bean
public Scheduler quartzSecondTestScheduler(SchedulerFactoryBean factoryBean, JobDetail QuartzSecondTestJobDetail, Trigger QuartzSecondTestTrigger) throws Exception {
Scheduler scheduler = factoryBean.getScheduler();
scheduler.scheduleJob(QuartzSecondTestJobDetail, QuartzSecondTestTrigger); // 잡과 트리거를 스케줄러에 등록 QuartzSecondTestTrigger : Triggers의 Name이 들어간다.
scheduler.start(); // 스케줄러 시작
return scheduler;
}
}
이제 서버를 실행 해주고 배치가 돌아가는 것을 확인해본다.
5초 주기
10초 주기
두 Job 모두 정해진 주기로 정상 실행되는 것을 확인 할 수 있다.
QRTZ_TRIGGERS
QRTZ_TRIGGERS 테이블에도 정상적으로 트리거와 Job이 등록 된 것을 확인할 수 있다.
참고 : https://docs.spring.io/spring-boot/reference/io/quartz.html
https://tall-developer.tistory.com/47
https://www.quartz-scheduler.org/documentation/
'Spring > Batch' 카테고리의 다른 글
Spring Batch(스프링배치) Chunk방식의 Repeat과 Skip(3) - 실패한 작업 제어 (0) | 2024.09.30 |
---|---|
Spring Batch(스프링배치) Chunk방식의 Repeat과 Skip(2) - 실패한 작업 제어 (0) | 2024.09.26 |
Spring Batch(스프링배치) Chunk방식의 Repeat과 Skip(1) - 실패한 작업 제어 (0) | 2024.09.24 |
Spring Batch(스프링배치) 5.1.x 버전 시작하기 (0) | 2024.09.23 |