FlatFileItemReader
- 일반적인 CSV, TSV 또는 고정형 등 파일 처리용 ItemReader 구현체
- 구현 스키마가 파일에 없는 경우에 사용 할 수 있다
- Resource(파일) 과 LineMapper(DefaultLineMapper) 구현체로 구성
구현 관계
- Resource
- FileSystemResource : 절대 경로 또는 파일 리소스를 찾는 방식
- ClassPathResource: ClassLoader를 통해서 ClassPath를 통해 리소스를 찾는 방식
- LineMapper
- 파일의 row를 Object로 변환해서 FlatFileItemReader로 Return
- 파일을 읽은 문자열을 토큰화 하여 Object에 Mapping 하는 과정
- FieldSet : row를 Fields로 구분하여 만든 Token으로 자른 결과
- LineTokenizer: 입력 받은 row를 FieldSet으로 변환해서 제공
- FieldSetMapper: FieldSet Object를 POJO 형태로 매핑
실제 처리 절차
FixedLengthTokenizer
- 고정형 파일을 처리 하기 위한 Tokenizer
고정형 Sample file
Aimee CHoover 7341Vel Avenue Mobile AL35928
Jonas UGilbert 8852In St. Saint Paul MN57321
Regan MBaxter 4851Nec Av. Gulfport MS33193
Octavius TJohnson 7418Cum Road Houston TX51507
Sydnee NRobinson 894 Ornare. Ave Olathe KS25606
Stuart KMckenzie 5529Orci Av. Nampa ID18562
Petra ZLara 8401Et St. Georgia GA70323
Cherokee TLara 8516Mauris St. Seattle WA28720
Athena YBurt 4951Mollis Rd. Newark DE41034
Kaitlin MMacias 5715Velit St. Chandler AZ86176
Leroy XCherry 7810Vulputate St. Seattle WA37703
Connor WMontoya 4122Mauris Av. College AK99743
Byron XMedina 7875At Road Rock Springs WY37733
Penelope YSandoval 2643Fringilla Av. College AK99557
Rashad VOchoa 6587Lacus Street Flint MI96640
Jordan UOneil 170 Mattis Ave Bellevue WA44941
Caesar ODaugherty 7483Libero Ave Frankfort KY56493
Wynne DRoth 8086Erat Street Owensboro KY50476
Robin IRoberson 8014Pellentesque Street Casper WY84633
Adrienne CCarpenter 8141Aliquam Avenue Tucson AZ85057
고정형 Sample Code
/**
* Customer.java
*/
@Getter
@Setter
public class Customer {
private String firstName;
private String middleInitial;
private String lastName;
private String addressNumber;
private String street;
private String address;
private String city;
private String state;
private String zipCode;
public Customer() {
}
public Customer(String firstName, String middleName, String lastName, String addressNumber, String street, String city, String state, String zipCode) {
this.firstName = firstName;
this.middleInitial = middleName;
this.lastName = lastName;
this.addressNumber = addressNumber;
this.street = street;
this.city = city;
this.state = state;
this.zipCode = zipCode;
}
@Override
public String toString() {
return "Customer{" +
"firstName='" + firstName + '\'' +
", middleInitial='" + middleInitial + '\'' +
", lastName='" + lastName + '\'' +
", address='" + address + '\'' +
", addressNumber='" + addressNumber + '\'' +
", street='" + street + '\'' +
", city='" + city + '\'' +
", state='" + state + '\'' +
", zipCode='" + zipCode + '\'' +
'}';
}
}
/**
* FixedWidthJobConfiguration.java
*/
@Configuration
public class FixedWidthJobConfiguration {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
@StepScope
public FlatFileItemReader<Customer> fixedWidthItemReader(
@Value("#{jobParameters['customerFile']}") Resource inputFile) {
return new FlatFileItemReaderBuilder<Customer>()
.name("fixedWidthItemReader")
.resource(inputFile)
.fixedLength()
.columns(new Range[]{new Range(1, 11), new Range(12, 12), new Range(13, 22),
new Range(23, 26), new Range(27, 46), new Range(47, 62), new Range(63, 64),
new Range(65, 69)})
.names(new String[]{"firstName", "middleInitial", "lastName",
"addressNumber", "street", "city", "state", "zipCode"})
.targetType(Customer.class)
.build();
}
@Bean
public ItemWriter<Customer> fixedWidthItemWriter() {
return (items) -> items.forEach(System.out::println);
}
@Bean
public Step fixedWidthStep() {
return this.stepBuilderFactory.get("fixedWidthStep")
.<Customer, Customer>chunk(10)
.reader(fixedWidthItemReader(null))
.writer(fixedWidthItemWriter())
.build();
}
@Bean
public Job fixedWidthJob() {
return this.jobBuilderFactory.get("fixedWidthJob")
.start(fixedWidthStep())
.build();
}
}
DelimitedLineTokenizer
- CSV, TSV 등 가변형 데이터를 처리 하기 위한 Tokenizer
가변형 Sample data
Aimee,C,Hoover,7341,Vel Avenue,Mobile,AL,35928
Jonas,U,Gilbert,8852,In St.,Saint Paul,MN,57321
Regan,M,Baxter,4851,Nec Av.,Gulfport,MS,33193
Octavius,T,Johnson,7418,Cum Road,Houston,TX,51507
가변형 Sample Code
/**
* FlatFileItemReader.java
*/
@Bean
@StepScope
public FlatFileItemReader<Customer> customerFlatFileItemReader(
@Value("#{jobParameters['customerFile']}") Resource inputFile) {
logger.info("Activated flat file reader: delimited");
return new FlatFileItemReaderBuilder<Customer>()
.name("customerItemReader")
.resource(inputFile)
.delimited()
.delimiter(",") // default ","
.names("firstName",
"middleInitial",
"lastName",
"addressNumber",
"street",
"city",
"state",
"zipCode")
.targetType(Customer.class)
.build();
}
만약 사용자가 address + street를 합쳐서 address 로 변경 하려면 FieldSetMapper를 Customize 해야 한다.
/**
* {@link BeanWrapperFieldSetMapper} 대신 {@link CustomerFieldMapper}를 이용한다.
*/
@Bean
@StepScope
public FlatFileItemReader<Customer> customerFlatFileItemReader(
@Value("#{jobParameters['customerFile']}") Resource inputFile) {
logger.info("Activated flat file reader: custom field mapper");
return new FlatFileItemReaderBuilder<Customer>()
// ItemStream 인터페이스는 애플리케이션 내 각 스텝의 ExecutionContext에 추가되는 특정 키의
// 접두문자로 사용될 이름이 필요
.name("customerItemReader")
.delimited()
.names("firstName",
"middleInitial",
"lastName",
"addressNumber",
"street",
"city",
"state",
"zipCode")
.fieldSetMapper(new CustomerFieldMapper())
.resource(inputFile)
.build();
}
public static class CustomerFieldMapper implements FieldSetMapper<Customer> {
@Override
public Customer mapFieldSet(FieldSet fieldSet) throws BindException {
return Customer.builder()
.firstName(fieldSet.readString("firstName"))
.middleInitial(fieldSet.readString("middleInitial"))
.lastName(fieldSet.readString("lastName"))
.address(fieldSet.readString("addressNumber") + " " + fieldSet.readString("street"))
.city(fieldSet.readString("city"))
.state(fieldSet.readString("state"))
.zipCode(fieldSet.readString("zipCode"))
.build();
}
}
Custom Record Parsing 하기 위한 LineTokenizer 사용 예시
@Bean
@StepScope
public FlatFileItemReader<Customer> customerFlatFileItemReader(
@Value("#{jobParameters['customerFile']}") Resource inputFile) {
// @Value를 사용하기 위해서는 `@StepScope`를 선언 해야 한다.
// Spring Bean이 생성되는 시점이 아닌 `@StepScope`가 수행 되는 시점에 지연 시키는것(Late Binding)
// Bean이 생성되는 시점에는 inputFile 값을 초기화 할 수 없으므로 에러가 발생함!!!
return new FlatFileItemReaderBuilder<Customer>()
.name("customerItemReader")
.lineTokenizer(new CustomerFileLineTokenizer())
.targetType(Customer.class)
.resource(inputFile)
.build();
}
public static class CustomerFileLineTokenizer implements LineTokenizer {
private final String delimiter = ",";
private final String[] names = {
"firstName",
"middleInitial",
"lastName",
"address",
"city",
"state",
"zipCode"
};
private final FieldSetFactory fieldSetFactory = new DefaultFieldSetFactory();
@Override
public FieldSet tokenize(String record) {
final String[] fields = record.split(delimiter);
final List<String> parsedFields = new ArrayList<>();
for (int i = 0; i < fields.length; i++) {
if (i == 4) {
parsedFields.set(i - 1, parsedFields.get(i - 1) + " " + fields[i]);
continue;
}
parsedFields.add(fields[i]);
}
return fieldSetFactory.create(parsedFields.toArray(new String[0]), names);
}
}
Reference
'Framework > Spring-Batch' 카테고리의 다른 글
(10) Spring Batch ItemWriter (0) | 2022.11.16 |
---|---|
(9) Spring Batch ItemProcessor (0) | 2022.11.16 |
(8) Spring Batch ItemReader (0) | 2022.11.16 |
(7) Spring Batch Chunk (0) | 2022.11.16 |
(6) Spring Batch Step (0) | 2022.11.16 |