Contents
개요1. Configuring a Job2. Restartablitiy3. Interception Job Execution✅ 왜 오류가 발생하지 않는가?3. Inheriting from a Parent Job4. JobParametersValidator5. DefaultBatchConfiguration6. JobRepository6-1. @EnableBatchProcessing6-2. DefaultBatchConfiguration6-3. Transaction Isolation Level Config (선언적 방식)6-4. Transaction Behavior Config (코드적 방식)7. Table Prefix8. JobLuancher8-1 Async Execution마무리Reference개요
지난 시간에는 Spring Batch의 기본 용어, 개념들을 알아보았다.
이번 포스팅은 Spring Batch의 기본 설정 방법이다. 각 설정의 작동 의도와, 파라미터들을 알아보겠다.
또한 설정 방법으로는 크게 2가지(XML, Java-Based)가 있고, 개인적으론 XML방식보다는 코드 기반이 보기 편하다고 생각하여 선호하기 때문에, 코드 기반으로 설정하는 방법을 알아보겠다.
1. Configuring a Job
@Bean
public Job footballJob(JobRepository jobRepository) {
return new JobBuilder("footballJob", jobRepository)
.start(playerLoad())
.next(gameLoad())
.next(playerSummarization())
.build();
}
Job은 JobRepository가 필요하다.
예제 코드에서 생성한 footballJob은 3개의 Step으로 이루어져 있다.
- playerLoad
- gameLoad
- playerSummarization
앞서 개념&용어 정리에서 Job은 1개 이상의 Step으로 구성되어 있다고 했다.
start, next의 인자는 Step을 받는다.
따라서 JobBuilder에서 초기 Step인 start를 지정해주지 않는다면 당연히 build가 불가능하다.
2. Restartablitiy
배치 작업을 실행할 때 가장 중요한 문제 중 하나는, Job이 재시작될 때의 동작이다.
특정 Job A에 대해 A가 이미 존재하는 경우, A를 실행하는 것은 재시작으로 간주한다.
이상적으로는 모든 작업이 중단된 위치에서 다시 시작될 수 있도록 보장해야 하지만, 그렇지 않은 경우도 있다. 만약 Job이 재시작할 수 없고, 매 번 새롭게 생성된 JobInstance를 실행하도록 보장하려면 restartable 속성을 false로 지정하면 된다.
@Bean
public Job footballJob(JobRepository jobRepository) {
return new JobBuilder("footballJob", jobRepository)
.preventRestart()
...
.build();
}
preventRestart() 설정이 restartable 속성을 false로 지정하는 것이다.
이는 Job을 다시 시작할 수 없음을 의미하고, 만약 이렇게 설정된 Job을 재시작하려고 하면
JobRestartException
이 발생한다.3. Interception Job Execution
Spring Batch에서는 Job을 실행하는 동안 사용자 정의 코드를 실행할 수 있도록 생명 주기에 존재하는 다양한 이벤트의 알림을 제공한다.
Listener를 동작하게 하는 방법은 2가지가 있다.
첫번째로는 JobExecutionListener 인터페이스를 구현하고, 등록하는 것이다.
public interface JobExecutionListener {
void beforeJob(JobExecution jobExecution);
void afterJob(JobExecution jobExecution);
}
@Bean
public Job footballJob(JobRepository jobRepository) {
return new JobBuilder("footballJob", jobRepository)
.listener(sampleListener())
...
.build();
}
JobExecutionListner 인터페이스를 구현하고, 설정에서 listner를 지정해주면 된다.
리스너를 동작하게 하는 두번째 방법은, 커스텀 리스너 클래스를 만들고,
@BeforeJob, @AfterJob
어노테이션을 사용하는 것이다.@Component
public class JobLoggerListener {
@BeforeJob
public void beforeJob(JobExecution jobExecution) {
System.out.println(">>> Job is about to start: " + jobExecution.getJobInstance().getJobName());
}
}
이런식으로 어노테이션기반 리스너를 동작하게 할 수 있다.
또한 인터페이스 기반 리스너가 아닌, 커스텀 리스너 클래스에서 어노테이션을 사용하지 않으면, 컴파일 과정에서 오류가 발생하거나, IDE가 잡아줄지 알았는데, 아니였다.
GPT에게 질의한 결과 그 이유는 다음과 같다.
✅ 왜 오류가 발생하지 않는가?
1. listener()
메서드는 Object 타입을 받는다
jobBuilder.listener(Object listener)
- 이 메서드는
Object
타입을 받아서 리플렉션으로 어노테이션 기반 리스너(@BeforeJob/@AfterJob) 또는 인터페이스 기반 리스너인지 판단합니다.
- 해당 리스너에서 아무 것도 인식되지 않으면 그냥 무시합니다.
- 따라서, 리스너가 유효하지 않더라도 예외를 던지지 않고 무시하는 것이 기본 동작입니다.
2. Spring Batch는 유연한 설계를 채택하고 있다
- Spring Batch는
JobExecutionListener
를 강제하지 않음.
- 대신 다음 중 하나라도 존재하면 그 메서드를 실행:
@BeforeJob
어노테이션이 붙은 메서드JobExecutionListener.beforeJob()
을 오버라이드한 메서드
그 외는 단순 리스너 객체일 뿐이므로 아무 행동도 하지 않습니다.
즉, 등록은 되지만 실행될 메서드가 없으면 그냥 무시합니다
중요한건 아니지만, 어쨌든 어노테이션 기반 리스너를 등록하지 않는다면, 의도한 동작대로 작동하지 않는다. 또한 오류가 발생하지 않고, 아무런 동작도 하지 않은 채 넘어간다. 이는 Spring Batch에서 의도된 유연성이다.
3. Inheriting from a Parent Job
만약 여러개의 Job이 특정 구성을 공유하는 경우, 부모를 정의하여 간소화 할 수 있다.
이런식으로 설정하면 된다.
<job id="baseJob" abstract="true">
<listeners>
<listener ref="listenerOne"/>
</listeners>
</job>
<job id="job1" parent="baseJob">
<step id="step1" parent="standaloneStep"/>
<listeners merge="true">
<listener ref="listenerTwo"/>
</listeners>
</job>
부모 Job을 설정하는 것은, XML방식밖에 지원하지 않는데, 사실 생각해보면 Java의 특징이기 때문에 Java에서 상속을 추가로? 지원한다는 것은 매우 이상하긴 하다.
4. JobParametersValidator
XML namespace에 정의되거나 AbstractJob의 서브클래스를 사용하는 job은 선택적으로 런타임에 job parameter를 위해 validator를 선언할 수 있다. 예를 들어 보두 의무적인 파라미터를 가지고 Job이 실행되는지 확인해야 할 때 유용하다.
간단한 필수적이고 선택적인 파라미터들의 결합을 제한할 수 있는 DefaultJobParametersValidator이 있다. 더 복잡한 제약사항에 대해 자신만의 인터페이스를 구현할 수 있다.
public class CustomJobParametersValidator implements JobParametersValidator {
@Override
public void validate(JobParameters parameters) throws JobParametersInvalidException {
String fileName = parameters.getString("fileName");
if (fileName == null || fileName.isEmpty()) {
throw new JobParametersInvalidException("fileName parameter is required.");
}
if (!fileName.endsWith(".csv")) {
throw new JobParametersInvalidException("fileName must be a CSV file.");
}
}
}
만약 검증이 필요하다면 JobParametersValidator를 구현하면 된다.
@Bean
public Job job1(JobRepository jobRepository) {
return new JobBuilder("job1", jobRepository)
.validator(parametersValidator())
...
.build();
}
그리고 설정에서 validator를 추가할 수 있다.
5. DefaultBatchConfiguration
SpringBatch5 부터 지원하는 기본 설정 클래스이다.
기존에는 @EnableBatchProcessing 어노테이션을 사용해 선언적 설정을 사용했었다.
DefaultBatchConfiguration 클래스를 상속하여 설정들을 커스터마이징 할 수 있다.
@Configuration
class MyJobConfiguration extends DefaultBatchConfiguration {
@Bean
public Job job(JobRepository jobRepository) {
return new JobBuilder("job", jobRepository)
// define job flow as needed
.build();
}
@Override
protected Charset getCharset() {
return StandardCharsets.ISO_8859_1;
}
}
@EnableBatchProcessing과 DefaultBatchConfiguration을 함께 사용해서는 안된다. 선언적 방식과 프로그래밍 방식 중 하나만 선택해서 사용해야 한다.
6. JobRepository
Spring Batch의 기본 설정을 사용할 경우, JobRepository는 기본적으로 제공하고, 관리된다.
만약 JobRepository의 설정을 커스터마이징 하고싶다면 아래와 같은 방법으로 하면 된다.
6-1. @EnableBatchProcessing
@Configuration
@EnableBatchProcessing(
dataSourceRef = "batchDataSource",
transactionManagerRef = "batchTransactionManager",
tablePrefix = "BATCH_",
maxVarCharLength = 1000,
isolationLevelForCreate = "SERIALIZABLE")
public class MyJobConfiguration {
// job definition
}
6-2. DefaultBatchConfiguration
@Configuration
public class MyBatchConfig extends DefaultBatchConfiguration {
@Autowired
private DataSource batchDataSource;
@Autowired
private PlatformTransactionManager batchTransactionManager;
@Override
protected DataSource getDataSource() {
return batchDataSource;
}
@Override
protected PlatformTransactionManager getTransactionManager() {
return batchTransactionManager;
}
@Override
protected String getTablePrefix() {
return "BATCH_";
}
@Override
protected int getMaxVarCharLength() {
return 1000;
}
@Override
protected String getIsolationLevelForCreate() {
return "ISOLATION_SERIALIZABLE";
}
}
둘을 비교했을 때 설정 시 동적인 로직이 필요한 것 아니라면, 비교적 깔끔한 선언적 방식이 나아보인다.
6-3. Transaction Isolation Level Config (선언적 방식)
또한 기억해야 할 건 JobRepository의 기본 트랜잭션 격리 수준은
ISOLATION_SERIALIZABLE
이다.같은 JobInstance간의 동시 실행을 방지하기 위해서 기본적으로 격리성 수준을 가장 높게 설정하는것 같다. 만약 Job실행 간의 동시성문제가 발생하지 않는다면, 아래와 같이 격리 수준을 설정할 수 있다.
@Configuration
@EnableBatchProcessing(isolationLevelForCreate = "ISOLATION_REPEATABLE_READ")
public class MyJobConfiguration {
// job definition
}
6-4. Transaction Behavior Config (코드적 방식)
트랜잭션 전파 속성등을 설정하고 싶다면 아래와 같은 방식으로 가능하다.
@Bean
public TransactionProxyFactoryBean baseProxy() {
TransactionProxyFactoryBean transactionProxyFactoryBean = new TransactionProxyFactoryBean();
Properties transactionAttributes = new Properties();
transactionAttributes.setProperty("*", "PROPAGATION_REQUIRED");
transactionProxyFactoryBean.setTransactionAttributes(transactionAttributes);
transactionProxyFactoryBean.setTarget(jobRepository());
transactionProxyFactoryBean.setTransactionManager(transactionManager());
return transactionProxyFactoryBean;
}
7. Table Prefix
JobRepository 메타데이터 테이블의 접두사를 변경할 수 있다.
기본 테이블 Prefix는
BATCH_
로 되어있다. 만약 이를 변경하고 싶을 때는 아래와 같이 하면 된다.@Configuration
@EnableBatchProcessing(tablePrefix = "SYSTEM.TEST_")
public class MyJobConfiguration {
// job definition
}
이렇게 변경했을 경우 메타데이터 테이블에 대한 모든 쿼리는
SYSTEM.TEST_BATCH_JOB_EXECUTION
와 같은 방식으로 변경된다.8. JobLuancher
SpringBatch 기본 설정을 했다면 JobRegistry가 기본적으로 제공된다.
이 섹션에서는 JobRepository와 마찬가지로 커스터마이징 하는 방법에 대해 알아본다.
가장 기본적인 구성은 아래와 같다.
...
@Bean
public JobLauncher jobLauncher() throws Exception {
TaskExecutorJobLauncher jobLauncher = new TaskExecutorJobLauncher();
jobLauncher.setJobRepository(jobRepository);
jobLauncher.afterPropertiesSet();
return jobLauncher;
}
...
8-1 Async Execution

