Spring/Spring Batch

[Spring Batch] 5장 : Configuring a Step - 2 : Configuring Skip Logic ~ Intercepting Step Execution

공대키메라 2023. 12. 7. 19:22

이번 시간에는 지난번에 읽던 Step 설정을 이어서 읽어볼 것이다.

(지난 내용이 궁금하면 여기 클릭!)

 


 

1. Configuring Skip Logic

처리하는동안 맞닥드린 에러들은 Step 의 실패로 결과를 초래하는 대신에 생략해야만 하는 시나리오가 있다.

이것은 데이터를 그것이 무슨 의미인지 이해하는 누군가에 의해 만들어지는 결정이다. 예를 들어, 금융 데이터는 돈이 전동될 때 스킵할 수 없는데 그것이 완벽한 정확함을 필요로하기 때문이다. 반면에 벤더의 리스트를 로딩하는 것은 스킵을 가능케 할 수도 있다. 만약 벤더가 부정확하게 포맷되어잇고 필요한 정보가 빠져 있다면 이슈가 없다. 대개, 이렇나 나쁜 기록들이 또한 쌓이는데 listener에 대해 논의할 때 나중에 다룬다.

 

다음 Java 예시가 skip limit의 사용 예시를 보인다.

 

Java Configuration

@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
	return new StepBuilder("step1", jobRepository)
				.<String, String>chunk(10, transactionManager)
				.reader(flatFileItemReader())
				.writer(itemWriter())
				.faultTolerant()
				.skipLimit(10)
				.skip(FlatFileParseException.class)
				.build();
}

 

앞선 예시에서 FlatFileItemReader가 사용됬는데, 어떤 시점에서든 FlatFileParseExcecption이 발생하고 item은 생략이 되고 skip limit의 총 10까지 세어진다. 선언된 예외들은 chunk 처리 동안 발생할 수 있다. step실행의 읽음(read), 처리(process), 그리고 쓰기 (write) 의 생략이 별도의 수를 세는데 제한은 모든 스킵에 적용된다. 일단 생략 제한이 도달하면 다음에 발견된 예시는 step을 실패하게 한다. 다시 말해서, 11번째 생략이 10번째가 아닌 예외를 발생시킨다.

 

앞선 예시의 한가지 문제점은 FlatFileParseException 외에 또다른 예외가 Job이 실패하게끔 한다는 점이다. 특정 시나리오에서, 이것은 알맞은 행동일 수 있지만 다른 시나리오에서 어떤 예외가 실패와 생략을 유발했는지 식별하는게 더 쉬울 수 있다. 

 

다음 Java 예시는 특정한 예외를 제외하는 예시를 보여준다.

 

Java Configuration

@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
	return new StepBuilder("step1", jobRepository)
				.<String, String>chunk(10, transactionManager)
				.reader(flatFileItemReader())
				.writer(itemWriter())
				.faultTolerant()
				.skipLimit(10)
				.skip(Exception.class)
				.noSkip(FileNotFoundException.class)
				.build();
}

 

 

java.lang.Exception을 생략 가능한 exception class로 식별함으로서, 설정은 모든 Exception이 생략 가능하다고 나타낸다.

하지만, java.io.FileNotFouncException을 제외함으로서, 설정이 FileNotFoundException을 제외하고 모든 Exception 클래스들을 생략할 수 있는 예외 클래스로 정의한다. 모든 제외된 예외 클래스는 맞닥드리면 치명적이다.

 

마주치는 모든 예외에 대해 생략 가능성은 클래스 계층에서 가장 가까운 슈퍼 클래스에 의해 결정된다. 모든 분류되지 않은 예외는 '치명적' 이라고 여겨진다.

 

skip과 noSkip 메소드 호출 순서는 중요하지 않다.

 

2. Configuring Retry Logic

대부분의 경우에 예외가 생략이던지 Step 실패이던지를 발생시키길 원할 수 있다. 하지만 모든 예외들이 결정적이진 않다. 만약 FlatFileParseException이 읽는 도중에 터진다면 항상 그 record를 던진다. ItemReader를 다시 세팅하는건 도움이 안된다. 하지만 다른 예외의 경우 기다리고 다시 시도하면 성공할 수 있다.

 

Java에서, 재시도는 다음과 같이 설정해야 한다.

 

