package com.spring.infra.batch; import java.util.ArrayList; import java.util.List; import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.launch.support.RunIdIncrementer; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.lang.NonNull; import org.springframework.transaction.PlatformTransactionManager; /** * 배치 작업을 정의하는 추상 클래스입니다. * *
이 클래스는 배치 작업의 기본 설정 및 실행을 관리하며, * 배치 작업의 메타데이터를 초기화하고, JobRepository에 등록하는 기능을 제공합니다.
* *구현 클래스는 이 클래스를 상속받아 특정 배치 작업의 로직을 정의해야 합니다.
* * @author mindol * @version 1.0 */ @Configuration public abstract class AbstractBatchTask implements AbstractBatch, ApplicationContextAware, InitializingBean { /** * 배치 작업 정보. * *BatchJobInfo 어노테이션을 통해 정의된 배치 작업의 메타데이터를 포함합니다.
*/ private final BatchJobInfo batchJobInfo; /** * 배치 작업 정보 서비스. * *배치 작업 정보를 관리하는 서비스 객체입니다.
*/ private BatchJobInfoService batchJobInfoService; /** * 배치 작업 정보 데이터. * *배치 작업의 실행에 필요한 데이터를 포함합니다.
*/ private BatchJobInfoData batchJobInfoData; /** * 애플리케이션 컨텍스트. * *Spring의 ApplicationContext를 통해 필요한 빈을 가져옵니다.
*/ private ApplicationContext applicationContext; /** * JobRepository. * *배치 작업의 상태를 관리하는 리포지토리입니다.
*/ private JobRepository jobRepository; /** * 트랜잭션 매니저. * *배치 작업의 트랜잭션 관리를 담당합니다.
*/ private PlatformTransactionManager transactionManager; private BatchExceptionListener batchExceptionListener; /** * 기본 생성자입니다. * *BatchJobInfo 어노테이션을 찾고, 없으면 예외를 발생시킵니다.
*/ protected AbstractBatchTask() { this.batchJobInfo = AnnotationUtils.findAnnotation(getClass(), BatchJobInfo.class); if (this.batchJobInfo == null) { throw new IllegalStateException("BatchJobInfo annotation is missing"); } } /** * 애플리케이션 컨텍스트를 설정합니다. * * @param applicationContext 설정할 ApplicationContext * @throws BeansException 빈 설정 중 발생할 수 있는 예외 */ @Override public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; this.batchJobInfoService = applicationContext.getBean(BatchJobInfoService.class); } /** * 빈의 속성이 설정된 후 호출됩니다. * *배치 작업 정보 초기화 및 배치 작업 등록을 수행합니다.
* * @throws Exception 초기화 중 발생할 수 있는 예외 */ @Override public void afterPropertiesSet() throws Exception { initializeBatchJobInfo(); registerJobBean(); } /** * 배치 작업 정보를 초기화합니다. * *배치 작업의 메타데이터를 로드하여 설정합니다.
*/ @Override public void initializeBatchJobInfo() { String beanName = applicationContext.getBeanNamesForType(this.getClass())[0]; this.batchJobInfoData = batchJobInfoService.getBatchJobInfo(removeScopedTargetPrefix(beanName)); } /** * 배치 작업을 Spring의 Bean으로 등록합니다. * *JobRepository에 배치 작업을 등록하여 실행할 수 있도록 합니다.
*/ @Override public void registerJobBean() { var beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory(); var registry = (BeanDefinitionRegistry) beanFactory; String jobBeanName = batchJobInfoData.getJobName(); if (!registry.containsBeanDefinition(jobBeanName)) { var beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Job.class, this::createJob); registry.registerBeanDefinition(jobBeanName, beanDefinitionBuilder.getBeanDefinition()); } } /** * JobRepository를 설정합니다. * * @param jobRepository 설정할 JobRepository */ @Autowired public void setJobRepository(JobRepository jobRepository) { this.jobRepository = jobRepository; } /** * PlatformTransactionManager를 설정합니다. * * @param transactionManager 설정할 PlatformTransactionManager */ @Autowired public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } @Autowired public void setBatchExceptionListener(BatchExceptionListener batchExceptionListener) { this.batchExceptionListener = batchExceptionListener; } /** * 배치 작업을 생성합니다. * *구현 클래스에서 정의해야 하는 추상 메서드입니다.
* * @return 생성된 Job 객체 * @throws IllegalStateException STEP이 정의되지 않은 경우 예외 발생 */ @Override public Job createJob() { List기본적으로 하나의 STEP을 생성하여 반환합니다.
* * @return 생성된 Step 리스트 */ protected List주어진 이름과 Tasklet을 사용하여 Step 객체를 생성합니다.
* * @param stepName STEP의 이름 * @param tasklet STEP에서 실행할 Tasklet * @return 생성된 Step 객체 */ protected Step addStep(String stepName, Tasklet tasklet) { return new StepBuilder(stepName) .repository(jobRepository) .transactionManager(transactionManager) .listener(batchExceptionListener) .tasklet(tasklet) .build(); } /** * 서브클래스에서 구현해야 하는 Tasklet을 생성합니다. * * @return 생성된 Tasklet 객체 */ protected abstract Tasklet createTasklet(); }