pooney
article thumbnail

 

안녕하세요 오늘은 Spring Batch 와 스케줄라 Quartz를 결합하여 사용하는 방법을 알려 드릴려고 합니다. 

보통 Batch , Quartz를 각 각 사용하는 방법은 쉽게 찾을 수 있지만 그 두개를 결합하여 사용하는 방법은 찾기가 힘들어 많은 검색을 통한 Batch + Quartz 결합 방법을 알려드리고자 합니다.  이글을 보시기전에 제가 작성한 Batch와 Quartz를 보고 오시는 것을 추천드립니다.  그것을 기반으로 진행할 것이기 때문입니다. 

 

 

 

 

 

Spring Batch + Quartz

 

 

예전에 작성한 Quartz 구성에서 빨간색 부분이 Batch를 사용하기 위해서 생성된 class 입니다. 

 

 

 

 

 

 

 

1. build.gradle

<java />
dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-batch' implementation "org.springframework.boot:spring-boot-starter-quartz" compileOnly 'org.projectlombok:lombok' runtimeOnly 'mysql:mysql-connector-java' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' }

 

 

 

2. quartz.properties

<java />
org.quartz.scheduler.instanceName=pooney org.quartz.scheduler.instanceId=AUTO org.quartz.scheduler.rmi.export=false org.quartz.scheduler.rmi.proxy=false org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount=3 org.quartz.context.key.QuartzTopic=QuartzPorperties org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate org.quartz.jobStore.tablePrefix=QRTZ_ org.quartz.jobStore.isClustered=true org.quartz.jobStore.dataSource = pooney org.quartz.dataSource.pooney.provider=hikaricp org.quartz.dataSource.pooney.driver = com.mysql.cj.jdbc.Driver org.quartz.dataSource.pooney.URL = jdbc:mysql://localhost:3306/batch?serverTimezone=UTC&characterEncoding=UTF-8 org.quartz.dataSource.pooney.user = root org.quartz.dataSource.pooney.password = 1234 org.quartz.dataSource.pooney.maxConnections = 30

 

 

 

 

 

3. application.yml

<java />
spring: jpa: hibernate: ddl-auto: create naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl use-new-id-generator-mappings: false show-sql: true properties: hibernate.format_sql: true datasource: url: jdbc:mysql://localhost:3306/batch?serverTimezone=UTC&characterEncoding=UTF-8 username: root password: 1234 driver-class-name: com.mysql.cj.jdbc.Driver

 

 

Quartz 구성 

4. QuartzConfig

<java />
@Configuration @RequiredArgsConstructor @Slf4j public class QuartzConfig { private final DataSource dataSource; private final ApplicationContext applicationContext; private final PlatformTransactionManager platformTransactionManager; @Bean public SchedulerFactoryBean schedulerFactoryBean() { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); AutoWiringSpringBeanJobFactory autoWiringSpringBeanJobFactory = new AutoWiringSpringBeanJobFactory(); autoWiringSpringBeanJobFactory.setApplicationContext(applicationContext); schedulerFactoryBean.setJobFactory(autoWiringSpringBeanJobFactory); schedulerFactoryBean.setDataSource(dataSource); schedulerFactoryBean.setOverwriteExistingJobs(true); schedulerFactoryBean.setAutoStartup(true); schedulerFactoryBean.setTransactionManager(platformTransactionManager); schedulerFactoryBean.setQuartzProperties(quartzProperties()); return schedulerFactoryBean; } private Properties quartzProperties() { PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); propertiesFactoryBean.setLocation(new ClassPathResource("quartz.properties")); Properties properties = null; try { propertiesFactoryBean.afterPropertiesSet(); properties = propertiesFactoryBean.getObject(); } catch (IOException e) { log.error("quartzProperties parse error : {}", e); } return properties; } }

 

 

 

5. QuartzJobListener

<java />
@Slf4j public class QuartzJobListner implements JobListener { @Override public String getName() { return this.getClass().getName(); } @Override public void jobToBeExecuted(JobExecutionContext context) { log.info("Job 수행 되기 전"); } @Override public void jobExecutionVetoed(JobExecutionContext context) { log.info("Job 중단"); } @Override public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { log.info("Job 수행 완료 후"); } }

 

 

 

 

