4. Configuring and Running a Job
도메인 장에서는, 다음 다이어그램을 가이드로 사용하여, 전체 아키텍처 설계를 논의했다.
이미지 7. 배치 개념(Stereotypes)
잡(Job)
객체는 스텝(Step)를 위한 단순한 컨테이너처럼 보일 수 있지만 많은 구성 옵션을 알고 있어야 한다. 또한, 잡
을 실행하는 방법과 해당 실행 중에 메타데이터를 저장하는 방법에 대한 많은 옵션을 생각해봐야 한다. 이 장에서는 잡
의 다양한 구성 옵션 및 런타임 문제에 대해 설명한다.
4.1. Configuring a Job
잡 인터페이스에는 여러 구현체가 있다. 그러나, 이러한 구현체는 제공된 빌더(자바 구성) 또는 XML 네임스페이스(XML 기반 구성) 뒤에서 추상화된다. 다음 예는 자바 및 XML 구성을 모두 보여준다:
자바 구성
@Bean
public Job footballJob(JobRepository jobRepository) {
return new JobBuilder("footballJob", jobRepository)
.start(playerLoad())
.next(gameLoad())
.next(playerSummarization())
.build();
}
XML 구성
<job id="footballJob">
<step id="playerload" parent="s1" next="gameLoad"/>
<step id="gameLoad" parent="s2" next="playerSummarization"/>
<step id="playerSummarization" parent="s3"/>
</job>
앞의 예제는 상위(parent) 빈 정의를 사용하여 스텝을 생성한다. 특정 스텝의 세부 정보를 인라인으로 선언할 때의 추가 옵션에 대해서는 스텝 구성 장을 참고하자. XML 네임스페이스는 기본값인 잡리포지터리(jobRepository)
의 id
로 리포지터리를 참조한다. 그러나, 이 기본값을 명시적으로 오버라이드할 수 있다:
<job id="footballJob" job-repository="specialRepository">
<step id="playerload" parent="s1" next="gameLoad"/>
<step id="gameLoad" parent="s3" next="playerSummarization"/>
<step id="playerSummarization" parent="s3"/>
</job>
스텝 외에도, 잡 구성에는 병렬화(<split>
), 선언적 흐름 제어(<decision>
) 및 흐름 정의의 외부화(<flow/>
)에 도움이 되는 다른 엘리먼트가 포함될 수 있다.
4.1.1. Restartability
배치 잡 실행 시 한 가지 중요한 문제는 잡
의 재시작 동작과 관련이 있다. 특정 잡인스턴스(JobInstance)
에 대한 잡익스큐션(JobExecution)
이 이미 존재하는 경우 잡
시작은 “재시작”으로 간주한다. 이상적으로, 모든 잡이 중단된 위치에서 시작할 수 있어야 하지만 이것이 불가능한 상황이 있다. 이 상황에서 새로운 잡인스턴스
가 생성됐는지 확인하는 것은 전적으로 개발자의 몫이다. 그러나, 스프링 배치에서 약간의 도움을 제공한다. 잡
을 재시작하면 항상 새로운 잡인스턴스로 실행해야 하는 경우, restartable 프로퍼티를 false
로 설정할 수 있다.
다음 예는 XML에서 restartable
필드를 false
로 설정하는 방법을 보여준다: XML 구성
<job id="footballJob" restartable="false">
...
</job>
다음 예는 자바에서 restartable
필드를 false
로 설정하는 방법을 보여준다: 자바 구성
@Bean
public Job footballJob(JobRepository jobRepository) {
return new JobBuilder("footballJob", jobRepository)
.preventRestart()
...
.build();
}
다르게 표현하자면, restartable
을 false
로 설정하면 “이 잡
은 재시작을 지원하지 않는다”를 의미한다. 재시작할 수 없는 잡
을 재시작하면 잡리스타트익셉션(JobRestartException)
이 발생한다. 다음 Junit 코드는 예외를 발생시킨다:
Job job = new SimpleJob();
job.setRestartable(false);
JobParameters jobParameters = new JobParameters();
JobExecution firstExecution = jobRepository.createJobExecution(job, jobParameters);
jobRepository.saveOrUpdate(firstExecution);
try {
jobRepository.createJobExecution(job, jobParameters);
fail();
}
catch (JobRestartException e) {
// 예외발생
}
재시작할 수 없는 잡에 대한 잡익셉션(JobExecution)
을 발생시키려는 첫 번째 시도는 문제가 발생하지 않는다. 그러나, 두 번째 시도에서는 잡리스타트익셉션(JobRestartException)
이 발생한다.
4.1.2. Intercepting Job Execution
잡
을 실행하는 동안, 커스텀 코드 실행을 위해 다양한 생명주기 이벤트에 대한 알림을 받는 것이 유용할 수 있다. 심플잡(SimpleJob)
은 적절한 위치에서 잡리스너(JobListener)
를 호출하여 이를 가능하게 한다:
public interface JobExecutionListener {
void beforeJob(JobExecution jobExecution);
void afterJob(JobExecution jobExecution);
}
잡에 리스너(listener)를 설정하여 잡리스너
를 심플잡
에 추가할 수 있다. 다음 예는 XML에서 잡에 리스너를 추가하는 방법을 보여준다: XML 구성
<job id="footballJob">
<step id="playerload" parent="s1" next="gameLoad"/>
<step id="gameLoad" parent="s2" next="playerSummarization"/>
<step id="playerSummarization" parent="s3"/>
<listeners>
<listener ref="sampleListener"/>
</listeners>
</job>
다음 예는 자바 구성에서 잡에 리스너 메서드를 추가하는 방법을 보여준다: 자바 구성
@Bean
public Job footballJob(JobRepository jobRepository) {
return new JobBuilder("footballJob", jobRepository)
.listener(sampleListener())
...
.build();
}
afterJob
메소드는 잡
의 성공 여부에 관계없이 호출된다. 성공 또는 실패를 결정해야 하는 경우, 잡익스큐션(JobExecution)
에서 해당 정보를 얻을 수 있다:
public void afterJob(JobExecution jobExecution){
if (jobExecution.getStatus() == BatchStatus.COMPLETED ) {
//잡 성공
} else if (jobExecution.getStatus() == BatchStatus.FAILED) {
//잡 실패
}
}
이 인터페이스에 해당하는 어노테이션은 다음과 같다:
@BeforeJob
@AfterJob
4.1.3. Inheriting from a Parent Job
자바는 더 나은 재사용 기능을 제공하므로, 이 절에 내용은 XML 구성에서만 사용된다.
유사하지만 동일하지 않은 구성을 가진 잡 그룹의 경우, 구체적인 잡
인스턴스가 프로퍼티를 상속할 수 있도록 “상위” 잡
을 정의하는 것이 도움이 될 수 있다. 자바의 클래스 상속과 유사하게, “하위” 잡
은 상위 잡의 요소, 애트리튜트와 결합할 수 있다.
다음 예에서 baseJob
은 리스너 목록만 정의하는 추상적인 잡
정의이다. 다음 예는 잡(job1)
은 baseJob
에서 리스너 목록을 상속하고 이를 자체 리스너 목록과 병합하여 2개의 리스너와 1개의 스텝(step1)
으로 잡
을 생성한다:
<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>
4.1.4. JobParametersValidator
XML 네임스페이스에서 선언되거나 앱스트랙트잡(AbstractJob)
의 하위 클래스(subclass)를 사용하는 잡은 옵셔널하게 런타임 시 잡 파라미터에 대한 유효성 검사(validator)를 선언할 수 있다. 예를 들어, 모든 필수 파라미터로 잡이 시작되었음을 확인해야 하는 경우 유용하다. 간단한 필수 파라미터와 옵셔널 파라미터의 조합을 제한하는 데 사용할 수 있는 디폴트잡파라미터밸리데이터(DefaultJobParametersValidator)
가 있다. 더 복잡한 제약 조건의 경우, 인터페이스(interface)를 직접 구현할 수도 있다.
유효성 검사의 구성은 다음과 같이 자바 빌더를 통해 지원된다:
@Bean
public Job job1(JobRepository jobRepository) {
return new JobBuilder("job1", jobRepository)
.validator(parametersValidator())
...
.build();
}
XML 네임스페이스 지원은 잡파라미터밸리테이터(JobParametersValidator)
구성에도 사용할 수 있다:
<job id="job1" parent="baseJob3">
<step id="step1" parent="standaloneStep"/>
<validator ref="parametersValidator"/>
</job>
밸리데이터를 참조(reference, 위에 표시된 대로)하거나 빈 네임스페이스의 중첩된 빈 정의로 지정할 수 있다.
4.2. Java Configuration
스프링 3은 XML 대신 자바로 애플리케이션을 구성하는 기능을 가지고 있다. 스프링 배치 2.2.0부터 동일한 자바 구성을 사용하여 배치 잡을 구성할 수 있다. 자바 기반 구성에는 @EnableBatchProcessing
어노테이션과 두 개의 빌더의 세 가지 컴포넌트가 있다.
@EnableBatchProcessing
어노테이션은 스프링 제품군의 다른 @Enable*
어노테이션과 유사하게 작동한다. 이 경우, @EnableBatchProcessing
은 배치 잡을 빌드하기 위한 기본 구성을 제공한다. 이 기본 구성 내에서, 스텝스코프(StepScope)
및 잡스코프(JobScope)
의 인스턴스가 생성되고, 오토와이어드(autowired)될 수 있는 여러 빈이 생성된다:
잡리포지터리(JobRepository)
:jobRepository
라는 이름의 빈(bean)잡런쳐(JobLauncher)
:jobLauncher
라는 이름의 빈잡레지스트리(JobRegistry)
:jobRegistry
라는 이름의 빈잡익스플로러(JobExplorer)
:jobExplorer
라는 이름의 빈잡오퍼레이터(JobOperator)
:jobOperator
라는 이름의 빈
기본 구현(implementation)은 앞의 목록에 언급된 빈을 제공하며 데이터소스(DataSource)
및 플랫폼트랜젝션매니저(PlatformTransactionManager)
가 컨텍스트 내에서 빈으로 제공되어야 한다. 데이터 소스 및 트랜잭션 매니저는 잡리포지터리(JobRepository)
및 잡익스플로러(JobExplorer)
인스턴스에서 사용된다. 기본적으로, 데이터소스(DataSource)
와 트랜젝션매니저(transactionManager)
가 사용된다. @EnableBatchProcessing
어노테이션의 애트리뷰트을 사용하여 이러한 빈을 커스텀할 수 있다. 다음 예는 사용자 정의 데이터 소스 및 트랜잭션 매니져를 제공하는 방법을 보여준다:
@Configuration
@EnableBatchProcessing(dataSourceRef = "batchDataSource", transactionManagerRef = "batchTransactionManager")
public class MyJobConfiguration {
@Bean
public DataSource batchDataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL)
.addScript("/org/springframework/batch/core/schema-hsqldb.sql")
.generateUniqueName(true)
.build();
}
@Bean
public JdbcTransactionManager batchTransactionManager(DataSource dataSource) {
return new JdbcTransactionManager(dataSource);
}
public Job job(JobRepository jobRepository) {
return new JobBuilder("myJob", jobRepository)
//필요에 따라 잡의 흐름 정의
.build();
}
}
하나의 구성 클래스에만
@EnableBatchProcessing
어노테이션이 있어야 한다. 클래스에 어노테이션을 달면, 앞에서 설명한 모든 구성을 갖게 된다.
v5.0부터, 디폴트배치컨피그레이션(DefaultBatchConfiguration)
클래스를 통해 기본 인프라스트럭처 빈을 구성하는 방식의 대안이 제공된다. 이 클래스는 @EnableBatchProcessing
에서 제공하는 동일한 빈을 제공하며 배치 잡을 구성하기 위한 기본 클래스로 사용할 수 있다. 다음 스니펫은 사용 방법에 대한 일반적인 예시다:
@Configuration
class MyJobConfiguration extends DefaultBatchConfiguration {
@Bean
public Job job(JobRepository jobRepository) {
return new JobBuilder("job", jobRepository)
//필요에 따라 잡의 흐름 정의
.build();
}
}
데이터 소스 및 트랜잭션 매니저는 애플리케이션 컨텍스트(application context)에서 확인되고 잡 레지스트리 및 잡 익스플로러에 설정된다. 필요한 setter를 오버라이드하여 인프라스트럭처 빈의 구성을 커스텀할 수 있다. 다음 예는 인스턴스에 대한 문자 인코딩을 커스텀하는 방법을 보여준다:
@Configuration
class MyJobConfiguration extends DefaultBatchConfiguration {
@Bean
public Job job(JobRepository jobRepository) {
return new JobBuilder("job", jobRepository)
//필요에 따라 잡의 흐름 정의
.build();
}
@Override
protected Charset getCharset() {
return StandardCharsets.ISO_8859_1;
}
}
@EnableBatchProcessing
은디폴트배치컨피그레이션(DefaultBatchConfiguration)
과 함께 사용하면 안 된다.@EnableBatchProcessing
을 통해 스프링 배치를 구성하는 선언적(declarative) 방법을 사용하거나,디폴트배치컨피그레이션(DefaultBatchConfiguration)
을 상속하는 프로그래밍 방식을 사용해야 하지만 동시에 두 가지 방법을 모두 사용할 수는 없다.
4.3. Configuring a JobRepository
@EnableBatchProcessing
을 사용하면 잡리포지터리(JobRepository)
가 제공된다. 이 절에서는 직접 구성하는 방법에 대해 설명한다.
앞에서 설명한 것처럼, 잡리포지터리(JobRepository)
는 잡익스큐션(JobExecution)
및 스텝익스큐션(StepExecution)
과 같은 스프링배치 내에서 지속되는 다양한 도메인 객체의 기본 CRUD 작업에 사용된다. 그리고, 잡런처(JobLauncher)
, 잡
및 스텝
과 같은, 많은 주요 프레임워크 기능에 필요하다.
배치 네임스페이스는 잡리포지터리(JobRepository)
구현체는 구현의 세부 정보와 협력 객체를 추상화한다. 그러나, 다음 예와 같이 몇 가지 구성 옵션을 사용할 수 있다:
XML 구성
<job-repository id="jobRepository"
data-source="dataSource"
transaction-manager="transactionManager"
isolation-level-for-create="SERIALIZABLE"
table-prefix="BATCH_"
max-varchar-length="1000"/>
id
외에는, 나열된 구성 옵션은 필요하지 않다. 설정하지 않으면 기본값이 사용된다. max-varchar-length
는 기본값으로 샘플 스키마 스크립트에서 긴 VARCHAR 열의 길이인 2500으로 설정된다.
데이터소스(dataSource)
및 트랜젝션매니저(transactionManager)
외에는 나열된 구성 옵션은 필요하지 않다. 설정하지 않으면, 이전에 표시된 기본값이 사용된다. 최대 varchar
길이는 기본적으로 샘플 스키마 스크립트에 있는 긴 VARCHAR
컬럼의 길이인 2500
이다.
4.3.1. Transaction Configuration for the JobRepository
네임스페이스 또는 제공된 팩토리빈(FactoryBean)
을 사용하면, 잡리포지터리에 트랜잭션 어드바이스(transactional advice)가 자동으로 생성된다. 이것은 실패 후 재시작이 필요한 상태를 포함하여, 배치 메타데이터가 올바르게 유지되도록 하기 위한 것이다. 리포지터리 메서드가 트랜잭션이 아닌 경우 프레임워크의 동작이 잘 정의되지 않는다. create*
메서드 애트리뷰트의 아이솔레이션 레벨(isolation level)은 잡이 시작될 때, 두 프로세스가 동시에 동일한 잡을 시작하려고 시도하는 경우, 하나만 성공하도록 별도로 지정된다. 해당 방법의 기본 아이솔레이션 레벨은 SERIALIZABLE
이며 매우 공격적이다. READ_COMMITTED
는 일반적으로 똑같이 잘 작동한다. 두 프로세스가 충돌할 가능성이 없으면 READ_UNCOMMITTED
가 좋다. 그러나 create*
메소드에 대한 호출은 매우 짧기 때문에 SERIALIZED
는 데이터베이스 플랫폼이 지원하는 한 문제를 일으킬 가능성이 없다. 그러나 이 설정을 오버라이드(override)할 수 있다.
다음 예에서는 XML에서 아이솔레이션 레벨을 오버라이드하는 방법을 보여준다:
XML 구성
<job-repository id="jobRepository"
isolation-level-for-create="REPEATABLE_READ" />
다음 예에서는 자바에서 아이솔레이션 레벨을 오버라이드하는 방법을 보여준다:
자바 구성
@Configuration
@EnableBatchProcessing(isolationLevelForCreate = "ISOLATION_REPEATABLE_READ")
public class MyJobConfiguration {
// 잡 정의
}
네임스페이스를 사용하지 않는 경우, AOP를 사용하여 리포지터리의 트랜잭션 동작도 구성해야 한다.
다음 예는 XML에서 리포지터리의 트랜잭션 동작을 구성하는 방법을 보여준다: XML 구성
<aop:config>
<aop:advisor pointcut="execution(* org.springframework.batch.core..*Repository+.*(..))"/>
<advice-ref="txAdvice" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" />
</tx:attributes>
</tx:advice>
변경 사항이 거의 없이 앞의 코드 조각(fragment)을 거의 그대로 사용할 수 있다. 또한 적절한 네임스페이스를 선언하고, spring-tx
및 spring-aop
(또는 스프링 전체)가 클래스 패스에 있는지 확인하자.
다음 예는 자바에서 리포지터리의 트랜잭션 동작을 구성하는 방법을 보여준다: 자바 구성
@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;
}
4.3.2. Changing the Table Prefix
잡리포지터리(JobRepository)
의 수정 가능한 다른 프로퍼티는 메타 데이터 테이블의 테이블 접두사이다. 기본적으로, 모두 BATCH_.BATCH_JOB_EXECUTION
으로 시작되고 BATCH_STEP_EXECUTION
는 두 가지 예가 있다. 그러나, 이 접두사를 수정해야 하는 이유가 있다. 스키마명을 테이블명 앞에 추가해야 하거나 동일한 스키마 내에서 둘 이상의 메타데이터 테이블 세트가 필요한 경우, 테이블 접두사를 변경해야 한다.
다음 예는 XML에서 테이블 접두사를 변경하는 방법을 보여준다: XML 구성
<job-repository id="jobRepository"
table-prefix="SYSTEM.TEST_" />
다음 예는 자바에서 테이블 접두사를 변경하는 방법을 보여준다:
자바 구성
@Configuration
@EnableBatchProcessing(tablePrefix = "SYSTEM.TEST_")
public class MyJobConfiguration {
// 잡 정의
}
앞의 변경 사항을 감안할 때, 메타데이터 테이블에 대한 모든 쿼리는 SYSTEM.TEST_.BATCH_JOB_EXECUTION
접두사가 붙고 SYSTEM.TEST_JOB_EXECUTION
이라고 한다.
테이블 접두사만 구성할 수 있다. 테이블 및 컬럼명은 변경이 불가능하다.
4.3.3. Non-standard Database Types in a Repository
지원되는 목록에 없는 데이터베이스 플랫폼을 사용하는 경우, SQL 변수가 비슷한 경우, 지원되는 타입 중 하나를 사용할 수 있다. 이렇게 하려면, 네임스페이스로 바로 가는 것 대신 잡리포지터리팩토리빈(JobRepositoryFactoryBean)
을 사용하고 데이터베이스 타입이 가장 일치하는 것으로 설정할 수 있다.
다음 예제는 XML에서 잡리포지터리팩토리빈(JobRepositoryFactoryBean)
을 사용하여 가장 일치하는 데이터베이스 타입으로 설정하는 방법을 보여준다: XML 구성
<bean id="jobRepository" class="org...JobRepositoryFactoryBean">
<property name="databaseType" value="db2"/>
<property name="dataSource" ref="dataSource"/>
</bean>
다음 예제는 자바에서 잡리포지터리팩토리빈(JobRepositoryFactoryBean)
을 사용하여 가장 일치하는 데이터베이스 타입으로 설정하는 방법을 보여준다: 자바 구성
@Bean
public JobRepository jobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(dataSource);
factory.setDatabaseType("db2");
factory.setTransactionManager(transactionManager);
return factory.getObject();
}
데이터베이스 타입이 지정되지 않은 경우,잡리포지터리팩토리빈(JobRepositoryFactoryBean)
은 데이터소스(DataSource)
에서 데이터베이스 타입을 자동 감지하려고 시도한다. 플랫폼 간 주요 차이점은 프라이머리 키(primary key)를 증가시키는 전략이므로 인크리먼터팩토리(incrementerFactory)
(스프링 프레임워크의 표준 구현 중 하나를 사용하여)도 오버라이드(override)해야 하는 경우가 많다.
그래도 작동하지 않거나 RDBMS를 사용하지 않는 경우, 유일한 옵션은 심플잡리포지터리(SimpleJobRepository)
가 의존하는 다양한 Dao 인터페이스를 구현하여 일반적인 스프링 방식으로 수동 연결하는 것일 수 있다.
4.4. Configuring a JobLauncher
@EnableBatchProcessing
을 사용하면, 잡레지스트리(JobRegistry)
가 제공된다. 이 장에서는 직접 구성하는 방법에 대해 설명한다.
잡런처(JobLauncher)
인터페이스(interface)의 가장 기본 구현체(implementation)는 태스크익스큐터잡런처(TaskExecutorJobLauncher)
이다. 유일한 필수 의존성은 잡리포지터리(JobRepository)
(실행하는 데 필요함)이다.
다음 예는 XML의 태스크익스큐터잡런처(TaskExecutorJobLauncher)
를 보여준다: XML 구성
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.TaskExecutorJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
다음 예제는 자바의 태스크익스큐터잡런처(TaskExecutorJobLauncher)
를 보여준다: 자바 구성
@Bean
public JobLauncher jobLauncher() throws Exception {
TaskExecutorJobLauncher jobLauncher = new TaskExecutorJobLauncher();
jobLauncher.setJobRepository(jobRepository);
jobLauncher.afterPropertiesSet();
return jobLauncher;
} ...
잡익스큐션(JobExecution)
이 확보되면, 다음 이미지와 같이 잡
의 익스큐트(execute) 메소드로 전달되어, 궁극적으로 잡익스큐션(JobExecution)
을 호출자(caller)에게 반환한다: 이미지 8. 잡 런처(Job Launcher) 시퀀스 다이어그램
시퀀스 다이어그램은 간단하며 스케줄러에서 시작할 때 잘 작동한다. 그러나, HTTP 요청을 시작하려고 할 때 문제가 발생한다. 이 상황에서는 태스크익스큐터잡런처(TaskExecutorJobLauncher)
가 호출자에게 즉시 반환되도록 실행을 비동기적으로 수행해야 한다. 느린 실행 프로세스(예: 배치 잡)에 필요한 시간 동안 HTTP 요청을 열린 상태로 유지하는 것은 좋지 않기 때문이다. 다음 이미지는 예제 시퀀스 다이어그램를 보여준다:
이미지 9. 비동기 잡 실행 시퀀스 다이어그램
태스크익스큐터(TaskExecutor)
를 구성하여 이 상황에서 잘 작동하도록 태스크익스큐터잡런처(TaskExecutorJobLauncher)
를 구성할 수 있다.
다음 XML 예제는 즉시 반환하도록 태스크익스큐터잡런처(TaskExecutorJobLauncher)
를 구성한다: XML 구성
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.TaskExecutorJobLauncher">
<property name="jobRepository" ref="jobRepository" />
<property name="taskExecutor">
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor" />
</property>
</bean>
다음 자바 예제는 즉시 반환(return)하도록 태스크잡익스큐터잡런처(TaskExecutorJobLauncher)
를 구성한다: 자바 구성
@Bean
public JobLauncher jobLauncher() {
TaskExecutorJobLauncher jobLauncher = new TaskExecutorJobLauncher();
jobLauncher.setJobRepository(jobRepository());
jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor());
jobLauncher.afterPropertiesSet();
return jobLauncher;
}
스프링 태스크익스큐터(TaskExecutor)
인터페이스의 모든 구현을 사용하여 잡이 비동기적으로 실행되는 방식을 제어할 수 있다.
4.5. Running a Job
최소한, 배치 잡을 시작하려면 시작할 잡
과 잡런처(JobLauncher)
두 가지가 필요하다. 둘 다 동일한 컨텍스트(context) 또는 다른 컨텍스트 내에 포함될 수 있다. 예를 들어 커맨드에서 잡을 시작하면, 각 잡
에 대해 새 JVM이 인스턴스화된다. 따라서, 모든 잡에는 자체 잡런처(JobLauncher)
가 있다. 그러나, HttpRequest
범위 에 있는 웹 컨테이너 내에서 실행하는 경우 잡을 시작하기 위해 여러 요청이 호출하는 하나의 잡런처(JobLauncher)
(비동기 잡 시작을 위해 구성)가 있다.
4.5.1. Running Jobs from the Command Line
엔터프라이즈 스케줄러에서 잡을 실행하려는 경우, 커맨드라인이 기본 인터페이스이다. 이는 대부분의 스케줄러(쿼츠(Quartz)를 제외하고, 네이티브잡(NativeJob)
을 사용하지 않는 한)가 주로 쉘 스크립트로 시작되는, 운영 체제 프로세스와 직접 작동하기 때문이다. 펄(Perl), 루비(Ruby) 또는 앤트(Ant) 또는 메이븐(Maven)과 같은 빌드 도구와 셸 스크립트 외에 자바 프로세스를 시작하는 방법은 여러 가지가 있다. 그러나 대부분의 사람들이 쉘 스크립트에 익숙하기 때문에 이 예제에서는 쉘 스크립트에 초점을 맞춘다.
The CommandLineJobRunner
잡을 시작하는 스크립트는 JVM(자바 버추얼 머신)을 시작해야 하므로, 기본 진입점 역할을 하는 메인
메서드가 있는 클래스가 있어야 한다. 스프링 배치는 이 목적을 수행하는 구현을 제공한다: 커맨트라인잡런처(CommandLineJobRunner)
. 이는 애플리케이션을 부트스트랩하는 한 가지 방법일 뿐이다. 자바 프로세스를 시작하는 방법에는 여러 가지가 있으며, 이 클래스를 필수적인 것으로 간주해서는 안 된다. 커맨트라인잡런처(CommandLineJobRunner)
는 네 가지 태스크를 수행한다.
- 적절한
애플리케이션컨텍스트(ApplicationContext)
를 로드. - 커맨드 라인 아규먼트를
잡파라미터(JobParameters)
로 파싱. - 아규먼트를 기반으로 적절한 잡을 찾음.
- 애플리케이션 컨텍스트에 제공된
잡런처(JobLauncher)
를 사용하여 잡 시작.
이러한 모든 태스크는 전달된 아규먼트로만 수행된다. 다음 표에는 필수 아규먼트가 설명되어 있다:
테이블 14. 커맨드라인잡러너(CommandLineJobRunner)
아규먼트
jobPath | 애플리케이션컨텍스트(ApplicationContext) 를 생성하는 데 사용되는 XML 파일의 위치이다. 이 파일에는 전체 잡을 실행하는 데 필요한 모든 것이 포함되어야 한다. |
jobName | 실행할 잡의 이름이다. |
이러한 아규먼트는, 경로(path)를 첫 번째로 이름(name)을 두 번째로 지정하여 전달해야 한다. 이후의 모든 아규먼트는 잡 파라미터로 간주되고, 잡파라미터(JobParameters)
객체로 변환되며 name=value
형식이어야 한다.
다음 예는 XML로 정의된 잡에 잡 파리미터로 전달된 날짜를 보여준다:
<bash$ java CommandLineJobRunner endOfDayJob.xml endOfDay schedule.date=2007-05-05,java.time.LocalDate
다음 예는 자바로 정의된 잡에 잡 파라미터로 전달된 날짜를 보여준다:
<bash$ java CommandLineJobRunner io.spring.EndOfDayJobConfiguration endOfDay schedule.date=2007-05-05,java.time.LocalDate
기본적으로,
커맨드라인잡러너(CommandLineJobRunner)
는 키/값 쌍을 식별 잡 파라미터로 변환하는디폴트잡파라미터컨버터(DefaultJobParametersConverter)
를 사용한다. 그러나, 각각true
또는false
접미사를 추가하여 식별 잡 파라미터와 식별하지 않는 잡 파라미터를 명시적으로 지정할 수 있다.
다음 예에서, schedule.date
는 식별 잡 파라미터인 반면, vendor.id
는 그렇지 않다:
<bash$ java CommandLineJobRunner endOfDayJob.xml endOfDay \
schedule.date=2007-05-05,java.time.LocalDate,true \
vendor.id=123,java.lang.Long,false
<bash$ java CommandLineJobRunner io.spring.EndOfDayJobConfiguration
endOfDay \
schedule.date=2007-05-05,java.time.LocalDate,true \
vendor.id=123,java.lang.Long,false
커스텀 잡파라미터컨버터(JobParametersConverter)
를 사용하여 이 동작을 오버라이드(override)할 수 있다.
대부분의 경우, 매니페스트(manifest)를 사용하여 jar에서 메인(main)
클래스를 선언할 수 있다. 그러나, 단순화를 위해, 클래스를 직접 사용했다. 이 예에서는 The Domain Language of Batch의 EndOfDay
예를 사용한다. 첫 번째 아규먼트는 잡이 구성된 위치(XML 파일 또는 정규화된 클래스 이름)이다. 두 번째 아규먼트인, endOfDay
는, 잡 이름을 나타낸다. 마지막 아규먼트인, schedule.date=2007-05-05,java.time.LocalDate는, java.time.LocalDate
타입의 잡파라미터(JobParameter)
객체로 변환된다.
다음 예는 XML의 endOfDay
에 대한 샘플 구성을 보여준다:
XML 구성
<job id="endOfDay">
<step id="step1" parent="simpleStep" />
</job>
<!-- 명확성을 위해 런처 세부 정보를 제거했다. -->
<beans:bean id="jobLauncher" class="org.springframework.batch.core.launch.support.TaskExecutorJobLauncher"
/>
다음 예는 자바의 endOfDay
에 대한 샘플 구성을 보여준다: 자바 구성
@Configuration
@EnableBatchProcessing
public class EndOfDayJobConfiguration {
@Bean
public Job endOfDay(JobRepository jobRepository, Step step1) {
return new JobBuilder("endOfDay", jobRepository)
.start(step1)
.build();
}
@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new StepBuilder("step1", jobRepository)
.tasklet((contribution, chunkContext) -> null, transactionManager)
.build();
}
}
앞의 예제는 일반적으로, 스프링 배치에서 배치 잡을 실행하는 데 더 많은 요구 사항이 있기 때문에 지나치게 단순하지만, 커맨드라인잡러너(CommandLineJobRunner)
의 두 가지 주요 요구 사항인 잡
및 잡런처(JobLauncher)
를 보여준다.
Exit Codes
커맨드 라인에서 배치 잡을 시작할 때, 엔터프라이즈 스케줄러가 자주 사용된다. 대부분의 스케줄러는 상당히 멍청하며 프로세스 레벨에서만 작동한다. 이는 일부 운영 체제 프로세스(예: 호출하는 셸 스크립트)에 대해서만 알고 있음을 의미한다. 이 상황에서, 잡의 성공 또는 실패에 대해 스케줄러와 다시 통신할 수 있는 유일한 방법은 반환 코드(return code)를 통한것이다. 반환 코드는 실행 결과를 나타내기 위해 프로세스가 스케줄러에 반환하는 숫자이다. 가장 간단한 경우, 0은 성공이고 1은 실패이다. 그러나, “잡 A가 4를 반환하면, 잡 B를 시작하고, 5를 반환하면, 잡 C를 시작한다.”와 같은 더 복잡한 시나리오가 있을 수 있다. 이러한 타입의 동작은 스케줄러 레벨에서 구성되지만, 스프링 배치 같은 프레임워크가 특정 배치 잡에 대한 종료 코드의 숫자 표현을 반환하는 방법을 제공하는 것이 중요하다. 스프링 배치에서 이것은 엑시트스테이터스(ExitStatus)
로 캡슐화되며, 이에 대해서는 5장에서 자세히 다룬다. 종료 코드(exit code)를 논의하기 위해, 알아야 할 중요 사항은 엑시트스테이터스(ExitStatus)
에 프레임워크(또는 개발자)가 설정하고 잡런처(JobLauncher)
에서 반환된 잡익스큐션(JobExecution)
의 일부로 반환되는 종료 코드 프로퍼티가 있다는 것이다. 커맨드라인잡러너(CommandLineJobRunner)
는 엑시트코드매퍼(ExitCodeMapper)
인터페이스를 사용하여 이 문자열 값을 숫자로 변환한다:
public interface ExitCodeMapper {
public int intValue(String exitCode);
}
엑시트코드매퍼(ExitCodeMapper)
의 핵심은, 문자열 종료 코드가 주어지면, 숫자 표현이 반환된다는 것이다. 잡 러너(job runner)가 사용하는 기본 구현(implementation)은 완료에 대해 0, 일반 오류에 대해 1, 제공된 컨텍스트에서 잡을 찾을 수 없는 것과 같은 모든 잡 러너 오류에 대해 2를 반환하는 심플Jvm엑시트코드매퍼(SimpleJvmExitCodeMapper)
이다. 위의 세 값보다 더 복잡한 값이 필요한 경우, 엑시트코드매퍼(ExitCodeMapper)
인터페이스의 커스텀 구현체를 제공해야 한다. 커맨드라인잡러너(CommandLineJobRunner)
는 애플리케이션컨텍스트(ApplicationContext)
를 생성하는 클래스이므로, ‘함께 와이어드(wired)’될 수 없고, 오버라이드(overwritten)한 모든 값이 오토와이어드(autowired)되어야 한다. 이것은 엑시트코드매퍼(ExitCodeMapper)
구현체가 빈팩토리(BeanFactory)
내에서 발견되면, 컨텍스트가 생성된 후 러너(runner)에 주입됨을 의미한다. 엑시트코드매퍼(ExitCodeMapper)
를 제공하기 위해 수행하는 모든 작업은 구현체를 루트 레벨 빈으로 선언하고 러너에 의해 로드되는 애플리케이션컨텍스트(ApplicationContext)
의 일부인지 확인하는 것이다.
4.5.2. Running Jobs from within a Web Container
앞에서 설명한 것처럼, 오프라인 처리(예: 배치 잡)는 커맨드라인에서 시작됐다. 그러나, HttpRequest로 하는 것이 더 나은 경우가 많다. 이러한 많은 사용 사례에는 리포팅(reporting), 임시 잡(ad-hoc job) 실행 및 웹 애플리케이션 지원이 포함된다. 배치 잡(정의상)은 오랬동안 실행되므로 가장 중요한 관심사는 잡을 비동기식으로(asynchronously) 시작하는 것이다:
이미지 10. 웹 컨테이너의 비동기 잡 런처 시퀀스 다이어그램
이 상황에서 컨트롤러는 스프링 MVC 컨트롤러이다. 스프링 MVC에 대한 자세한 내용은 스프링 프레임워크 레퍼런스 가이드를 참고하자. 컨트롤러는 비동기식으로 시작하도록 구성된 잡런처(JobLauncher)
를 사용하여 잡을 시작하며, 잡익스큐션(JobExecution)
을 즉시 반환한다. 잡이 아직 실행 중일 수 있다. 그러나, 이 논블로킹 동작을 통해 컨트롤러가 즉시 반환(return)할 수 있으며, 이는 HttpRequest를 처리할 때 필요하다. 다음은 예시를 보여준다:
@Controller
public class JobLauncherController {
@Autowired
JobLauncher jobLauncher;
@Autowired
Job job;
@RequestMapping("/jobLauncher.html")
public void handle() throws Exception{
jobLauncher.run(job, new JobParameters());
}
}
4.6. Advanced Metadata Usage
지금까지, 잡런처(JobLauncher)
및 잡리포지터리(JobRepository)
인터페이스에 대해 논의했다. 다음 그림은, 잡의 간단한 시작과 배치 도메인 객체의 기본 CRUD 작업을 함께 나타낸다:
이미지 11. 잡 리포지터리
잡런처(JobLauncher)
는 잡리포지터리(JobRepository)
를 사용하여 새로운 잡익스큐션(JobExecution)
객체를 생성하고 실행한다. 잡
및 스텝
의 구현체는 잡 실행 중 동일한 익스큐션의 업데이트에 동일한 잡리포지터리(JobRepository)
를 사용한다. 간단한 상황에는 기본 작업으로 충분하다. 그러나, 수백 개의 배치 잡과 복잡한 스케줄링 요구 사항이 있는 대규모 배치 환경에서는, 메타데이터에 대해 자세한 고급 기법이 필요하다:
이미지 12. 잡 리포지터리 고급 기법 접근
다음 섹션에서 설명할, 잡익스플로러(JobExplorer)
및 잡오퍼레이터(JobOperator)
인터페이스는, 메타데이터 쿼리 및 제어를 위한 기능을 추가한다.
4.6.1. Querying the Repository
고급 기능에 가장 기본적인 요구 사항은 실행에 대해 리포지터리를 쿼리하는 기능이다. 이 기능은 잡익스플로러(JobExplorer)
인터페이스에서 제공한다:
public interface JobExplorer {
List<JobInstance> getJobInstances(String jobName, int start, int count);
JobExecution getJobExecution(Long executionId);
StepExecution getStepExecution(Long jobExecutionId, Long stepExecutionId);
JobInstance getJobInstance(Long instanceId);
List<JobExecution> getJobExecutions(JobInstance jobInstance);
Set<JobExecution> findRunningJobExecutions(String jobName);
}
메서드 시그니처(method signatures)에서 알 수 있듯이, 잡익스플로러(JobExplorer)
는 잡리포지터리(JobRepository)
의 읽기 전용 버전이며, 잡리포지터리(JobRepository)
와 마찬가지로 팩토리 빈을 사용하여 쉽게 구성할 수 있다.
다음 예는 XML에서 잡익스플로러(JobExplorer)
를 구성하는 방법을 보여준다: XML 구성
<bean id="jobExplorer" class="org.spr...JobExplorerFactoryBean"
p:dataSource-ref="dataSource" />
다음 예는 자바에서 잡익스플로러(JobExplorer)
를 구성하는 방법을 보여준다: 자바 구성
...
// 이는 디폴트배치컨피규레이션(DefaultBatchConfiguration) 상속에 있다.
@Bean
public JobExplorer jobExplorer() throws Exception {
JobExplorerFactoryBean factoryBean = new JobExplorerFactoryBean();
factoryBean.setDataSource(this.dataSource);
return factoryBean.getObject();
}
...
이 장의 앞부분에서, 다른 버전이나 스키마를 허용하도록 잡리포지터리(JobRepository)
의 테이블 접두사를 수정할 수 있다고 언급했었다. 잡익스플로러(JobExplorer)
는 동일한 테이블에서 작동하기 때문에 접두사를 설정하는 기능도 필요하다.
다음 예는 XML에서 잡익스플로러(JobExplorer)
의 테이블 접두사를 설정하는 방법을 보여준다: XML 구성
<bean id="jobExplorer" class="org.spr...JobExplorerFactoryBean" p:tablePrefix="SYSTEM."/>
다음 예는 자바에서 잡익스플로러(JobExplorer)
의 테이블 접두사를 설정하는 방법을 보여준다: 자바 구성
...
// 이는 디폴트배치컨피규레이션(DefaultBatchConfiguration) 상속에 있다.
@Bean
public JobExplorer jobExplorer() throws Exception {
JobExplorerFactoryBean factoryBean = new JobExplorerFactoryBean();
factoryBean.setDataSource(this.dataSource);
factoryBean.setTablePrefix("SYSTEM.");
return factoryBean.getObject();
}
...
4.6.2. JobRegistry
잡레지스트리(JobRegistry)
(및 상위 인터페이스인 잡로케이터(JobLocator)
)는 필수는 아니지만, 컨텍스트에서 사용 가능한 잡을 추적(track)하려는 경우 유용할 수 있다. 또한 잡이 다른 곳(예: 하위 컨텍스트)에서 생성된 경우 애플리케이션 컨텍스트에서 중앙 집중식으로 잡을 수집하는 데 유용하다. 커스텀 잡레지스트리(JobRegistry)
구현체(implementation)를 사용하여 등록된 잡의 이름 및 기타 프로퍼티를 조작할 수도 있다. 프레임워크에서 제공하는 구현체는 단 하나이고 잡 이름과 잡 인스턴스으로 맵 기반이다.
다음 예는 XML에서 정의된 잡에 잡레지스트리(JobRegistry)
를 포함하는 방법을 보여준다:
<bean id="jobRegistry" class="org.springframework.batch.core.configuration.support.MapJobRegistry" />
@EnableBatchProcessing
을 사용하면, 잡레지스트리(JobRegistry)
가 제공된다. 다음 예는 자신의 잡레지스트리(JobRegistry)
를 구성하는 방법을 보여준다:
...
// 이는 이미 @EnableBatchProcessing을 통해 제공되지만 다음을 통해 사용자 정의할 수 있다.
// 디폴트배치컨피규레이션(DefaultBatchConfiguration)에서 빈 오버라이드(overriding)
@Override
@Bean
public JobRegistry jobRegistry() throws Exception {
return new MapJobRegistry();
}
...
빈 포스트 프로세서(bean post processor)를 사용하거나 레지스트러 생명주기 컴포넌트(registrar lifecycle component)를 사용하여 잡레지스트리(JobRegistry)
를 채울 수 있다. 다음 절에서는 이 두 가지 메커니즘에 대해 설명한다.
JobRegistryBeanPostProcessor
생성된 모든 잡을 등록할 수 있는 빈 포스트 프로세서(bean post processor)이다.
다음 예제는 XML에 정의된 잡에 대한 잡레지스트리빈포스트프로세서(JobRegistryBeanPostProcessor)
를 포함하는 방법을 보여준다: XML 구성
<bean id="jobRegistryBeanPostProcessor" class="org.spr...JobRegistryBeanPostProcessor">
<property name="jobRegistry" ref="jobRegistry"/>
</bean>
다음 예제는 자바에 정의된 잡에 대한 잡레지스트리빈포스트프로세서(JobRegistryBeanPostProcessor)
를 포함하는 방법을 보여준다: 자바 구성
@Bean
public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) {
JobRegistryBeanPostProcessor postProcessor = new JobRegistryBeanPostProcessor();
postProcessor.setJobRegistry(jobRegistry);
return postProcessor;
}
반드시 필요한 것은 아니지만, 예제의 포스트 프로세서에는 하위 컨텍스트(예: 상위 빈 정의)에 포함될 수 있고 거기에서 생성된 모든 잡이 자동으로 등록되도록 id가 부여됐다.
AutomaticJobRegistrar
하위 컨텍스트를 생성하고 해당 컨텍스트에서 생성된 잡을 등록하는 생명주기 컴포넌트(lifecycle component)이다. 이것의 한 가지 이점은, 하위 컨텍스트의 잡 이름이 여전히 레지스트리에서 전역적으로 고유해야 하지만, 해당 의존성이 “자연스러운” 이름을 가질 수 있다는 것이다. 예를 들어, 각각 하나의 잡만 있지만 동일한 빈 이름(예: reader)을 가진 아이템리더(ItemReader)
의 정의가 모두 다른 XML 구성 파일 집합을 생성할 수 있다. 이러한 모든 파일을 동일한 컨텍스트로 가져온 경우, 리더(reader)
정의가 충돌하고 서로 오버라이드하지만, 오토메틱 레지스트러(automatic registrar)를 사용하면 이를 방지할 수 있다. 이렇게 하면 애플리케이션의 개별 모듈에서 제공된 잡을 더 쉽게 통합할 수 있다.
다음 예는 XML에 정의된 잡에 대해 오토메틱잡레지스트러(AutomaticJobRegistrar)
를 포함하는 방법을 보여준다: XML 구성
<bean class="org.spr...AutomaticJobRegistrar">
<property name="applicationContextFactories">
<bean class="org.spr...ClasspathXmlApplicationContextsFactoryBean">
<property name="resources" value="classpath*:/config/job*.xml" />
</bean>
</property>
<property name="jobLoader">
<bean class="org.spr...DefaultJobLoader">
<property name="jobRegistry" ref="jobRegistry" />
</bean>
</property>
</bean>
다음 예는 자바에 정의된 잡에 대해 오토메틱잡레지스트러(AutomaticJobRegistrar)
를 포함하는 방법을 보여준다: 자바 구성
@Bean
public AutomaticJobRegistrar registrar() {
AutomaticJobRegistrar registrar = new AutomaticJobRegistrar();
registrar.setJobLoader(jobLoader());
registrar.setApplicationContextFactories(applicationContextFactories());
registrar.afterPropertiesSet();
return registrar;
}
레지스트러(registrar)는 애플리케이션컨텍스트팩토리(ApplicationContextFactory)
의 배열(이전 예제에서 편리한 팩토리 빈에서 생성됨)과 잡로더(JobLoader)
두 가지 필수 프로퍼티스를 가진다. 잡로더(JobLoader)
는 하위 컨텍스트의 생명주기을 관리하고 잡레지스트리(JobRegistry)
에 잡을 등록하는 역할을 한다.
애플리케이션컨텍스트팩토리(ApplicationContextFactory)
는 하위 컨텍스트 생성을 담당한다. 가장 일반적인 사용법은 (이전 예제에서와 같이) 클래스패스Xml애플리케이션컨텍스트팩토리(ClassPathXmlApplicationContextFactory)
를 사용하는 것이다. 이 팩토리의 기능 중 하나는, 상위 컨텍스트에서 하위 컨텍스트로 일부 구성을 복사하는 것이다. 예를 들어, 상위와 동일해야 한다면 하위에서 프로퍼티플래이스홀더컨피규어러(PropertyPlaceholderConfigurer)
또는 AOP 구성을 오버라이드할 필요가 없다.
잡레지스트리빈포스트프로세서(JobRegistryBeanPostProcessor)
와 함께 오토메틱잡레지스트러(AutomaticJobRegistrar)
(디폴트잡로더(DefaultJobLoader)도 사용하는 한)를 사용할 수 있다. 예를 들어, 상위 컨텍스트와 하위 위치에 정의된 잡이 있는 경우 이는 바람직할 수 있다.
4.6.3. JobOperator
이전에 논의한 것처럼, 잡리포지터리(JobRepository)
는 메타데이터에 대한 CRUD 작업을 제공하고, 잡익스플로러(JobExplorer)
는 메타데이터에 대한 읽기 전용 작업을 제공한다. 그러나, 이러한 작업은 배치 오퍼레이터가 일반적으로 수행하는 잡 중지, 재시작 또는 요약과 같은 일반적인 모니터링 작업을 함께 수행할 때 유용하다. 스프링 배치는 잡오퍼레이터(JobOperator)
인터페이스에서 이러한 타입의 작업을 제공한다:
public interface JobOperator {
List<Long> getExecutions(long instanceId) throws NoSuchJobInstanceException;
List<Long> getJobInstances(String jobName, int start, int count) throws NoSuchJobException;
Set<Long> getRunningExecutions(String jobName) throws NoSuchJobException;
String getParameters(long executionId) throws NoSuchJobExecutionException;
Long start(String jobName, String parameters) throws NoSuchJobException, JobInstanceAlreadyExistsException;
Long restart(long executionId) throws JobInstanceAlreadyCompleteException, NoSuchJobExecutionException, NoSuchJobException, JobRestartException;
Long startNextInstance(String jobName) throws NoSuchJobException, JobParametersNotFoundException, JobRestartException, JobExecutionAlreadyRunningException, JobInstanceAlreadyCompleteException;
boolean stop(long executionId) throws NoSuchJobExecutionException, JobExecutionNotRunningException;
String getSummary(long executionId) throws NoSuchJobExecutionException;
Map<Long, String> getStepExecutionSummaries(long executionId) throws NoSuchJobExecutionException;
Set<String> getJobNames();
}
이전 작업은 잡런처(JobLauncher)
, 잡리포지터리(JobRepository)
, 잡익스플로러(JobExplorer)
및 잡레지스트리(JobRegistry)
와 같은 다양한 인터페이스의 메서드를 나타낸다. 이러한 이유로 제공된 잡오퍼레이터(JobOperator)
구현체(심플잡오퍼레이터(SimpleJobOperator)
)에는 많은 의존성이 있다.
다음 예제는 XML에서 심플잡오퍼레이터(SimpleJobOperator)
에 대한 일반적인 빈 정의를 보여준다:
<bean id="jobOperator" class="org.spr...SimpleJobOperator">
<property name="jobExplorer">
<bean class="org.spr...JobExplorerFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
</property>
<property name="jobRepository" ref="jobRepository" />
<property name="jobRegistry" ref="jobRegistry" />
<property name="jobLauncher" ref="jobLauncher" />
</bean>
다음 예제는 자바에서 심플잡오퍼레이터(SimpleJobOperator)
에 대한 일반적인 빈 정의를 보여준다:
/**
* 이 빈에 주입된 모든 의존성은 @EnableBatchProcessing 인프라에서 제공된다.
*/
@Bean
public SimpleJobOperator jobOperator(JobExplorer jobExplorer, JobRepository jobRepository, JobRegistry jobRegistry, JobLauncher jobLauncher) {
SimpleJobOperator jobOperator = new SimpleJobOperator();
jobOperator.setJobExplorer(jobExplorer);
jobOperator.setJobRepository(jobRepository);
jobOperator.setJobRegistry(jobRegistry);
jobOperator.setJobLauncher(jobLauncher);
return jobOperator;
}
버전 5.0부터, @EnableBatchProcessing 어노테이션은 자동으로 잡 오퍼레이터 빈(job operator bean)을 애플리케이션 컨텍스트에 등록한다.
잡 리포지터리에서 테이블 접두사를 설정한 경우, 잡 익스플로러에서도 설정하는 것을 잊지 말자.
4.6.4. JobParametersIncrementer
잡오퍼레이터(JobOperator)
의 대부분의 메소드는 설명이 필요 없으며, 인터페이스의 자바독(Javadoc)에서 더 자세한 설명을 찾을 수 있다. 그러나, startNextInstance
메소드는 주목할만 하다. 이 메서드는 항상 잡의 새 인스턴스를 시작한다. 이는 잡익스큐션(JobExecution)에 심각한 문제가 있고 잡을 처음부터 다시 시작해야 하는 경우 매우 유용할 수 있다. 잡런처(JobLauncher)
(새 잡인스턴스(JobInstance)
를 트리거하는 새 잡파라미터(JobParameter)
객체가 필요함)와 달리, 파라미터가 이전 파라미터 세트와 다른 경우 startNextInstance
메서드는 잡에 연결된 잡파라미터인크리먼터(JobParametersIncrementer)
를 사용하여 잡을 새 인스턴스로 강제 실행한다:
public interface JobParametersIncrementer {
JobParameters getNext(JobParameters parameters);
}
잡파라미터인크리먼터(JobParametersIncrementer)
의 기능은, 잡파라미터(JobParameters) 객체가 주어지면, 포함할 수 있는 필요한 값을 증가시켜 “다음” 잡파라미터(JobParameter) 객체를 반환하는 것이다. 예를 들어, 잡파라미터(JobParameter)의 유일한 값이 날짜이고 다음 인스턴스를 생성해야 할 떄, (잡이 매주, 수행되는 경우) 해당 값을 하루를 증가시켜야 하는지 일주일씩 증가시켜야 하는지? 다음 예에서 볼 수 있듯이, 잡을 식별하는 데 도움이 되는 모든 숫자 값에 대해서도 마찬가지이다:
public class SampleIncrementer implements JobParametersIncrementer {
public JobParameters getNext(JobParameters parameters) {
if (parameters==null || parameters.isEmpty()) {
return new JobParametersBuilder().addLong("run.id", 1L).toJobParameters();
}
long id = parameters.getLong("run.id", 1L) + 1;
return new JobParametersBuilder().addLong("run.id", id).toJobParameters();
}
}
이 예에서, 키(key)가 run.id
인 값은 잡인스턴스(JobInstance)
를 구분하는 데 사용된다. 전달된 잡파라미터(JobParameter)
가 null인 경우, 잡이 이전에 실행된 적이 없다고 가정할 수 있으므로, 초기 상태가 반환될 수 있다. 단, 그렇지 않은 경우 이전 값을 구하여, 1씩 증가시켜, 반환한다.
네임스페이스의 incrementer
애트리뷰트을 사용하여 증분기(incrementer)를 잡과 연결할 수 있다:
<job id="footballJob" incrementer="sampleIncrementer">
...
</job>
자바 구성 빌더는 증분기(incrementer) 구성을 위한 기능도 제공한다:
@Bean
public Job footballJob(JobRepository jobRepository) {
return new JobBuilder("footballJob", jobRepository)
.incrementer(sampleIncrementer())
.build();
}
...
4.6.5. Stopping a Job
잡오퍼레이터(JobOperator)의 가장 일반적인 사례 중 하나는 정상적으로 잡을 중지하는 것이다:
Set<Long> executions = jobOperator.getRunningExecutions("sampleJob");
jobOperator.stop(executions.iterator().next());
특히 비즈니스 서비스와 같이, 프레임워크가 제어할 수 없는 개발자 코드가 실행되는 경우, 강제 종료할 방법이 없기 때문에, 종료가 즉각적이지 않다. 그러나, 제어가 프레임워크로 다시 반환되는 즉시, 현재 스텝익스큐션(StepExecution)
의 상태를 BatchStatus.STOPPED
로 설정하고, 저장한 다음, 완료하기 전에 잡익스큐션(JobExecution)
에 대해 동일한 작업을 수행한다.
4.6.6. Aborting a Job
FAILED
인 잡 익스큐션(job execution)을 (잡을 재시작할 수 있는 경우) 재시작할 수 있다. 상태가 ABANDONED
인 잡 실행은 프레임워크에서 재시작할 수 없다. 또한 ABANDONED
상태는 재시작된 잡 익스큐션(job execution)의 스텝 익스큐션(step execution)에 스킵을 표시하는 데 사용된다. 잡이 실행 중이고 이전에 실패한 잡 익스큐션(job execution)에서 ABANDONED
로 표시된 스텝를 만나면, 다음 스텝으로 이동(잡 흐름 정의 및 스텝 익스큐션 종료 상태에 따라 결정됨)한다.
프로세스가 종료되면(kill -9
또는 서버 오류), 당연히, 잡이 실행되지 않지만, 잡리포지터리(JobRepository)
는 프로세스가 종료되기 전에 아무도 알려주지 않기 때문에 알 수 있는 방법이 없다. 익스큐션(execution)이 실패했거나 중단된 것을 수동으로 알려야 한다(상태를 FAILED
또는 ABANDONED
로 변경). 이것은 비즈니스 결정이며, 이를 자동화할 방법이 없다. 재시작 가능하고 재시작 데이터가 유효한 경우에만 상태를 FAILED로 변경하자.