[Spring Batch] ItemReader / ItemWriter
Step에서 데이터를 읽어오는 역할을 담당하는 ItemReader를 구현할 때는 개발자가 직접 ItemReader 인터페이스를 구현해 read 메서드를 구현한 후 Step에 추가할 수도 있고, 스프링 배치가 기본적으로 제공하는 ItemReader를 사용할 수도 있다.
@Bean
@StepScope
public FlatFileItemReader<StudentCsv> flatFileItemReader(
@Value("#{jobParameters['inputFile']}") FileSystemResource fileSystemResource
) {
FlatFileItemReader<StudentCsv> flatFileItemReader = new FlatFileItemReader<>();
// flatFileItemReader.setResource(new FileSystemResource(new File("C:\\Users\\admin\\Desktop\\spring-batch\\InputFiles\\students.csv")));
flatFileItemReader.setResource(fileSystemResource);
flatFileItemReader.setLineMapper(new DefaultLineMapper<>(){
{
setLineTokenizer(new DelimitedLineTokenizer("|") {
{
setNames("ID","First Name", "Last Name", "Email");
}
});
setFieldSetMapper(new BeanWrapperFieldSetMapper<>() {
{
setTargetType(StudentCsv.class);
}
});
}
});
flatFileItemReader.setLinesToSkip(1);
return flatFileItemReader;
}
스프링 배치가 제공하는 FlatFileItemReader 클래스를 사용해 csv 파일에서 데이터를 읽어오는 예시이다.
데이터소스의 경로 / csv파일 구분자 / Model 객체를 준비하자.
데코레이터 패턴과 자바의 Double Brace initialization 기법을 사용한다. (안 써도 상관없다)
데코레이터 패턴은 디자인 패턴 중 하나고..더블 브레이스 초기화 기법은 중괄호 2개를 써서 하나로는 익명 내부 클래스를, 나머지 하나로는 인스턴스 초기화 블록을 선언해 객체 속성을 초기화 하는 기법이다.
기본적으로 구분자는 "," 를 사용하는데 변경하려면 DelimitedLineTokenizer 를 생성할 때 생성자에 구분자를 입력하면 된다.
@StepScope 애너테이션은 배치에서 스텝의 실행 컨텍스트 내의 빈 생명주기를 관리할 때 사용된다.
@Bean 애너테이션으로 등록한 FlatFileItemReader 빈은 스텝 실행 시점에서만 생성되고 완료될 시 소멸돼 각 스텝 실행마다 빈의 상태를 초기화한다.
예시에서는 스프링 부트 애플리케이션 실행 시 인자 값으로 데이터소스 경로를 넣고 그 경로를 jobParameter에 넣은 후 사용한다.
FlatFileItemReader는 csv 파일을 읽을 때 사용한다.
csv 파일 외에도 스프링 배치는 xml, json, jdbc 등 다양한 데이터소스로부터 데이터를 읽어오는 구현체를 제공한다.
(JdbcCursorItemReader, JsonItemReader ... )
json을 다룰 때는 모델 객체와 json 변수명을 @JsonProperty / @JsonIgnoreProperties 애너테이션을 사용해 적절히 매핑해주고
jdbc를 다룰 때는 접속하는 DataSource 객체를 적절하게 구현 해 주는 등 사용하는 데이터소스마다 주의해서 구현하자.
웬만하면 스프링 배치를 사용할 때 실제 데이터베이스와 소통하게 되니.. jdbc 관련 내용을 자세하게 살펴보자.
스프링에서 DataSource 객체는 jdbc api를 사용할 때 데이터베이스 연결을 제공하는 역할을 수행하고, 커넥션 풀과 트랜잭션 관리 기능 등을 제공한다.
JPA를 사용할 때도 내부적으로는 DataSource를 사용한다.
DataSource가 담고 있는 정보를 Hibernate 등 JPA 구현체에게 제공하고 구현체가 엔티티 객체와 데이터베이스 테이블 간의 영속성을 관리한다.
스프링부트는 properties 파일의 데이터베이스 연결 관련 설정을 읽고 DataSource 객체를 만들어 사용하는데, 필요한 경우 자바 코드로 직접 DataSource 객체를 커스텀 할 수 있다.
spring.datasource.jdbc-url=jdbc:mysql://localhost:3306/spring_batch
spring.datasource.username=root
spring.datasource.password=root
spring.universitydatasource.jdbc-url=jdbc:mysql://localhost:3306/university
spring.universitydatasource.username=root
spring.universitydatasource.password=root
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
};
@Bean
@ConfigurationProperties(prefix = "spring.universitydatasource")
public DataSource universityDataSource() {
return DataSourceBuilder.create().build();
};
하나의 애플리케이션에서 여러 데이터베이스와 통신할 때는 DataSource를 @ConfigurationProperties 애너테이션을 사용해서 정의한다.
@Primary 애너테이션은 해당 DataSource를 애플리케이션의 기본 데이터 소스로 지정함을 의미한다. (Autowired 시 기본)
MyBatis나 JPA를 사용해서 데이터베이스와 통신할 경우 사실 DataSource 객체를 직접 사용할 일은 없지만...
스프링 배치를 사용할 때 Tasklet Step으로 데이터베이스 연결이 제대로 됐는지 확인하는 로직을 작성할 때 DataSource를 사용하면 편하기도 하고
JdbcCursorItemReader / JpaItemWriter / JdbcBatchItemWriter 등 스프링 배치가 제공하는 구현체들은 내부적으로 DataSource 객체를 사용해 데이터베이스 연결을 관리하니..
스프링 배치를 사용할 때는 DataSoure 객체를사용하는 편이 합리적이다.
csv, db, json 외에도 MSA를 사용하는 경우 스프링에서 RestTemplate 객체를 사용해 외부 api 서버에서 데이터를 읽어오는 경우도 있는데...
이 때는 외부 api로부터 데이터를 가져올 때 reader 적절하게 설정해서 데이터를 모두 읽었을 때 null을 반환하도록 메서드를 작성하자.
스프링 배치는 Reader와 마찬가지로 Writer에 대해서도 여러 구현체를 제공한다.
@Bean
@StepScope
FlatFileItemWriter<StudentJdbc> flatFileItemWriter(
@Value("#{jobParameters['outputFile']}") FileSystemResource fileSystemResource
) {
FlatFileItemWriter<StudentJdbc> flatFileItemWriter = new FlatFileItemWriter<>();
flatFileItemWriter.setResource(fileSystemResource);
flatFileItemWriter.setHeaderCallback(new FlatFileHeaderCallback() {
@Override
public void writeHeader(Writer writer) throws IOException {
writer.write("Id,First Name,Last Name,Email");
}
});
flatFileItemWriter.setLineAggregator(new DelimitedLineAggregator<>(){
{
setDelimiter(",");
setFieldExtractor(new BeanWrapperFieldExtractor<>(){
{
setNames(new String[] {"id", "firstName", "lastName", "email"});
}
});
}
});
flatFileItemWriter.setFooterCallback(new FlatFileFooterCallback() {
@Override
public void writeFooter(Writer writer) throws IOException {
writer.write("Created @" + new Date());
}
});
return flatFileItemWriter;
}
제공하는 구현체 중 하나인 FlatFileItemWriter로 데이터베이스에서 읽어온 값을 csv파일로 작성하는 예시이다.
header와 footer를 설정할 수 있고 FlatFileItemReader와 마찬가지로 구분자와 BeanWrapper를 지정해 줘야 한다.
이 외에도 json, xml, jdbc 등 다양한 구현체를 제공하는데...
데이터베이스 데이터를 작성하는 경우를 자세하게 살펴보자.
JdbcBatchItemWriter
@Bean
public JdbcBatchItemWriter<Student> writer(DataSource dataSource) {
JdbcBatchItemWriter<Student> writer = new JdbcBatchItemWriter<>();
writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>());
writer.setSql("INSERT INTO student (firstname, lastname) VALUES (:p1, :p2)");
writer.setDataSource(dataSource);
return writer;
}
내부적으로 jdbc를 직접 사용해 쿼리를 작성하는 방식이다.
직관적으로 이해하기는 쉽지만.. 요즘은 jdbc를 직접 사용하기보다는 jdbc를 추상화한 기술을 사용한다.
RepositoryItemWriter
@Bean
public RepositoryItemWriter<Student> writer(StudentRepository repository) {
RepositoryItemWriter<Student> writer = new RepositoryItemWriter<>();
writer.setRepository(repository);
writer.setMethodName("save");
return writer;
}
Spring Data JPA를 사용하는 ItemWriter 구현체이다.
리포지토리의 메서드를 직접 호출하는 방식으로 데이터베이스에 데이터를 쓸 수 있다.
jdbc, JPA, MyBatis 등.. 자주 사용되는 쿼리 매퍼에 대한 구현체를 제공한다.
혹시나 구현체가 따로 제공되지 않는 기술을 사용할 때는 ItemWriter 인터페이스를 구현하는 CustomItemWriter를 작성해서 사용하면 된다.
Job에 포함되는 Step을 구성할 때
자주 사용되는 구조인 Reader - Processor - Writer 구조를 사용하면 데이터를 chunk 기반, 병렬로 처리할 수 있어 대규모 데이터를 다룰 때 효과적이지만..
복잡한 문제를 해결하기 위해 스프링 배치를 사용해 비즈니스 로직을 작성할 때는 Reader - Processor - Writer 구조만으로 해결할 수 없는 경우가 많다.
이 경우 Tasklet Step을 도입해 chunk 기반 처리와 Tasklet Step을 혼합해서 사용하거나, Tasklet Step만으로 Job을 구성해보자.
이전 Step의 결과에 따라 다음 Step이 달라지는 것 처럼 비선형적으로 데이터를 처리하는 경우 Tasklet Step만으로 Job을 구성하는 방식이 합리적이다.
Tasklet Step은 chunk 방식보다 데이터 처리에 있어서 더 유연하고, 세밀하게 조작할 수 있어 복잡한 요구사항을 구현할 때 효과적이니... chunk 방식이 가지는 대용량 데이터 처리를 포기하더라도 일단 작동하는 프로그램을 작성할 수는 있다.
항상 해결할 문제에 초점을 맞춰서 구현 방식을 선택하자.
순차적이고 의존적인 Step으로 구성되어 있더라도 그 중 일부 작업에서 대량의 데이터 처리가 필요하고, 또 chunk 기반 처리가 가능하다면 그 부분만 chunk 기반으로 처리하도록 하고 나머지 부분은 Tasklet으로 구현하는 방법을 선택할 수도 있다.
'Spring > Spring Batch' 카테고리의 다른 글
[Spring Batch] Chunk 아키텍처 (0) | 2024.08.24 |
---|---|
[Spring Batch] Flow 아키텍처 (2) | 2024.07.21 |
[Spring Batch] Job / Step 아키텍처 (0) | 2024.07.07 |
[Spring Batch] 배치 도메인 이해 (0) | 2024.06.30 |
[Spring Batch] 스프링 배치 내부 흐름 (0) | 2024.02.25 |
댓글
이 글 공유하기
다른 글
-
[Spring Batch] Flow 아키텍처
[Spring Batch] Flow 아키텍처
2024.07.21 -
[Spring Batch] Job / Step 아키텍처
[Spring Batch] Job / Step 아키텍처
2024.07.07 -
[Spring Batch] 배치 도메인 이해
[Spring Batch] 배치 도메인 이해
2024.06.30 -
[Spring Batch] 스프링 배치 내부 흐름
[Spring Batch] 스프링 배치 내부 흐름
2024.02.25