Spring Batch Tutorial: Spring으로 간편하게 일괄 처리

게시 됨: 2022-03-11

일괄 처리(대량 지향, 비대화형 및 자주 오래 실행되는 백그라운드 실행으로 대표됨)는 거의 모든 산업에서 널리 사용되며 다양한 작업에 적용됩니다. 일괄 처리는 데이터 또는 계산 집약적일 수 있으며 순차적으로 또는 병렬로 실행되며 임시, 예약 및 주문형을 비롯한 다양한 호출 모델을 통해 시작될 수 있습니다.

이 Spring Batch 튜토리얼은 일반적으로 배치 애플리케이션의 프로그래밍 모델과 도메인 언어를 설명하고 특히 현재 Spring Batch 3.0.7 버전을 사용하여 배치 애플리케이션의 설계 및 개발에 대한 몇 가지 유용한 접근 방식을 보여줍니다.

스프링 배치란?

Spring Batch는 강력한 배치 애플리케이션의 개발을 용이하게 하도록 설계된 가볍고 포괄적인 프레임워크입니다. 또한 최적화 및 파티셔닝 기술을 통해 대용량 및 고성능 배치 작업을 지원하는 고급 기술 서비스 및 기능을 제공합니다. Spring Batch는 모든 경험이 풍부한 Spring 개발자에게 친숙한 Spring Framework의 POJO 기반 개발 접근 방식을 기반으로 합니다.

예를 들어 이 기사에서는 XML 형식의 고객 파일을 로드하고 다양한 속성으로 고객을 필터링하며 필터링된 항목을 텍스트 파일로 출력하는 샘플 프로젝트의 소스 코드를 고려합니다. Lombok 주석을 사용하는 Spring Batch 예제의 소스 코드는 여기 GitHub에서 사용할 수 있으며 Java SE 8 및 Maven이 필요합니다.

일괄 처리란 무엇입니까? 주요 개념 및 용어

배치 개발자는 배치 처리의 주요 개념에 익숙해지고 익숙해지는 것이 중요합니다. 아래 다이어그램은 다양한 플랫폼에서 수십 년 동안 구현되어 입증된 배치 참조 아키텍처의 단순화된 버전입니다. Spring Batch에서 사용되는 일괄 처리와 관련된 주요 개념과 용어를 소개합니다.

Spring Batch 튜토리얼: 주요 개념 및 용어

배치 처리 예제에서 볼 수 있듯이 배치 프로세스는 일반적으로 여러 Step 로 구성된 Job 으로 캡슐화됩니다. 각 Step 에는 일반적으로 단일 ItemReader , ItemProcessorItemWriter 가 있습니다. JobJobLauncher 에 의해 실행되며 구성 및 실행된 작업에 대한 메타데이터는 JobRepository 에 저장됩니다.

Job 은 여러 JobInstance 와 연관될 수 있으며, 각각은 일괄 작업을 시작하는 데 사용되는 특정 JobParameters 에 의해 고유하게 정의됩니다. JobInstance 의 각 실행을 JobExecution 이라고 합니다. 각 JobExecution 은 일반적으로 현재 및 종료 상태, 시작 및 종료 시간 등과 같이 실행 중에 발생한 일을 추적합니다.

Step 는 일괄 Job 의 독립적인 특정 단계로, 모든 Job 은 하나 이상의 Step 로 구성됩니다. Job 과 유사하게, Step 에는 Step 실행을 위한 단일 시도를 나타내는 개별 StepExecution 이 있습니다. StepExecution 은 현재 및 종료 상태, 시작 및 종료 시간 등에 대한 정보와 해당 StepJobExecution 인스턴스에 대한 참조를 저장합니다.

ExecutionContext 는 범위가 StepExecution 또는 JobExecution 인 정보를 포함하는 키-값 쌍 세트입니다. Spring Batch는 ExecutionContext 를 유지하므로 일괄 실행을 다시 시작하려는 경우(예: 치명적인 오류가 발생한 경우 등)에 도움이 됩니다. 필요한 것은 단계 간에 공유할 개체를 컨텍스트에 넣는 것뿐이며 나머지는 프레임워크에서 처리합니다. 다시 시작한 후 이전 ExecutionContext 의 값이 데이터베이스에서 복원되어 적용됩니다.

JobRepository 는 이 모든 지속성을 가능하게 하는 Spring Batch의 메커니즘입니다. JobLauncher , JobStep 인스턴스화에 대한 CRUD 작업을 제공합니다. Job 이 시작되면 저장소에서 StepExecution JobExecution JobExecution 인스턴스가 저장소에 유지됩니다.