QuartzTriggerListener

<java />
@Slf4j public class QuartzTriggerListener implements TriggerListener { @Override public String getName() { return this.getClass().getName(); } @Override public void triggerFired(Trigger trigger, JobExecutionContext context) { log.info("Trigger 실행"); } /** * @Content : 결과가 true이면 JobListener jobExecutionVetoed(JOB중단) 실행 */ @Override public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) { log.info("Trigger 상태 체크"); JobDataMap map = context.getJobDetail().getJobDataMap(); int executeCount = 1; if (map.containsKey("executeCount")) { executeCount = (int) map.get("executeCount"); } return executeCount >= 2; } @Override public void triggerMisfired(Trigger trigger) { } @Override public void triggerComplete(Trigger trigger, JobExecutionContext context, Trigger.CompletedExecutionInstruction triggerInstructionCode) { log.info("Trigger 성공"); } }

 

6. BeanUtil

<java />
@Component @RequiredArgsConstructor public class BeanUtil { private final ApplicationContext applicationContext; public Object getBean(String name){ return applicationContext.getBean(name); } }

 

 

 

 

 

BeanUtil의 역할은 등록한 Bean 이름을 주면 Context에서 해당 하는 Bean을 찾아주는 역할을 수행합니다.  여기서 Bean 은 Job이 되겠습니다. 

 

 

 

 

 

7. QuartzBatchJob

<java />
@Component public class QuartzBatchJob extends QuartzJob { @Autowired private JobLauncher jobLauncher; @Autowired private BeanUtil beanUtil; @SneakyThrows @Override public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); //전달받은 JodDataMap에서 Job이름을 꺼내오고 그 Job이름으로 context에서 bean을 가져온다 Job job = (Job) beanUtil.getBean((String) jobDataMap.get(QuartzService.JOB_ANME)); JobParameters jobParameters = new JobParametersBuilder() .addDate("curDate", new Date()) .toJobParameters(); jobLauncher.run(job, jobParameters); } }

 

 

 

Quartz가 동작시키는 Job은 QuartzBatchJob 하나로 구성되어 있습니다.  JobDataMap으로 동작시킬 JOB_NAME을 넘기면 해당 JOB_NAME을 가지고 Context에서 동작 시킬  Job(Bean) 을 찾고  JobLauncher을 통해 해당 Job을 실행 시킵니다.  이렇게 구성하면 JOB_NAME만 잘넘겨준다면 QuartzBatchJob하나의 Job으로 동작시키고자 하는 Spring Batch Job을 실행시킬 수 있습니다. 

8.  

9.  

10.  

11. QuartzService

