[개발] 스프링 배치 설정

김종준·2023년 8월 30일
0

Hiit

목록 보기
11/12
post-thumbnail

스프링 배치 설정


관련 코드 바로가기


들어가며

개발을 시작한 지 얼마 되지 않았을 때 "내가 배치를 할 수 있었으면 기능이 더 풍성하였을 텐데.."하는 생각을 많이 하였던 것 같습니다.

드디어 배치에 관해 공부하고 프로젝트에 맞도록 배치 관련 설정을 해보았는데 함께 살펴봅시다.


@EnableBatchProcessing

@EnableBatchProcessing를 사용하면 스프링 부트가 배치 관련된 설정 클래스를 자동으로 만들어 줍니다.

하지만 자동으로 만들어진 설정 클래스가 모든 프로젝트에 맞을 수는 없겠죠?

이번에도 @EableBatchProcessing을 살펴보며 우리가 어떤 설정 클래스를 만들어야 하는지 알아봅시다.


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(BatchConfigurationSelector.class)
public @interface EnableBatchProcessing {

	boolean modular() default false;
}

어떤 설정 클래스를 만들어야 하는지 알기 위해서 우리는 @EnableBatchProcessing이 임포트하고 있는 BatchConfigurationSelector 클래스를 살펴봅시다.


public class BatchConfigurationSelector implements ImportSelector {

	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    ...
		String[] imports;
		if (attributes.containsKey("modular") && attributes.getBoolean("modular")) {
			imports = new String[] { ModularBatchConfiguration.class.getName() };
		}
		else {
			imports = new String[] { SimpleBatchConfiguration.class.getName() };
		}

		return imports;
	}
}

BatchConfigurationSelector를 보면 네이밍에 맞게 ModularBatchConfiguration 그리고 SimpleBatchConfiguration 둘 중 하나를 앞서 @EnableBatchProcessing에 있었던 modular를 통해 설정하고 있습니다.

modular의 기본값을 false이었기에 우리는 SimpleBatchConfiguration를 통해 설정해야 할 클래스를 알아봅시다.


@Configuration(proxyBeanMethods = false)
public class SimpleBatchConfiguration extends AbstractBatchConfiguration {
  ...
	@Override
	@Bean
	public JobRepository jobRepository() throws Exception {
		return createLazyProxy(jobRepository, JobRepository.class);
	}

	@Override
	@Bean
	public JobLauncher jobLauncher() throws Exception {
		return createLazyProxy(jobLauncher, JobLauncher.class);
	}

	@Override
	@Bean
	public JobRegistry jobRegistry() throws Exception {
		return createLazyProxy(jobRegistry, JobRegistry.class);
	}

	@Override
	@Bean
	public JobExplorer jobExplorer() {
		return createLazyProxy(jobExplorer, JobExplorer.class);
	}

	@Override
	@Bean
	public PlatformTransactionManager transactionManager() throws Exception {
		return createLazyProxy(transactionManager, PlatformTransactionManager.class);
	}

	protected void initialize() throws Exception {
		if (initialized) {
			return;
		}
		BatchConfigurer configurer = getConfigurer(context.getBeansOfType(BatchConfigurer.class).values());
		jobRepository.set(configurer.getJobRepository());
		jobLauncher.set(configurer.getJobLauncher());
		transactionManager.set(configurer.getTransactionManager());
		jobRegistry.set(new MapJobRegistry());
		jobExplorer.set(configurer.getJobExplorer());
		initialized = true;
	}
}
  • JobRepository
  • JobLauncher
  • JobRegistry
  • JobExplorer
  • PlatformTransactionManager

SimpleBatchConfiguration 클래스 내에서 확인할 수 있는 설정 클래스는 위와 같습니다.

하지만 끝이 아닙니다.

SimpleBatchConfiguration는 AbstractBatchConfiguration를 상속하고 있어 AbstractBatchConfiguration도 살펴봅시다.


@Configuration(proxyBeanMethods = false)
@Import(ScopeConfiguration.class)
public abstract class AbstractBatchConfiguration implements ImportAware, InitializingBean {
  ...
	@Bean
	public JobBuilderFactory jobBuilders() throws Exception {
		return this.jobBuilderFactory;
	}

	@Bean
	public StepBuilderFactory stepBuilders() throws Exception {
		return this.stepBuilderFactory;
	}

	@Bean
	public abstract JobRepository jobRepository() throws Exception;