스프링 배치 프레임워크 시작하기

Spring Batch의 장점 중 하나는 프로젝트 종속성이 최소화되어 더 쉽게 시작하고 빠르게 실행할 수 있다는 것입니다. 존재하는 몇 가지 종속성은 여기에서 액세스할 수 있는 프로젝트의 pom.xml 에 명확하게 지정되고 설명됩니다.

애플리케이션의 실제 시작은 다음과 같은 클래스에서 발생합니다.

 @EnableBatchProcessing @SpringBootApplication public class BatchApplication { public static void main(String[] args) { prepareTestData(1000); SpringApplication.run(BatchApplication.class, args); } }

@EnableBatchProcessing 주석은 Spring Batch 기능을 활성화하고 일괄 작업 설정을 위한 기본 구성을 제공합니다.

@SpringBootApplication 주석은 독립 실행형, 프로덕션 준비, Spring 기반 애플리케이션을 제공하는 Spring Boot 프로젝트에서 가져옵니다. 하나 이상의 Spring 빈을 선언하고 자동 구성 및 Spring의 구성 요소 스캐닝을 트리거하는 구성 클래스를 지정합니다.

샘플 프로젝트에는 주입된 JobBuilderFactoryStepBuilderFactoryCustomerReportJobConfig 에서 구성한 작업이 하나만 있습니다. 최소 작업 구성은 CustomerReportJobConfig 에서 다음과 같이 정의할 수 있습니다.

 @Configuration public class CustomerReportJobConfig { @Autowired private JobBuilderFactory jobBuilders; @Autowired private StepBuilderFactory stepBuilders; @Bean public Job customerReportJob() { return jobBuilders.get("customerReportJob") .start(taskletStep()) .next(chunkStep()) .build(); } @Bean public Step taskletStep() { return stepBuilders.get("taskletStep") .tasklet(tasklet()) .build(); } @Bean public Tasklet tasklet() { return (contribution, chunkContext) -> { return RepeatStatus.FINISHED; }; } }

단계를 구축하는 데에는 두 가지 주요 접근 방식이 있습니다.

위의 예에서 볼 수 있는 한 가지 접근 방식은 tasklet 기반 입니다. TaskletRepeatStatus.FINISHED 를 반환하거나 실패를 알리는 예외를 throw할 때까지 반복적으로 호출되는 execute() 메서드가 하나만 있는 간단한 인터페이스를 지원합니다. Tasklet 에 대한 각 호출은 트랜잭션으로 래핑됩니다.

또 다른 접근 방식인 청크 지향 처리 는 데이터를 순차적으로 읽고 트랜잭션 경계 내에서 기록될 "청크"를 생성하는 것을 말합니다. 각 개별 항목은 ItemReader 에서 ItemProcessor 로 전달되고 집계됩니다. 읽은 항목 수가 커밋 간격과 같으면 ItemWriter 를 통해 전체 청크가 작성된 다음 트랜잭션이 커밋됩니다. 청크 지향 단계는 다음과 같이 구성할 수 있습니다.

 @Bean public Job customerReportJob() { return jobBuilders.get("customerReportJob") .start(taskletStep()) .next(chunkStep()) .build(); } @Bean public Step chunkStep() { return stepBuilders.get("chunkStep") .<Customer, Customer>chunk(20) .reader(reader()) .processor(processor()) .writer(writer()) .build(); }

chunk() 메서드는 제공된 크기로 항목을 청크로 처리하는 단계를 빌드하고 각 청크는 지정된 판독기, 프로세서 및 작성기로 전달됩니다. 이러한 방법은 이 문서의 다음 섹션에서 더 자세히 설명합니다.

커스텀 리더

Spring Batch 샘플 애플리케이션의 경우 XML 파일에서 고객 목록을 읽으려면 org.springframework.batch.item.ItemReader 인터페이스 구현을 제공해야 합니다.

 public interface ItemReader<T> { T read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException; }

ItemReader 는 데이터를 제공하며 상태를 저장해야 합니다. 일반적으로 각 배치에 대해 여러 번 호출되며 read() 에 대한 각 호출은 다음 값을 반환하고 모든 입력 데이터가 소진되면 마지막으로 null 을 반환합니다.

Spring Batch는 컬렉션, 파일 읽기, JMS 및 JDBC 통합, 여러 소스 등과 같은 다양한 목적에 사용할 수 있는 ItemReader 의 일부 즉시 사용 가능한 구현을 제공합니다.