Job은 위와 같은 시퀀스로 실행된다. 하지만 위 시퀀스가 HTTP 요청에서 실행될 때는 심각한 문제가 발생한다. HTTP는 서버의 응답이 필요한 프토토콜이므로, Job이 끝날때까지 HTTP 요청을 열어둬야하고, 이는 서버의 리소스 낭비로 이어진다.

따라서 HTTP 환경에서 Job을 실행할 경우 위와 같은 비동기적 시퀀스가 필요하다.
Client가 Job을 실행하고, 즉시 반환되도록 구성해야 한다. 비동기적으로 작업을 실행하는 방식을 설정하려면 아래와 같이 하면 된다.
@Bean
public JobLauncher jobLauncher() {
TaskExecutorJobLauncher jobLauncher = new TaskExecutorJobLauncher();
jobLauncher.setJobRepository(jobRepository());
jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor());
jobLauncher.afterPropertiesSet();
return jobLauncher;
}
SimpleAsyncTaskExecutor
를 TaskExecutor으로 지정해주면 작업을 비동기적으로 실행할 수 있다.마무리
지금까지 Spring Batch의 기본 개념과, 설정을 알아보았다.
예전에 프로젝트를 하면서 시간 소요가 꽤 걸리는 작업을 HTTP로 트리거할 때, 중간에 Redis를 두고 값을 스케쥴러에서 가져와 실행하는 방식으로 구현했었는데, 공부를 하면서 Spring Batch 환경에서는 같은 문제를 비동기적인 작업 방식을 설정하여 해결하는게 인상깊었다.
당시에 Spring Batch를 제대로 알았더라면 더 나은 구조로 구현할 수 있었을 것 같아 아쉬우면서도, 지금이라도 이를 배우고 익히고 있다는 점에서 의미 있는 시간이었다.
다음은 Spring Batch를 사용하여 직접 작업을 실행시켜보는 포스트로 돌아오겠다.
Reference
Share article