<Spring Batch> 예제 프로그램 따라하기
by BFine
스케줄러 형태로 되어있는 배치는 많이 봤는데 Spring Batch를 사용한 배치 프로그램은 본적이 없는 것 같다.
궁금하기도 하고 공부해뒀다가 나중에 시간될때 적용해봐야겠다!!!
예제 : spring.io/guides/gs/batch-processing/
가. 개요
a. 의존성
- Spring boot 4.3 , Spring Batch Starter로 간단하게 구현할 수 있다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-batch'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'org.hsqldb:hsqldb'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.batch:spring-batch-test'
}
- 여기서는 데이터를 저장하기 위해 내장형 DB HSQL을 사용한다.
b. 로직
- csv 파일에 있는 이름을 읽어서 대문자로 변경하고 DB에 저장한다. 그리고 DB에서 값을 읽어 보여준다.
c. 파일
- sql 파일과 csv 파일을 사용하는데 resources 폴더에 추가하자
- sample-date.csv
Jill,Doe
Joe,Doe
Justin,Doe
Jane,Doe
John,Doe
- schema-all.sql
DROP TABLE people IF EXISTS;
CREATE TABLE people (
person_id BIGINT IDENTITY NOT NULL PRIMARY KEY,
first_name VARCHAR(20),
last_name VARCHAR(20)
);
나. 클래스
a. Person
- Parsing error at line: 1 in resource=[class path resource [sample-data.csv]], input=[Jill,Doe]
=> FlatFileItemReader의 lineMapper에서 오류가 발생한다.
- 위의 오류는 디폴트 생성자가 없기 때문에 발생한다. 소스코드 내부를 찾아 들어가보면 type.newInstance()로
객체를 만드는 것을 볼수있는데 얘는 디폴트생성자로 객체를 만들기 때문에 오류가 발생한다.
- 이부분을 방지하기 위해 꼭! 디폴트 생성자를 만들자!! (@NoArgsConstructor)
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Person {
private String lastName;
private String firstName;
}
b. PersonItemProcessor
- ItemProceesor를 상속받아 핵심로직을 처리한다. 같은이름의 인터페이스가 있으니 주의하자!
@Slf4j
public class PersonItemProcessor implements ItemProcessor<Person,Person> {
@Override
public Person process(Person item){
final String firstName = item.getFirstName().toUpperCase();
final String lastName = item.getLastName().toUpperCase();
final Person transformedPerson = new Person(firstName, lastName);
log.info("Converting ({}) into ({})",item,transformedPerson);
return transformedPerson;
}
}
c. BatchConfiguration
- 배치의 동작순서가 딱 보이는 Bean 설정 클래스이다.
@Configuration
@EnableBatchProcessing
@RequiredArgsConstructor
public class BatchConfiguration {
public final JobBuilderFactory jobBuilderFactory;
public final StepBuilderFactory stepBuilderFactory;
. . .
}
- @EnableBatchProcessing 어노테이션을 통해 dataSource를 가져오고 batch 처리와 관련 Bean들을
자동으로 가져오며 아래에 선언하는 여러 @Bean들을 연결시켜준다.
- 내부에 설정할 Bean들을 확인해보자
@Bean
public FlatFileItemReader<Person> reader() {
return new FlatFileItemReaderBuilder<Person>()
.name("personItemReader")
.resource(new ClassPathResource("sample-data.csv"))
.delimited()
.names(new String[]{"firstName", "lastName"})
.fieldSetMapper(new BeanWrapperFieldSetMapper<Person>() {{
setTargetType(Person.class);
}})
.build();
}
@Bean
public PersonItemProcessor processor() {
return new PersonItemProcessor();
}
@Bean
public JdbcBatchItemWriter<Person> writer(DataSource dataSource) {
return new JdbcBatchItemWriterBuilder<Person>()
.itemSqlParameterSourceProvider
(new BeanPropertyItemSqlParameterSourceProvider<>())
.sql("INSERT INTO people (first_name, last_name)
VALUES (:firstName, :lastName)")
.dataSource(dataSource)
.build();
}
- 되게 직관적으로 메서드 이름만 봐도 어떻게 쓰일지가 한눈에 보인다.
- 먼저 reader()는 IteamReader를 생성하고 csv 파일로부터 데이터를 읽어서 Person 객체 형태로 파싱한다.
=> reader의 역할은 어떤 데이터를 읽어서 자바프로그램으로 처리할 수 있도록 객체로 만드는 것이다.
- processor()는 핵심 로직을 처리한다.
- writer()는 처리된 데이터를 말그대로 쓰는 역할을 한다. 결과를 반영하는 역할이다.
@Bean
public Step step1(JdbcBatchItemWriter<Person> writer) {
return stepBuilderFactory.get("step1")
.<Person, Person> chunk(10)
.reader(reader())
.processor(processor())
.writer(writer)
.build();
}
- 아래의 step1 메서드를 보면 위의 Bean들을 하나의 Step으로 묶어주는게 보인다.
- 여기서 chunk의 제네릭은 Inuput, Output 을 의미한다. 여기서는 결과도 Person이므로 동일하다.
- 즉 Step은 read -> process -> writer 형태의 단위를 의미하고 여러개가 될 수 있다.
@Bean
public Job importUserJob(JobCompletionNotificationListener listener, Step step1) {
return jobBuilderFactory.get("importUserJob")
.incrementer(new RunIdIncrementer())
.listener(listener)
.flow(step1)
.end()
.build();
}
- 여기서 당황스러울수 있는 부분은 JobCompletionNotificationListener 이 클래스이다.
=> 마치 Library 클래스 같지만 커스텀 클래스이다. 레퍼런스에는 미국식이라 그런지 미리 생성한걸 보여주지
않고 아래에 만드는걸 보여줘서 한참을 찾았다;; 어순이 목적어가 뒤에 나오는 거랑 비슷한걸까..
- Job을 생성하는 단계로 listener와 step을 등록한다. Step과 마찬가지로 여러개 등록이 가능하다.
d. JobCompletionNotificationListener
- 위의 Job을 생성할때 등록하는 listener 클래스를 만들자
- 여기서는 afterJob만 사용하지만 JobExecutionListenerSupport 살펴보면 beforeJob도 가지고 있다.
=> 마치 AOP처럼 전처리와 후처리를 한다.
- BatchStatus를 통해 Job의 처리 상태를 확인할 수 있다.
- afterJob은 결과값을 확인하고 알림(슬랙)을 보내는 용도로 사용하면 좋을 것 같다.
@Slf4j
@Component
@RequiredArgsConstructor
public class JobCompletionNotificationListener extends JobExecutionListenerSupport {
private final JdbcTemplate jdbcTemplate;
@Override
public void afterJob(JobExecution jobExecution) {
if(jobExecution.getStatus() == BatchStatus.COMPLETED) {
log.info("### JOB 끝남 ####");
jdbcTemplate.query("SELECT first_name, last_name FROM people",
(rs, row) -> new Person(
rs.getString(1),
rs.getString(2))
).forEach(person -> log.info(" <" + person + "> 디비에 등록됨"));
}
}
}
다. 테스트
a. 실행
- 결과 로그를 확인해보면 ...
b. 구조
- 어떻게 흘러가는지 그려보았다.
- 하나의 Job이 처리되는 모습을 그려보았다. 여기서 Job은 또 여러개가 될 수 있다.
예전에 Spring Batch 볼때는 꽤 어려웠던걸로 기억했었는데 예제 프로그램을 따라서 해보니까 쉬운 예제라
그럴 수 있지만되게 간단하면서도 직관적으로 역할이 분배 되어있고 구성도 딱딱 맞게 고정 되어있어서 놀라웠다.
확실히 스케쥴러만 사용하는것보다 구성만으로도 사용할 가치가 충분히 있어보인다.
레퍼런스 보면서 조금씩 세부적으로 정리해봐야겠다.
2021.09.19 - [공부(2021)/Spring Batch] - 0. 스프링 배치란?
2021.09.21 - [공부(2021)/Spring Batch] - 1. JobParameters, ExecutionContext
2021.09.22 - [공부(2021)/Spring Batch] - 2. JobRepository, Config Customizing
2021.09.27 - [공부(2021)/Spring Batch] - 3. 배치 테이블 Embedded DB(H2)로 따로 관리하기
2021.10.02 - [공부(2021)/Spring Batch] - 4. Chunk 기반 배치, ItemReader
2021.10.03 - [공부(2021)/Spring Batch] - 5. ItemProcessor, ItemWriter
'공부 > Spring Batch' 카테고리의 다른 글
<Spring Batch> 3. 배치 테이블 Embedded DB(H2)로 따로 관리하기 (1) | 2021.09.27 |
---|---|
<Spring Batch> 2. JobRepository, Config Customizing (0) | 2021.09.22 |
<Spring Batch> 1. JobParameters, ExecutionContext (0) | 2021.09.21 |
<Spring Batch> 0. 스프링 배치란? (2) | 2021.09.19 |
<Spring Batch> API 호출해서 DB에 저장하기(with JPA) (4) | 2021.03.01 |
블로그의 정보
57개월 BackEnd
BFine