Spring Batch 에서 제공하는 ItemReader 구현체를 통해서
대표적인 파일 포맷인 CSV, XML, JSON 을 조회하기 위한 코드 작성법을 알아봅시다.
name,age,year
user01,30,2000
user02,31,2001
user03,32,2002
user04,33,2003
user05,34,2004
user06,35,2005
user07,36,2006
user08,37,2007
user09,38,2008
user10,39,2009
package coding.toast.batch.flat_file;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
@RequiredArgsConstructor
@Slf4j
public class FlatFileConfiguration {
private final JobRepository jobRepository;
private final PlatformTransactionManager platformTransactionManager;
@Bean("FlatFileConfiguration-job-01")
public Job job() {
return new JobBuilder("FlatFileConfiguration-job-01", jobRepository)
.start(step1())
.build();
}
// Reader 작성법1: 직접 세팅하는 법
@Bean("FlatFileConfiguration-item-reader-01")
public ItemReader<Customer> itemReader() {
FlatFileItemReader<Customer> itemReader = new FlatFileItemReader<>();
itemReader.setResource(new ClassPathResource("customer.csv"));
// LineMapper => 하나의 라인을 읽어서 원하는 도메인으로 변경하는 역할
// => 내부적으로 LineTokenizer, FieldSetMapper 을 사용함
// LineTokenizer :
// 하나의 라인을 받으면, 그걸 특별한 조건 (ex: 구분자)에 따라 문자르
// 토큰화한다. 토큰화한 정보들은 모아서 FieldSet 인스턴스에 저장
//
// FieldSetMapper : LineTokenizer 가 반환한 FieldSet 을 활용하여
// 원하는 도메인 객체로 변환하는 작업을 담당한다.
DefaultLineMapper<Customer> lineMapper = new DefaultLineMapper<>();
DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
tokenizer.setNames("name", "age", "year");
lineMapper.setLineTokenizer(tokenizer);
BeanWrapperFieldSetMapper<Customer> fieldSetMapper
= new BeanWrapperFieldSetMapper<>();
fieldSetMapper.setTargetType(Customer.class);
lineMapper.setFieldSetMapper(fieldSetMapper);
itemReader.setLineMapper(lineMapper);
itemReader.setLinesToSkip(1);
return itemReader;
}
@Bean("FlatFileConfiguration-step-01")
public Step step1() {
return new StepBuilder("FlatFileConfiguration-step-01", jobRepository)
.<Customer, Customer>chunk(5, platformTransactionManager)
.reader(itemReader2())
.writer(items -> {
for (Customer item : items) {
System.out.println("item = " + item);
}
})
.build();
}
// Reader 작성법(2): builder 사용
@Bean("FlatFileConfiguration-item-reader-02")
public ItemReader<Customer> itemReader2() {
return new FlatFileItemReaderBuilder<Customer>()
.name("FlatFileConfiguration-item-reader-02")
.resource(new ClassPathResource("customer.csv"))
// .lineTokenizer(new DelimitedLineTokenizer())
.delimited().delimiter(",").names("name", "age", "year")
.fieldSetMapper(new BeanWrapperFieldSetMapper<>()).targetType(Customer.class)
.linesToSkip(1)
.build();
}
}
참고: 사용한 Customer.java
public record Customer(String name, int age, String year) { }
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.20</version>
</dependency>
<?xml version="1.0" encoding="UTF-8" ?>
<customers>
<customer id="1">
<id>1</id>
<name>홍길동</name>
<age>40</age>
</customer>
<customer id="2">
<id>2</id>
<name>김말순</name>
<age>41</age>
</customer>
<customer id="3">
<id>3</id>
<name>박찬호</name>
<age>42</age>
</customer>
</customers>
package coding.toast.batch.xml_reader;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.security.AnyTypePermission;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.xml.builder.StaxEventItemReaderBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.oxm.Unmarshaller;
import org.springframework.oxm.xstream.XStreamMarshaller;
import org.springframework.transaction.PlatformTransactionManager;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
@Configuration
@RequiredArgsConstructor
public class XMLConfiguration {
private final JobRepository jobRepository;
private final PlatformTransactionManager platformTransactionManager;
@Bean("XMLConfiguration-Job-01")
public Job job() {
return new JobBuilder("XMLConfiguration-Job-01", jobRepository)
.start(step1()).build();
}
@Bean("XMLConfiguration-Step-01")
public Step step1() {
return new StepBuilder("XMLConfiguration-Step-01", jobRepository)
.<Customer, Customer>chunk(5, platformTransactionManager)
.reader(customItemReader())
.writer(items -> {
for (Customer item : items) {
System.out.println("item = " + item);
}
})
.build();
}
@Bean("XMLConfiguration-Reader-01")
public ItemReader<Customer> customItemReader() {
return new StaxEventItemReaderBuilder<Customer>()
.name("XMLConfiguration-Reader-01")
.resource(new ClassPathResource("customer.xml"))
.addFragmentRootElements("customer")
.unmarshaller(itemUnmarshaller())
.build();
}
@Bean("XMLConfiguration-Unmarshaller-01")
public Unmarshaller itemUnmarshaller() {
Map<String, Class<?>> alias = new HashMap<>();
alias.put("customer", Customer.class); // 항상 루트 명부터!
alias.put("id", Long.class);
alias.put("name", String.class);
alias.put("age", Integer.class);
XStreamMarshaller xStreamMarshaller = new XStreamMarshaller();
xStreamMarshaller.setEncoding(StandardCharsets.UTF_8.toString());
XStream xStream = xStreamMarshaller.getXStream();
xStream.alias("customer", Customer.class);
xStream.addPermission(AnyTypePermission.ANY);
xStreamMarshaller.setAliases(alias);
return xStreamMarshaller;
}
}
참고: Customer.java 코드
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class Customer {
Long id;
String name;
Integer age;
}
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
[
{
"id": 1,
"name": "홍길동",
"age": 40
},
{
"id": 2,
"name": "박세리",
"age": 41
},
{
"id": 3,
"name": "박찬호",
"age": 42
}
]
package coding.toast.batch.json_reader;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.json.JacksonJsonObjectReader;
import org.springframework.batch.item.json.builder.JsonItemReaderBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
@RequiredArgsConstructor
public class JsonConfiguration {
private final JobRepository jobRepository;
private final PlatformTransactionManager platformTransactionManager;
@Bean("JsonConfiguration-Job-01")
public Job job() {
return new JobBuilder("JsonConfiguration-Job-01", jobRepository)
.start(step1()).build();
}
@Bean("JsonConfiguration-Step-01")
public Step step1() {
return new StepBuilder("JsonConfiguration-Step-01", jobRepository)
.<Customer, Customer>chunk(5, platformTransactionManager)
.reader(customerItemReader())
.writer(items -> {
for (Customer item : items) {
System.out.println("item = " + item);
}
})
.build();
}
@Bean("JsonConfiguration-Reader-01")
public ItemReader<Customer> customerItemReader() {
return new JsonItemReaderBuilder<Customer>()
.name("jsonReader")
.resource(new ClassPathResource("customer.json"))
.jsonObjectReader(new JacksonJsonObjectReader<>(Customer.class))
.build();
}
}
참고: Customer.java 코드
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class Customer {
Long id;
String name;
Integer age;
}
https://github.com/spring-projects/spring-batch-extensions/tree/main/spring-batch-excel 를 참고해주세요.
추후에 시간이 되면 예시를 작성해보겠습니다.