@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
	return new StepBuilder("step1", jobRepository)
				.<String, String>chunk(2, transactionManager)
				.reader(itemReader())
				.writer(itemWriter())
				.faultTolerant()
				.retryLimit(3)
				.retry(DeadlockLoserDataAccessException.class)
				.build();
}

 

Step은 개별 항목을 재시도할 수 있는 횟수와 "재시도 가능한" 예외 목록에 대한 제한을 허용합니다. 어떻게 재시다(retry)가 작동하는지 retry에서 더 자세히 알 수 있다.

 

3. Controlling Rollback

기본적으로 재시작(retry) 와 생략(skip)에 상관없이 ItemWriter부터 발생한 모든 예외는 Step에 의헤 제어되는 트랜잭션이 롤백(rollback)되도록 한다. 만약 생략을 이전에 묘사했듯이 설정했다면 ItemReader로부터 발생한 예외는 rollback을 발생시키지 않는다. 하지만 ItemWriter 로 부터 발생한 예외들이 rollback을 발생시키면 안되는 많은 시나리오가 있다. 이러한 이유로, Step을 rollback을 실행하지 않도록 예외 리스트를 설정할 수 있다. 

 

@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
	return new StepBuilder("step1", jobRepository)
				.<String, String>chunk(2, transactionManager)
				.reader(itemReader())
				.writer(itemWriter())
				.faultTolerant()
				.noRollback(ValidationException.class)
				.build();
}

 

4. Transactional Readers

ItemReader의 기본 계약은 오직 앞으로만 간다는 것이다 (문 스윙스! 난 앞으로만가!). Step이 롤백 시 reader에서 item을 다시 읽을 필요가 없도록 reader input을 버퍼링한다. 하지만 reader가 트랜잭션 리소스의 최상위에 생성된 특정한 시나리오가 있다. JMS queue처럼 말이다. 이러한 경우에, queue가 롤백된 트랜잭션에 묶여있기 때문에 queue로 부터 얻어온 message은 다시 배치된다. 이러한 이유로 아이템을 버퍼링하지 않는 step을 설정할 수 있다.

 

다음 예시는 Java에서 어떻게 아이템을 버퍼하지 않는 reader를 생성하는지 보여준다.

 

@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
	return new StepBuilder("step1", jobRepository)
				.<String, String>chunk(2, transactionManager)
				.reader(itemReader())
				.writer(itemWriter())
				.readerIsTransactionalQueue()
				.build();
}

 

5. Transaction Attributes

고립, 전파 그리고 타임아웃 세팅을 제어하기 위해 트랜잭션 속성을 사용할 수 있다. Spring core documentation에서 트랜잭션 속성을 세팅하는 더 많은 정보를 찾을 수 있다.

 

다음 예시는 Java에서 고립, 전파 그리고 타임아웃 트랜잭션 속성을 세팅한다.

 

@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
	DefaultTransactionAttribute attribute = new DefaultTransactionAttribute();
	attribute.setPropagationBehavior(Propagation.REQUIRED.value());
	attribute.setIsolationLevel(Isolation.DEFAULT.value());
	attribute.setTimeout(30);

	return new StepBuilder("step1", jobRepository)
				.<String, String>chunk(2, transactionManager)
				.reader(itemReader())
				.writer(itemWriter())
				.transactionAttribute(attribute)
				.build();
}

 

 

6. Registering ItemStream with a Step

Step은 lifecycle에서 필요한  때에 ItemStream callback을 돌봐야만 한다.

만약 Step이 실패하거나 재시작 될 필요가 있다면 필수적인데 ItemStream 인터페이스가 실행들 사이에 영구적인 상태에 대해 step이 정보를 얻기 때문이다. 

 

만약 ItemReader, ItemProcessor 혹은 ItemWriter가  스스로 ItemStream 인터페이스를 구현한다면, 이것들은 자동적으로 등록된다. 모든 다른 stream들은 별도로 등록되야 한다. 이것은 자주 delegate같은 간접적인 의존성이 reader와 writer로 주입되는 경우이다. stream element를 통해서 step에 stream을 등록할 수 있다.

 

다음 예시는 Java에서 어떻게 step에 stream을 등록하는지 보여준다.

 

Java Configuration

@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
	return new StepBuilder("step1", jobRepository)
				.<String, String>chunk(2, transactionManager)
				.reader(itemReader())
				.writer(compositeItemWriter())
				.stream(fileItemWriter1())
				.stream(fileItemWriter2())
				.build();
}