	@Bean
	public abstract JobLauncher jobLauncher() throws Exception;

	@Bean
	public abstract JobExplorer jobExplorer() throws Exception;

	@Bean
	public JobRegistry jobRegistry() throws Exception {
		return this.jobRegistry;
	}

	@Bean
	public abstract PlatformTransactionManager transactionManager() throws Exception;

	@Override
	public void afterPropertiesSet() throws Exception {
		this.jobBuilderFactory = new JobBuilderFactory(jobRepository());
		this.stepBuilderFactory = new StepBuilderFactory(jobRepository(), transactionManager());
	}

	protected BatchConfigurer getConfigurer(Collection<BatchConfigurer> configurers) throws Exception {
    ...
}


@Configuration(proxyBeanMethods = false)
class ScopeConfiguration {

	private static StepScope stepScope;

	private static JobScope jobScope;

	static {
		jobScope = new JobScope();
		jobScope.setAutoProxy(false);

		stepScope = new StepScope();
		stepScope.setAutoProxy(false);
	}

	@Bean
	public static StepScope stepScope() {
		return stepScope;
	}

	@Bean
	public static JobScope jobScope() {
		return jobScope;
	}
}
  • JobBuilderFactory
  • StepBuilderFactory

배치를 사용하려면 설정해야 하는 클래스가 상당히 많네요..ㅎㅎ

하나씩 천천히 설정해 봅시다.


BatchConfigurer

가장 먼저 설정할 클래스는 BatchConfigurer입니다.

BatchConfigurer를 가장 먼저 설정하는 이유는 SimpleBatchConfiguration의 initialize 메서드에 있습니다.

다시 한번 살펴봅시다.

protected void initialize() throws Exception {
  if (initialized) {
    return;
  }
  BatchConfigurer configurer = getConfigurer(context.getBeansOfType(BatchConfigurer.class).values());
  jobRepository.set(configurer.getJobRepository());
  jobLauncher.set(configurer.getJobLauncher());
  transactionManager.set(configurer.getTransactionManager());
  jobRegistry.set(new MapJobRegistry());
  jobExplorer.set(configurer.getJobExplorer());
  initialized = true;
}

initialize 메서드는 SimpleBatchConfiguration에서 필요한 클래스를 설정을 configurer에서 가져와 설정하고 있습니다.

getConfigurer는 AbstractBatchConfiguration에서 확인할 수 있는데 DefaultBatchConfigurer를 생성해서 사용하고 있습니다.


DefaultBatchConfigurer의 선언부를 살펴보면 다음과 같습니다.

public class DefaultBatchConfigurer implements BatchConfigurer { ... }

이를보아 BatchConfigurer을 구현하고 있는 구현체중 적절한 것을 선택하여 BatchConfigurer를 생성하면 될 것 같습니다.

  • BasicBatchConfigurer
  • DefaultBatchConfigurer
  • JpaBatchConfigurer

위의 3가지 구현체가 있습니다.

저희 프로젝트의 경우 배치를 통해 DB를 다루는 작업을 많이 할 예정이기에 JpaBatchConfigurer를 사용하여 BatchConfigurer를 생성해보겠습니다.


DelegatedBatchConfigurer

DelegatedBatchConfigurer를 설정한 코드는 아래와 같습니다.

@Component(value = DelegatedBatchConfigurer.DELEGATED_BATCH_CONFIGURER_BEAN_NAME)
public class DelegatedBatchConfigurer extends JpaBatchConfigurer {

	public static final String DELEGATED_BATCH_CONFIGURER_BEAN_NAME =
			BatchConfig.BEAN_NAME_PREFIX + "Configurer";

	public PlatformTransactionManager transactionManager;

	public DelegatedBatchConfigurer(
			@Qualifier(value = BatchSupportConfig.PROPERTY_BEAN_NAME) BatchProperties properties,
			@Qualifier(value = BatchDataSourceConfig.DATASOURCE_NAME) DataSource dataSource,
			TransactionManagerCustomizers transactionManagerCustomizers,
			@Qualifier(value = BatchDataSourceConfig.ENTITY_MANAGER_FACTORY_NAME)
					EntityManagerFactory entityManagerFactory,
			@Qualifier(value = BatchDataSourceConfig.TRANSACTION_MANAGER_NAME)
					PlatformTransactionManager transactionManager) {
		super(properties, dataSource, transactionManagerCustomizers, entityManagerFactory);
		this.transactionManager = transactionManager;
	}