<java />
@Slf4j @Configuration @RequiredArgsConstructor public class QuartzService { private final Scheduler scheduler; public static final String JOB_ANME = "JOB_NAME"; @PostConstruct public void init() { try { scheduler.clear(); scheduler.getListenerManager().addJobListener(new QuartzJobListner()); scheduler.getListenerManager().addTriggerListener(new QuartzTriggerListener()); // addJob(QuartzJob.class, "QuartzJob", "Quartz Job 입니다", paramsMap, "0/1 * * * * ?"); addJob(QuartzBatchJob.class, "createJob1", "createJob1 입니다", null , "0/1 * * * * ?"); addJob(QuartzBatchJob.class, "createJob2", "createJob2 입니다", null , "0/1 * * * * ?"); } catch (Exception e){ log.error("addJob error : {}", e); } } //Job 추가 public <T extends Job> void addJob(Class<? extends Job> job ,String name, String dsec, Map paramsMap, String cron) throws SchedulerException { JobDetail jobDetail = buildJobDetail(job,name,dsec,paramsMap); Trigger trigger = buildCronTrigger(cron); if(scheduler.checkExists(jobDetail.getKey())) scheduler.deleteJob(jobDetail.getKey()); scheduler.scheduleJob(jobDetail,trigger); } //JobDetail 생성 public <T extends Job> JobDetail buildJobDetail(Class<? extends Job> job, String name, String desc, Map paramsMap) { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put(JOB_ANME, name); jobDataMap.put("executeCount", 1); return JobBuilder .newJob(job) .withIdentity(name) .withDescription(desc) .usingJobData(jobDataMap) .build(); } //Trigger 생성 private Trigger buildCronTrigger(String cronExp) { return TriggerBuilder.newTrigger() .withSchedule(CronScheduleBuilder.cronSchedule(cronExp)) .build(); } }

 

 

 

 

 

 

Spring Batch Job 구성

 

 

여기서는 Quartz와 Spring Batch를 결합하는 방법을 소개하는거라 Job은 간단하게 Tasklet으로 구성하고 있습니다.  [Reader,Writer,Processor] , JobParameter를 사용하는 방법은 제가 작성한 글을 참고 해주세요. 

 

 

 

https://pooney.tistory.com/96

 

Spring boot - Spring Batch란?

안녕하세요 오늘은 많이 사용하는 Spring Batch를 설명해 드릴려고 합니다. Batch를 사용하기 위해선 스케줄러를 같이 사용하는데 대표적으로 아래와 같습니다 쉽게 어노테이션으로 사용가능 한 @Sch

pooney.tistory.com

 

 

 

 

 

12. BatchJob

<java />
@Slf4j @Configuration @RequiredArgsConstructor public class BatchJob { private final JobBuilderFactory jobBuilderFactory; private final StepBuilderFactory stepBuilderFactory; @Bean(name = "createJob1") public Job createJob1(){ return jobBuilderFactory.get("createJob1") .start(createJob1_Step1()) .build(); } public Step createJob1_Step1() { return stepBuilderFactory.get("createJob1_Step1") .tasklet(new Tasklet() { @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { log.info("createJob1_Step1 start!!!!"); return RepeatStatus.FINISHED; } }).build(); } }

 

 

 

13. BatchJob2

<java />
@Slf4j @Configuration @RequiredArgsConstructor public class BatchJob2 { private final JobBuilderFactory jobBuilderFactory; private final StepBuilderFactory stepBuilderFactory; @Bean(name = "createJob2") public Job createJob2(){ return jobBuilderFactory.get("createJob2") .start(createJob2_Step1()) .build(); } public Step createJob2_Step1() { return stepBuilderFactory.get("createJob2_Step1") .tasklet(new Tasklet() { @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { log.info("createJob2_Step1 start!!!!"); return RepeatStatus.FINISHED; } }).build(); } }

 

 

 

전체적인 Flow는 아래와 같습니다 

  1. createJob1 , createJob2 Bean 등록
  2. QuartzSerivce의 addJob()을 통해  QuartzBatchJob을 등록 
  3. QuartzBatchJob이 동작할때 전달받은 JobDataMap에서 JOB_NAME을 꺼냄
  4. 해당 JOB_NAME을 가지고 BeanUtil을 통해 Bean(Job)을 가지고 오고 해당 Bean을 JobLauncher를 통해 실행 
  5. createJob1 , createJob2 동작

 

 

 

 

 

결과 화면을 보시면 1초마다  Worker 스레드가 할당되어  creatJob1, creatJob2가 동작하는 것을 확인 하 실 수 있습니다. 

 

 

 

 

 

 

 

아래에 Quartz, Batch 테이블을 보시면 정상적으로 Trigger, Job이 등록 된 것을 확인 할 수 있습니다.  참고로 Batch 테이블은 JOB_INSTANCE_ID, JOB_EXCUTION_ID를 추적하면서 보시면 됩니다. 

 

 

 

14. batch_job_instance 

 

15. batch_job_executon

 

16. batch_job_exction_params

 

17. qrtz_cron_triggers

 

18. qrtz_job_detatils

 

 

 

 

이처럼 Spring Batch + Quartz를 결합하여 1초마다 스케줄러가  createJob1, createJob2를 동작시키는 배치를 만들어 보았는데요 많이 부족한점이 보일 수 있는데 저도 공부 목적으로 만든 것이라 이해해 주시면 감사하겠습니다. 

 

 

 

 

 

 

profile

pooney

@pooney

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!