샘플 애플리케이션에서 CustomerItemReader 클래스는 실제 read() 호출을 IteratorItemReader 클래스의 느리게 초기화된 인스턴스에 위임합니다.

 public class CustomerItemReader implements ItemReader<Customer> { private final String filename; private ItemReader<Customer> delegate; public CustomerItemReader(final String filename) { this.filename = filename; } @Override public Customer read() throws Exception { if (delegate == null) { delegate = new IteratorItemReader<>(customers()); } return delegate.read(); } private List<Customer> customers() throws FileNotFoundException { try (XMLDecoder decoder = new XMLDecoder(new FileInputStream(filename))) { return (List<Customer>) decoder.readObject(); } } }

이 구현을 위한 Spring 빈은 @Component@StepScope 주석으로 생성되어 이 클래스가 단계 범위의 Spring 구성 요소이며 다음과 같이 단계 실행당 한 번 생성됨을 Spring에 알립니다.

 @StepScope @Bean public ItemReader<Customer> reader() { return new CustomerItemReader(XML_FILE); }

맞춤형 프로세서

ItemProcessors 는 입력 항목을 변환하고 항목 지향 처리 시나리오에서 비즈니스 로직을 도입합니다. 그들은 org.springframework.batch.item.ItemProcessor 인터페이스의 구현을 제공해야 합니다:

 public interface ItemProcessor<I, O> { O process(I item) throws Exception; }

process() 메서드는 I 클래스의 하나의 인스턴스를 허용하며 동일한 유형의 인스턴스를 반환할 수도 있고 반환하지 않을 수도 있습니다. null 을 반환하면 항목이 계속 처리되지 않아야 함을 나타냅니다. 평소와 같이 Spring은 주입된 ItemProcessor 시퀀스와 입력을 검증하는 ValidatingItemProcessor 를 통해 항목을 전달하는 CompositeItemProcessor 와 같은 몇 가지 표준 프로세서를 제공합니다.

샘플 애플리케이션의 경우 프로세서는 다음 요구 사항에 따라 고객을 필터링하는 데 사용됩니다.

  • 고객은 당월에 출생해야 합니다(예: 생일 특별 행사에 플래그 지정).
  • 고객은 완료된 거래가 5개 미만이어야 합니다(예: 신규 고객 식별)

"현재 월" 요구 사항은 사용자 정의 ItemProcessor 를 통해 구현됩니다.

 public class BirthdayFilterProcessor implements ItemProcessor<Customer, Customer> { @Override public Customer process(final Customer item) throws Exception { if (new GregorianCalendar().get(Calendar.MONTH) == item.getBirthday().get(Calendar.MONTH)) { return item; } return null; } }

"제한된 트랜잭션 수" 요구 사항은 ValidatingItemProcessor 로 구현됩니다.

 public class TransactionValidatingProcessor extends ValidatingItemProcessor<Customer> { public TransactionValidatingProcessor(final int limit) { super( item -> { if (item.getTransactions() >= limit) { throw new ValidationException("Customer has less than " + limit + " transactions"); } } ); setFilter(true); } }

그런 다음 이 프로세서 쌍은 대리자 패턴을 구현하는 CompositeItemProcessor 내에 캡슐화됩니다.

 @StepScope @Bean public ItemProcessor<Customer, Customer> processor() { final CompositeItemProcessor<Customer, Customer> processor = new CompositeItemProcessor<>(); processor.setDelegates(Arrays.asList(new BirthdayFilterProcessor(), new TransactionValidatingProcessor(5))); return processor; }

커스텀 라이터

데이터 출력을 위해 Spring Batch는 필요에 따라 객체를 직렬화하기 위한 org.springframework.batch.item.ItemWriter 인터페이스를 제공합니다.

 public interface ItemWriter<T> { void write(List<? extends T> items) throws Exception; }

write() 메서드는 내부 버퍼가 플러시되는지 확인하는 역할을 합니다. 트랜잭션이 활성 상태이면 일반적으로 후속 롤백에서 출력을 폐기해야 합니다. 기록기가 데이터를 보내는 리소스는 일반적으로 이 자체를 처리할 수 있어야 합니다. CompositeItemWriter , JdbcBatchItemWriter , JmsItemWriter , JpaItemWriter , SimpleMailMessageItemWriter 등과 같은 표준 구현이 있습니다.

샘플 애플리케이션에서 필터링된 고객 목록은 다음과 같이 작성됩니다.

 public class CustomerItemWriter implements ItemWriter<Customer>, Closeable { private final PrintWriter writer; public CustomerItemWriter() { OutputStream out; try { out = new FileOutputStream("output.txt"); } catch (FileNotFoundException e) { out = System.out; } this.writer = new PrintWriter(out); } @Override public void write(final List<? extends Customer> items) throws Exception { for (Customer item : items) { writer.println(item.toString()); } } @PreDestroy @Override public void close() throws IOException { writer.close(); } }

