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();
  }

다르게 표현하자면, restartablefalse로 설정하면 “이 은 재시작을 지원하지 않는다”를 의미한다. 재시작할 수 없는 을 재시작하면 잡리스타트익셉션(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-txspring-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 BatchEndOfDay 예를 사용한다. 첫 번째 아규먼트는 잡이 구성된 위치(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로 변경하자.