/**
 * In Spring Batch 4, the CompositeItemWriter implements ItemStream so this isn't
 * necessary, but used for an example.
 */
@Bean
public CompositeItemWriter compositeItemWriter() {
	List<ItemWriter> writers = new ArrayList<>(2);
	writers.add(fileItemWriter1());
	writers.add(fileItemWriter2());

	CompositeItemWriter itemWriter = new CompositeItemWriter();

	itemWriter.setDelegates(writers);

	return itemWriter;
}

 

앞선 예시에서 CompositeItemWriter는 ItemStream이 아니지만 두 대리자는 모두 ItemStream이다.

그러므로 대리자 writer 둘 다 정확하게 framework를 제어하기 위해 stream으로 명확하게  등록되야 할 필요가 없다. ItemReader는 Step의 직접적인 속성이기 때문에 명확히 등록될 필요가 없다. step은 지금 재시작 가능하고 reader와 writer의 상태는 올바르게 실패 이벤트를 저장한다.

 

7. Intercepting Step Execution

Job과 마찬가지로 사용자가 Step의 실행 동안 몇가지 기능들을 실행할 필요가 있을지 모르는 많은 이벤트들이 있다. 예를 들어, footer를 요구하는 flat file에 데이터를 쓰기 위해 ItemWriter는 Step이 완료되었을 때 footer가 쓰일 수 있도록 알림을 받을 필요가 있다. 이것은  많은 Step중의 하나가 listener를 스코프 할 때 해결될 수 있다.

 

StepListener의 확장을 구현하는 모든 클래스는 listeners 요소를 통해 step에 적용될 수 있다. 단, StepListener 인터페이스 자체는 적용될 수 없다. 왜냐하면 그것은 실제로 아무것도 포함하지 않기 때문이다. listeners 요소는 step, tasklet 또는 chunk 선언 안에서 유효하다. 리스너의 기능이 적용되는 레벨에서 리스너를 선언하는 것이 좋다. 또는, 리스너가 여러 기능을 가질 경우 (예: StepExecutionListenerItemReadListener 같은), 가장 세부적인 레벨에서 선언하라.

 

여기서 footer란 html의 footer가 아니라 파일 끝에 추가되는 것을 그냥 마지막에 등록되서 footer라고 표현한 것 같다. 또 StepListner는 인터페이스를 구현하는게 아니라 확장을 구현하는것으로 적혀있다. 

 

Java Configuration

@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
	return new StepBuilder("step1", jobRepository)
				.<String, String>chunk(10, transactionManager)
				.reader(reader())
				.writer(writer())
				.listener(chunkListener())
				.build();
}

 

StepListener 인터페이스중의 하나를 구현하는 ItemReader, ItemWriter 또는 ItemProcessor는 만약 네임스페이스 <step> 원소 혹은 StepFactoryBean 팩토리중의 하나를 사용하면 Step에 자동적으로 등록된다. 이것은 component에 Step을 직접적으로 주입하도록 한다. 만약 listener가 다른 component 안에 있다면 그것을 명확하게 등록해야만 한다. (Registering ItemStream with a Step 처럼 말이다)


StepListener 인터페이스에 더해서, 어노테이션들이 같은 문제를 해결하기 위해 제공된다. Plain old Java objects(POJO)는 이러한 어노테이션으로 이해할 수 있는 StepListener type으로 변환시키는 메소드들을 가질 수 있다. ItemReader 혹은 ItemWriter 혹은 Tasklet같은 chunk 컴포넌트들의 custom 구현체들을 어노테이트하는건 또 흔하다. 어노테이션들은 XML parser에 의해 분석되는데 <listener/> 요소에 대해서도 마찬가지다. 빌더의 리스너 메소드와 함께 어노테이션도 등록되고 사용자는 XML 네임스페이스나 빌더를 사용해서 스텝에 리스너를 등록하기만 하면 된다

 



이번에는 Configuring Skip Logic 에서 부터 Intercepting Step Execution 을 읽어 보았다.

글을 이해하는것도 어렵지만 이것을 또 자연스럽게 한글로 바꾸는 것이 어려워서 이거... 다 읽기 좀 버겁긴 하네!

 

출처 :

https://docs.spring.io/spring-batch/docs/5.0.4/reference/html/step.html