스프링 배치 작업 스케줄링

기본적으로 Spring Batch는 시작할 때 찾을 수 있는 모든 작업을 실행합니다(즉, CustomerReportJobConfig 에서와 같이 구성된 작업). 이 동작을 변경하려면 application.properties 에 다음 속성을 추가하여 시작 시 작업 실행을 비활성화하십시오.

 spring.batch.job.enabled=false

그런 다음 구성 클래스에 @EnableScheduling 주석을 추가하고 작업 자체를 실행하는 메서드에 @Scheduled 주석을 추가하여 실제 스케줄링을 수행합니다. 예약은 지연, 속도 또는 cron 표현식으로 구성할 수 있습니다.

 // run every 5000 msec (ie, every 5 secs) @Scheduled(fixedRate = 5000) public void run() throws Exception { JobExecution execution = jobLauncher.run( customerReportJob(), new JobParametersBuilder().toJobParameters() ); }

하지만 위의 예에는 문제가 있습니다. 런타임 시 작업은 처음에만 성공합니다. 두 번째(즉, 5초 후) 시작하면 로그에 다음 메시지가 생성됩니다(이전 버전의 Spring Batch에서는 JobInstanceAlreadyCompleteException 이 발생했을 수 있음).

 INFO 36988 --- [pool-2-thread-1] osbclsupport.SimpleJobLauncher : Job: [SimpleJob: [name=customerReportJob]] launched with the following parameters: [{}] INFO 36988 --- [pool-2-thread-1] osbatch.core.job.SimpleStepHandler : Step already complete or not restartable, so no action to execute: StepExecution: id=1, version=3, name=taskletStep, status=COMPLETED, exitStatus=COMPLETED, readCount=0, filterCount=0, writeCount=0 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=1, rollbackCount=0, exitDescription= INFO 36988 --- [pool-2-thread-1] osbatch.core.job.SimpleStepHandler : Step already complete or not restartable, so no action to execute: StepExecution: id=2, version=53, name=chunkStep, status=COMPLETED, exitStatus=COMPLETED, readCount=1000, filterCount=982, writeCount=18 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=51, rollbackCount=0, exitDescription=

이것은 유일한 JobInstance 만 생성 및 실행될 수 있고 Spring Batch는 첫 번째 JobInstance와 두 번째 JobInstance 를 구별할 방법이 없기 때문에 발생합니다.

일괄 작업을 예약할 때 이 문제를 방지하는 두 가지 방법이 있습니다.

하나는 각 작업에 하나 이상의 고유한 매개변수(예: 실제 시작 시간(나노초))를 도입하는 것입니다.

 @Scheduled(fixedRate = 5000) public void run() throws Exception { jobLauncher.run( customerReportJob(), new JobParametersBuilder().addLong("uniqueness", System.nanoTime()).toJobParameters() ); }

또는 SimpleJobOperator.startNextInstance() 를 사용하여 지정된 작업에 연결된 JobParametersIncrementer 에 의해 결정된 일련의 JobInstance 에서 다음 작업을 시작할 수 있습니다.

 @Autowired private JobOperator operator; @Autowired private JobExplorer jobs; @Scheduled(fixedRate = 5000) public void run() throws Exception { List<JobInstance> lastInstances = jobs.getJobInstances(JOB_NAME, 0, 1); if (lastInstances.isEmpty()) { jobLauncher.run(customerReportJob(), new JobParameters()); } else { operator.startNextInstance(JOB_NAME); } }

스프링 배치 단위 테스트

일반적으로 Spring Boot 애플리케이션에서 단위 테스트를 실행하려면 프레임워크가 해당 ApplicationContext 를 로드해야 합니다. 이를 위해 두 가지 주석이 사용됩니다.

 @RunWith(SpringRunner.class) @ContextConfiguration(classes = {...})

배치 작업을 테스트하기 위한 유틸리티 클래스 org.springframework.batch.test.JobLauncherTestUtils 가 있습니다. 전체 작업을 시작하는 방법과 작업의 모든 단계를 실행할 필요 없이 개별 단계의 종단 간 테스트를 허용하는 방법을 제공합니다. Spring Bean으로 선언해야 합니다.

 @Configuration public class BatchTestConfiguration { @Bean public JobLauncherTestUtils jobLauncherTestUtils() { return new JobLauncherTestUtils(); } }

