์ฟผ์ธ ์ค์ผ์ค๋ฌ
์์ ์ฝ๋ : kdevkr/spring-demo-quartz
์ํํธ์จ์ด ์์ง๋์ด์ธ ๊ฐ๋ฐ์๋ ๋ฐ๋ณต์ ์ผ๋ก ์ ํด์ง ์๊ฐ์ ์ํ๋์ด์ผํ ์์ ์ ์์ฝํด๋๋ ์ค์ผ์ค๋ง ๊ธฐ๋ฅ์ ์์ฃผ ์ฌ์ฉํ๊ฒ ๋๋ ํธ์ด๋ค. ๋ฆฌ๋ ์ค ์๋ฒ์์๋ ๋ฐฐ์ ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํ์ฌ ํฌ๋ก ํญ(crontab)์ ๋ฑ๋กํ์ฌ ์คํ๋ ์ ์๋๋ก ํ๋ฉฐ ์์คํ ์ ๊ตฌ์ฑํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์๋ ์ค์ผ์ค๋ง ๊ธฐ๋ฅ์ ํ์ฉํด์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ํ์ํ ๋ฐ๋ณต์ ์ธ ์์ ์ ์ํํ๋๋ก ๊ตฌํํ๊ฒ ๋๋ค. ์ฟผ์ธ ์ค์ผ์ค๋ฌ๋ ์คํ๋ง ๋ถํธ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ์ง์ํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ฏ๋ก ๋ฐ๋ณต์ ์ธ ์์ ์ ์ํํ๊ธฐ ์ํ ์ค์ผ์ค ๊ธฐ๋ฅ์ ์ ์ฉํ ์ ์๋ค.
์ธ๋ฉ๋ชจ๋ฆฌ ๋ ๋ฆฝ ์ค์ผ์ค๋ฌ
QuartzAutoConfiguration์ ์ํด ์๋์ผ๋ก SchedulerFactoryBean๊ฐ ๋ฑ๋ก๋๊ณ RAMJobStore๊ฐ ๊ธฐ๋ณธ๊ฐ์ด๊ธฐ์ ๋ณ๋ค๋ฅธ ์ค์ ์์ด๋ ์ฟผ์ธ ์ค์ผ์ค๋ฌ๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
spring:
quartz:
scheduler-name: QuartzScheduler
job-store-type: memory
properties:
org.quartz.scheduler.instanceName: QuartzScheduler
org.quartz.scheduler.instanceId: AUTO
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 100
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
JDBC ๋ถ์ฐ ์ค์ผ์ค๋ฌ
์ค๋ฌด์์ ์ฟผ์ธ ์ค์ผ์ค๋ฌ๋ฅผ ์ฌ์ฉํ๊ณ ์์ง๋ง ์ฟผ์ธ ํ์์ ์ ๊ณตํ๋ JDBC ๊ธฐ๋ฐ์ ํด๋ฌ์คํฐ๋ง ๊ธฐ๋ฅ์ ์ฌ์ฉํ๊ณ ์์ง๋ ์๋ค. ๋น์ทํ๊ฒ ์ค์ผ์ค ์ํ์ ๋ํด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฝ์ ์ด์ฉํ์ฌ ๋ถ์ฐ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ค์ผ์ค์ด ์ค๋ณตํ์ฌ ์คํ๋์ง ์๋๋ก ๊ตฌํ๋ ์ํ์ด๋ค. ์๋ฌดํผ ์ด ๊ธ์์๋ ์ฟผ์ธ ์์ ์ ๊ณตํ๋ JDBC ํด๋ฌ์คํฐ๋ง์ผ๋ก ์ค์ผ์ค์ ๊ด๋ฆฌํด๋ณด๋๋ก ํ์.
์ฟผ์ธ ์ค์ผ์ค๋ฌ์ฉ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ฑ
์ฟผ์ธ ์ค์ผ์ค๋ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง ์์ฑ
JDBC ๊ธฐ๋ฐ JobStoreTX ๊ตฌ์ฑ
CREATE USER quartz WITH ENCRYPTED PASSWORD 'quartz123' CONNECTION LIMIT 100;
CREATE DATABASE quartz OWNER quartz;
-- Run tables_postgres.sql
spring:
quartz:
scheduler-name: QuartzScheduler
job-store-type: jdbc
properties:
org.quartz.scheduler.instanceName: QuartzScheduler
org.quartz.scheduler.instanceId: AUTO
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 100
org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
org.quartz.jobStore.dataSource: quartzDS
org.quartz.jobStore.isClustered: true
org.quartz.jobStore.clusterCheckinInterval: 20000
org.quartz.dataSource.quartzDS.provider: hikaricp
org.quartz.dataSource.quartzDS.driver: org.postgresql.Driver
org.quartz.dataSource.quartzDS.URL: jdbc:postgresql://localhost:5432/quartz
org.quartz.dataSource.quartzDS.user: quartz
org.quartz.dataSource.quartzDS.password: quartz123
org.quartz.dataSource.quartzDS.maxConnections: 10
org.quartz.dataSource.quartzDS.provider๋ฅผ hikaricp๋ก ์ง์ ํ์ง ์์ผ๋ฉด c3p0 ์ปค๋ฅ์ ํ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์์กดํ๋ฏ๋ก ์ฃผ์ํ์.
์ค์ผ์ค ์ก ๋ฐ ํธ๋ฆฌ๊ฑฐ ๋ฑ๋ก
๋ง์ ๋ธ๋ก๊ทธ ๊ธ์์ ์ฟผ์ธ ์ค์ผ์ค๋ฌ๋ก ๋์ํ๋ ์ค์ผ์ค ์ ๋ณด์ ํธ๋ฆฌ๊ฑฐ๋ฅผ ๋ฑ๋กํ๋ ๋ฐฉ๋ฒ์ ์ด๋ ต๊ฒ ์ค๋ช ํ์ง๋ง ์๊ฐ๋ณด๋ค ๊ฐ๋จํ๋ค. ์ค์ผ์ค ์ก์ ๊ตฌํํ ๋์ JobDetail๊ณผ Trigger๋ฅผ ํจ๊ป ๋น์ผ๋ก ๋ฑ๋กํ ์ ์๋๋ก ์๋์ ๊ฐ์ด ๊ด๋ฆฌํ๋ฉด ํธ๋ฆฌํ๊ฒ ๋ฑ๋กํ ์ ์๋ค.
package com.example.demo.scheduler;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class SampleJob extends QuartzJobBean {
public static final String JOB_NAME = "SampleJob";
public static final String JOB_DETAIL_NAME = JOB_NAME + "Detail";
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
log.info("{}, {}, {}", context.getTrigger().getKey().toString(), context.getJobInstance().toString(), context.getFireTime());
}
@Bean(JOB_DETAIL_NAME)
public JobDetail jobDetail() {
return JobBuilder.newJob().ofType(SampleJob.class)
.storeDurably()
.withIdentity("SampleJobDetail")
.withDescription("Invoke Sample Job...")
.build();
}
@Bean
public Trigger simpleTrigger(@Qualifier(JOB_DETAIL_NAME) JobDetail job) {
return TriggerBuilder.newTrigger().forJob(job)
.withIdentity("SampleJobTrigger")
.withDescription("Sample trigger with interval")
.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatForever().withIntervalInSeconds(5))
.build();
}
@Bean
public Trigger cronTrigger(@Qualifier(JOB_DETAIL_NAME) JobDetail job) {
return TriggerBuilder.newTrigger().forJob(job)
.withIdentity("SampleJobTrigger")
.withDescription("Sample trigger with cron")
.withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?"))
.build();
}
}
์คํ๋ง ์ค์ผ์ค๋ง ๋ถ์ฐ ๋๊ธฐํ
์ฟผ์ธ ์ค์ผ์ค๋ฌ๋ฅผ ๋์ ํ๋ ์ด์ ๋ ์คํ๋ง์์ ์ ๊ณตํ๋ @Scheduled๋ฅผ ํตํ ์ค์ผ์ค๋ง ๊ธฐ๋ฅ์ ํด๋ฌ์คํฐ๋ง์ ์ง์ํ์ง ์๊ธฐ ๋๋ฌธ์ ๋ถ์ฐ๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ ๋ฆฝ์ ์ผ๋ก ์คํ๋๊ธฐ ๋๋ฌธ์ด๋ค. ์ฟผ์ธ ์ค์ผ์ค๋ฌ์ ๋์ผํ๊ฒ JDBC ๊ธฐ๋ฐ์ผ๋ก ๋๊ธฐํ๋ฅผ ์ํํ ์ ์๋๋ก ShedLock ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ๋๊ธฐํ๋ ์ค์ผ์ค์ด ๋์๋๋๋ก ํ ์ ์๋ค. ์คํ๋ง์์ ์ ๊ณตํ๋ ์ค์ผ์ค๋ง ๊ธฐ๋ฅ์ ์ ํ๋ฆฌ์ผ์ด์ ๋ง๋ค ์คํํด๋ ์๊ด์๋ ์์ ์ ๊ฐ๋จํ๊ฒ ์ ์ฉํ๋ ๊ฒ์ด ์ข๋ค๊ณ ์๊ฐ๋๋ฏ๋ก ๋๊ธฐํ๋ ์ค์ผ์ค์ด ํ์ํ ๊ฒฝ์ฐ๋ผ๋ฉด ์ฟผ์ธ ์ค์ผ์ค๋ฌ๋ฅผ ์ฌ์ฉํ๋๊ฒ ์ข๋ค๊ณ ์๊ฐ๋๋ค.