	@Override
	protected PlatformTransactionManager createTransactionManager() {
		return transactionManager;
	}
}

해당 글에서는 생략하였지만 BatchDataSourceConfig에서 설정한 DataSource 관련된 설정과

yml, porperties를 통해 설정할 수 있는 배치 설정 요소를 가지고 있는 BatchProperties를 주입하여

프로젝트에 필요한 BatchConfigurer를 만들었습니다.

그럼, 본격적으로 설정을 해봅시다!


BatchMetaDataConfig

스프링 배치를 사용하면 Job과 Step에 대한 정보를 DB에 저장합니다.

이 정보를 메타 데이터라고 하고 이와 관련된 설정 클래스를 BatchMetaDataConfig에 모아두었습니다.


/** DB에 저장되는 Batch 메타 정보와 관련된 설정 클래스 */
@Configuration
public class BatchMetaDataConfig {
  ...
	@Bean(name = JOB_EXPLORER_BEAN_NAME)
	public JobExplorer jobExplorer(
			@Qualifier(value = DELEGATED_BATCH_CONFIGURER_BEAN_NAME) BatchConfigurer configurer)
			throws Exception {
		return configurer.getJobExplorer();
	}

	@Bean(name = JOB_REPOSITORY_BEAN_NAME)
	public JobRepository jobRepository(
			@Qualifier(value = DELEGATED_BATCH_CONFIGURER_BEAN_NAME) BatchConfigurer configurer)
			throws Exception {
		return configurer.getJobRepository();
	}

	@Bean(name = JOB_EXPLORER_BEAN_NAME)
	public JobExplorer jobExplorer(
			@Qualifier(value = DELEGATED_BATCH_CONFIGURER_BEAN_NAME) BatchConfigurer configurer)
			throws Exception {
		return configurer.getJobExplorer();
	}
  
	@Profile(value = "local")
	@Bean(name = BATCH_DATA_SOURCE_SCRIPT_DATABASE_INITIALIZER_BEAN_NAME)
	BatchDataSourceScriptDatabaseInitializer batchDataSourceInitializer(
			@Qualifier(value = DATASOURCE_NAME) DataSource dataSource,
			@Qualifier(value = PROPERTY_BEAN_NAME) BatchProperties properties) {
		return new BatchDataSourceScriptDatabaseInitializer(dataSource, properties.getJdbc());
	}
}

JobRegistry는 생성된 Job을 자동으로 등록, 추적 및 관리합니다.

JobRepository는 배치 작업 중 정보를 저장하는 저장소 역할을 하고 Job 수행과 관련된 모든 메타 데이터를 저장합니다.

JobExplorer는 JobRepository의 readonly 버전으로 실행 중인 Job의 실행 정보인 JobExecution 또는 Step의 실행 정보인 StepExecution을 조회할 수 있습니다.


그리고 BatchDataSourceScriptDatabaseInitializer는 위에서 만들어지는 정보를 저장하는 스키마를 생성해 주는 설정입니다.

이때 스프링 배치를 사용하려면 해당 스키마가 꼭 필요합니다.

그렇기에 해당 프로젝트에서는 BatchDataSourceScriptDatabaseInitializer의프로파일을 local로 한정하여 local의 경우는 BatchDataSourceScriptDatabaseInitializer를 통해 간단히 스키마를 생성하여 사용할 수 있도록 하였고

이외의 프로파일에서는 Flyway를 통해 스키마를 구성할 수 있도록 하였습니다.


BatchFactoryConfig

BatchFactoryConfig는 배치를 구성하기 위해 필요한 Job과 Step을 생성하기 위한 설정하기 위한 클래스입니다.


/** Batch 팩토리 설정 클래스 */
@Configuration
public class BatchFactoryConfig {
  ...
	@Bean(name = JOB_BUILDER_FACTORY_BEAN_NAME)
	public JobBuilderFactory jobBuilderFactory(
			@Qualifier(value = DELEGATED_BATCH_CONFIGURER_BEAN_NAME) BatchConfigurer configurer)
			throws Exception {
		return new JobBuilderFactory(configurer.getJobRepository());
	}