작업 및 단계에 대한 일반적인 테스트는 다음과 같습니다(모든 모의 프레임워크도 사용할 수 있음).

 @RunWith(SpringRunner.class) @ContextConfiguration(classes = {BatchApplication.class, BatchTestConfiguration.class}) public class CustomerReportJobConfigTest { @Autowired private JobLauncherTestUtils testUtils; @Autowired private CustomerReportJobConfig config; @Test public void testEntireJob() throws Exception { final JobExecution result = testUtils.getJobLauncher().run(config.customerReportJob(), testUtils.getUniqueJobParameters()); Assert.assertNotNull(result); Assert.assertEquals(BatchStatus.COMPLETED, result.getStatus()); } @Test public void testSpecificStep() { Assert.assertEquals(BatchStatus.COMPLETED, testUtils.launchStep("taskletStep").getStatus()); } }

Spring Batch는 단계 및 작업 컨텍스트에 대한 추가 범위를 도입합니다. 이러한 범위의 객체는 Spring 컨테이너를 객체 팩토리로 사용하므로 실행 단계 또는 작업당 각 빈의 인스턴스가 하나만 있습니다. 또한 StepContext 또는 JobContext 에서 액세스할 수 있는 참조의 후기 바인딩에 대한 지원이 제공됩니다. 런타임 시 단계 또는 작업 범위로 구성된 구성 요소는 컨텍스트를 단계 또는 작업 실행에 있는 것처럼 설정할 수 있는 방법이 없으면 독립 실행형 구성 요소로 테스트하기 어렵습니다. 이것이 Spring Batch의 org.springframework.batch.test.StepScopeTestExecutionListenerorg.springframework.batch.test.StepScopeTestUtils 구성 요소와 JobScopeTestExecutionListenerJobScopeTestUtils 의 목표입니다.

TestExecutionListeners 는 클래스 수준에서 선언되며, 그 역할은 각 테스트 메소드에 대한 단계 실행 컨텍스트를 생성하는 것입니다. 예를 들어:

 @RunWith(SpringRunner.class) @TestExecutionListeners({DependencyInjectionTestExecutionListener.class, StepScopeTestExecutionListener.class}) @ContextConfiguration(classes = {BatchApplication.class, BatchTestConfiguration.class}) public class BirthdayFilterProcessorTest { @Autowired private BirthdayFilterProcessor processor; public StepExecution getStepExecution() { return MetaDataInstanceFactory.createStepExecution(); } @Test public void filter() throws Exception { final Customer customer = new Customer(); customer.setId(1); customer.setName("name"); customer.setBirthday(new GregorianCalendar()); Assert.assertNotNull(processor.process(customer)); } }

두 개의 TestExecutionListener 가 있습니다. 하나는 일반 스프링 테스트 프레임워크에서 가져온 것으로 구성된 애플리케이션 컨텍스트에서 종속성 주입을 처리합니다. 다른 하나는 단위 테스트에 종속성 주입을 위한 단계 범위 컨텍스트를 설정하는 Spring Batch StepScopeTestExecutionListener 입니다. 테스트 메서드 기간 동안 StepContext 가 생성되고 주입된 모든 종속성이 사용할 수 있습니다. 기본 동작은 고정 속성으로 StepExecution 을 생성하는 것입니다. 또는 테스트 케이스에서 올바른 유형을 반환하는 팩토리 메소드로 StepContext 를 제공할 수 있습니다.

또 다른 접근 방식은 StepScopeTestUtils 유틸리티 클래스를 기반으로 합니다. 이 클래스는 종속성 주입을 사용하지 않고 보다 유연한 방식으로 단위 테스트에서 StepScope 를 만들고 조작하는 데 사용됩니다. 예를 들어 위의 프로세서에 의해 필터링된 고객의 ID를 읽는 것은 다음과 같이 수행할 수 있습니다.

 @Test public void filterId() throws Exception { final Customer customer = new Customer(); customer.setId(1); customer.setName("name"); customer.setBirthday(new GregorianCalendar()); final int id = StepScopeTestUtils.doInStepScope( getStepExecution(), () -> processor.process(customer).getId() ); Assert.assertEquals(1, id); }

고급 스프링 배치에 대한 준비가 되셨습니까?

이 기사에서는 Spring Batch 애플리케이션의 설계 및 개발에 대한 몇 가지 기본 사항을 소개합니다. 그러나 이 문서에서 다루지 않은 더 많은 고급 주제와 기능(예: 확장, 병렬 처리, 수신기 등)이 있습니다. 이 기사가 시작하는 데 유용한 기초가 되기를 바랍니다.

이러한 고급 주제에 대한 정보는 Spring Batch에 대한 공식 Spring Back 문서에서 찾을 수 있습니다.