	@Bean(name = STEP_BUILDER_FACTORY_BEAN_NAME)
	public StepBuilderFactory stepBuilderFactory(
			@Qualifier(value = DELEGATED_BATCH_CONFIGURER_BEAN_NAME) BatchConfigurer configurer)
			throws Exception {
		return new StepBuilderFactory(
				configurer.getJobRepository(), configurer.getTransactionManager());
	}
}

JobBuilderFactory와 StepBuilderFactory는 각각 Job과 Step을 생성하는 것을 도와줍니다.


BatchLaunchConfig

마지막으로 BatchLaunchConfig 입니다.

BatchLaunchConfig는 배치의 실행과 관련된 설정을 하는 클래스입니다.


/** Batch 실행 설정 클래스 */
@Configuration
public class BatchLaunchConfig {
  ...
	@Bean(name = JOB_LAUNCHER_BEAN_NAME)
	public JobLauncher jobLauncher(
			@Qualifier(value = DELEGATED_BATCH_CONFIGURER_BEAN_NAME) BatchConfigurer configurer)
			throws Exception {
		SimpleJobLauncher simpleJobLauncher = new SimpleJobLauncher();
		simpleJobLauncher.setJobRepository(configurer.getJobRepository());
		return simpleJobLauncher;
	}
}

JobLauncher는 Job을 실행시키는 역할을 가진 설정 클래스입니다.


마치며

이번 배치 설정 관련된 글을 작성하는 것이 지금까지 작성한 설정 관련 글 중 가장 자신 없이 작성한 것 같습니다.

아마 그렇게 느낀 이유는 배치에 대한 학습이 부족하였기에 그렇게 느낀 것이겠죠?

사실 배치가 어떻게 돌아가는지 알고 싶어 batch-core 부분도 디버깅하며 확인해 보고

다른 배치 구현 레퍼런스들도 찾아보며 공부해 보았는데 아직 잘 이해하지는 못한 것 같습니다.

지금은 이해하지 못하였지만 앞으로 계속 이해하지 못할 것으로 생각하지 않습니다.

자신이 아는 범위를 원의 크기라 하고 충분히 학습한다면 원이 점점 채워진다고 생각해 봅시다.

저는 학습은 자신의 원을 늘려나가는 것으로 생각합니다.

처음 공부할 때 그리는 원은 작은 원이기에 손쉽게 그릴 수 있을 것입니다.

그리고 그 원을 채우기도 쉬울 것입니다.

그럼, 원의 크기를 점점 더 늘려봅시다.

원을 그리기 쉬울까요? 원을 채우기는 쉬울까요??

원은 점점 삐뚤삐뚤하게 그려질 것이고 그 속을 꼼꼼히 채우기란 더 힘들어질 것입니다.

원을 이쁘게 잘 그려온 사람에게 삐뚤삐뚤하게 그려진 원은 외면하고 싶은 것일 겁니다.

많은 사람이 이 삐뚤삐뚤한 원을 보고 싶지 않기에 더 원을 늘이려 하지 않습니다.

저 역시 그러하였습니다.

그럼, 조금 덜 삐뚤삐뚤하게 원을 늘려나가려면 어떻게 해야 할까요?

저는 원을 늘려나가는 것에 대해서는 도움을 받아야 한다고 생각합니다.

실제로 원을 그릴 때 컴퍼스와 같은 도구에 도움을 받듯이 우리는 자신보다 먼저 원을 늘려나간 사람에게 도움을 받으며 조금씩 원을 늘려나가야 합니다.

그렇게 원의 크기를 늘렸다면 어떻게 해야 하죠? 채워야 하죠!

채우는 것은 다른 누가 함께 해주지 않습니다.

스스로 해야 합니다.

늘려나간 원을 채우다 보면 보이지 않던 기존 원의 채워지지 않은 부분도 보이게 되고 다시 한번 원을 꼼꼼히 채워 나갈 수 있습니다.

저는 이번 프로젝트 설정과 관련된 글을 작성하며 이전에는 설정에 관해 잘 알지 못하여 레퍼런스에 맞추어 프로젝트를 변경하였다면 이제는 프로젝트에 맞추어 설정할 수 있게 되었습니다.

조금 저의 원을 늘린 것이죠..?!ㅎㅎㅎ

앞으로 프로젝트를 진행하며 늘린 저의 원을 조금씩 채워나가 보도록 하겠습니다.

이렇게 채워나가다 보면 지금은 이해되지 않았던 부분들도 다시 보았을 때는 이해할 수 있겠죠?

감사합니다.

0개의 댓글