[스프링 배치] resources/ 경로의 파일 읽어와 여러 방법으로 필드 매핑하기 (FieldSetMapper, LineTokenizer)

반응형
728x90
반응형

전체 코드 보기

package com.seohae.batch.batch.fileBatch1.job;

import com.seohae.batch.batch.fileBatch1.entity.Customer;
import com.seohae.batch.batch.fileBatch1.mapper.CustomerFieldSetMapper;
import com.seohae.batch.batch.fileBatch1.mapper.CustomerFileLineTokenizer;
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.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
import org.springframework.batch.item.file.transform.Range;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

/**
 * --job.name=flatFileItemJob
 */
@Slf4j
@Configuration
@RequiredArgsConstructor
public class FlatFileItemJobConfig {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    /**
     * Job
     * @return
     */
    @Bean
    public Job flatFileItemJob() {
        return jobBuilderFactory.get("flatFileItemJob")
                .incrementer(new RunIdIncrementer())
                .start(flatFileItemStep())
                .build();
    }

    /**
     * Step
     * @return
     */
    @Bean
    public Step flatFileItemStep() {
        return stepBuilderFactory.get("flatFileItemStep")
                .<Customer, Customer>chunk(10)
                .reader(flatFileItemReaderStep())
                .writer(flatFileItemWriterStep())
                .build();
    }

    /**
     * 1) 파일을 읽어들여 Customer.class 로 변환한다.
     * @return
     */
    @Bean
    @StepScope
    public FlatFileItemReader<Customer> flatFileItemReaderStep() {
        Resource inputFile = new ClassPathResource("input/customerFixedWidth.txt");

        return new FlatFileItemReaderBuilder<Customer>()
                .name("FlatFileItemReaderStep")
                .resource(inputFile)
                .fixedLength()
                /* Range 객체 (파싱해야할 칼럼의 시작 위치와 종료 위치를 나타낸다) 의 배열 지정 */
                .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("firstName", "middleInitial", "lastName",
                        "addressNumber", "street", "city", "state", "zipCode")
                .targetType(Customer.class)
                .build();
    }

    /**
     * 2) .delimited() 추가버전 (구분값 파일 읽기)
     * @return
     */
    @Bean
    @StepScope
    public FlatFileItemReader<Customer> flatFileItemReaderStep2() {
        Resource inputFile = new ClassPathResource("input/customerFixedWidth.txt");

        return new FlatFileItemReaderBuilder<Customer>()
                .name("FlatFileItemReaderStep")
                /* 구분자로 구분되어있을 경우 사용, default: 콤마 */
                .delimited()
                .names("firstName", "middleInitial", "lastName",
                        "addressNumber", "street", "city", "state", "zipCode")
                .resource(inputFile)
                .targetType(Customer.class)
                .build();
    }

    /**
     * 3) CustomerFieldSetMapper.java 설정 추가버전
     * @return
     */
    @Bean
    @StepScope
    public FlatFileItemReader<Customer> flatFileItemReaderStep3() {
        Resource inputFile = new ClassPathResource("input/customerFixedWidth.txt");

        return new FlatFileItemReaderBuilder<Customer>()
                .name("FlatFileItemReaderStep")
                /* 구분자로 구분되어있을 경우 사용, default: 콤마 */
                .delimited()
                .names("firstName", "middleInitial", "lastName",
                        "addressNumber", "street", "city", "state", "zipCode")
                .resource(inputFile)
                .fieldSetMapper(new CustomerFieldSetMapper())
                .targetType(Customer.class)
                .build();
    }

    /**
     * 4) CustomerFileLineTokenizer.java 설정 추가버전
     * @return
     */
    @Bean
    @StepScope
    public FlatFileItemReader<Customer> flatFileItemReaderStep4() {
        Resource inputFile = new ClassPathResource("input/customerFixedWidth.txt");

        return new FlatFileItemReaderBuilder<Customer>()
                .name("FlatFileItemReaderStep")
                .lineTokenizer(new CustomerFileLineTokenizer())
                .resource(inputFile)
                .targetType(Customer.class)
                .build();
    }

    /**
     * ItemWriter.write 메서드에 전달된 List 내 각 아이템에 대해 출력한다.
     * @return
     */
    @Bean
    public ItemWriter<Customer> flatFileItemWriterStep() {
        return (items) -> items.forEach(System.out::println);
    }


}

 

 

resources/input 경로에 파일 추가

  • 1) customerFixedWidth.txt
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
Laura      AHammond   5329Accumsan Rd.        Orlando         FL54848
Lacey      UHolman    2421Metus. Rd.          Billings        MT87006
Edward     IMarquez   4069Ornare Ave          Lansing         MI66221
Veda       ZMatthews  8509Donec Av.           Evansville      IN26057

 

 

 

첫번째 방법. 파일 그대로 읽어오기

  • 1) Reader 메서드 추가
    /**
     * 1) 파일을 읽어들여 Customer.class 로 변환한다.
     * @return
     */
    @Bean
    @StepScope
    public FlatFileItemReader<Customer> flatFileItemReaderStep() {
        Resource inputFile = new ClassPathResource("input/customerFixedWidth.txt");

        return new FlatFileItemReaderBuilder<Customer>()
                .name("FlatFileItemReaderStep")
                .resource(inputFile)
                .fixedLength()
                /* Range 객체 (파싱해야할 칼럼의 시작 위치와 종료 위치를 나타낸다) 의 배열 지정 */
                .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("firstName", "middleInitial", "lastName",
                        "addressNumber", "street", "city", "state", "zipCode")
                .targetType(Customer.class)
                .build();
    }

 

 

 

 

두번째방법. 파일이 콤마(,)구분으로 되어있을 경우

  • 1) Reader 메서드 추가
...
    /**
     * 2) .delimited() 추가버전 (구분값 파일 읽기)
     * @return
     */
    @Bean
    @StepScope
    public FlatFileItemReader<Customer> flatFileItemReaderStep2() {
        Resource inputFile = new ClassPathResource("input/customerFixedWidth.txt");

        return new FlatFileItemReaderBuilder<Customer>()
                .name("FlatFileItemReaderStep")
                /* 구분자로 구분되어있을 경우 사용, default: 콤마 */
                .delimited()
                .names("firstName", "middleInitial", "lastName",
                        "addressNumber", "street", "city", "state", "zipCode")
                .resource(inputFile)
                .targetType(Customer.class)
                .build();
    }
...

 

 

 

세번째방법. 필드 매핑을 직접 설정해주기

  • 1) CustomerFieldSetMapper.java 생성
package com.seohae.batch.batch.fileBatch1.mapper;

import com.seohae.batch.batch.fileBatch1.entity.Customer;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.validation.BindException;

/**
 * flatFileItemReaderStep3 의 .fieldSetMapper(new CustomerFieldSetMapper()) 설정 추가
 */
public class CustomerFieldSetMapper implements FieldSetMapper<Customer> {
    /**
     * 커스터 매퍼 생성 (Customer 타입으로)
     * 원하는 값으로 필드를 매핑할 수 있다.
     * fieldSet 은 타입별로도 가능
     * @param fieldSet
     * @return
     * @throws BindException
     */
    @Override
    public Customer mapFieldSet(FieldSet fieldSet) throws BindException {
        Customer customer = new Customer();
        customer.setAddress(fieldSet.readString("addressName") + " " + fieldSet.readString("street"));
        customer.setCity(fieldSet.readString("city"));
        customer.setFirstName(fieldSet.readString("firstName"));
        customer.setLastName(fieldSet.readString("lastName"));
        customer.setMiddleInitial(fieldSet.readString("middleInitial"));
        customer.setState(fieldSet.readString("state"));
        customer.setZipCode(fieldSet.readString("zipCode"));

        return customer;
    }
}

 

  • 2) Reader 메서드 추가
...
    /**
     * 3) CustomerFieldSetMapper.java 설정 추가버전
     * @return
     */
    @Bean
    @StepScope
    public FlatFileItemReader<Customer> flatFileItemReaderStep3() {
        Resource inputFile = new ClassPathResource("input/customerFixedWidth.txt");

        return new FlatFileItemReaderBuilder<Customer>()
                .name("FlatFileItemReaderStep")
                /* 구분자로 구분되어있을 경우 사용, default: 콤마 */
                .delimited()
                .names("firstName", "middleInitial", "lastName",
                        "addressNumber", "street", "city", "state", "zipCode")
                .resource(inputFile)
                .fieldSetMapper(new CustomerFieldSetMapper())
                .targetType(Customer.class)
                .build();
    }
...

 

.delimited() 메서드가 추가되었고, defualt 는 콤마(,)이다. 파일이 , 구분으로 되어있다고 가정하자.

 

 

 

네번째방법. 각 레코드별로 가져와서 설정해주기

  • 1) CustomerFileLineTokenizer.java 생성
package com.seohae.batch.batch.fileBatch1.mapper;

import org.springframework.batch.item.file.transform.DefaultFieldSetFactory;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.batch.item.file.transform.FieldSetFactory;
import org.springframework.batch.item.file.transform.LineTokenizer;

import java.util.ArrayList;
import java.util.List;

/**
 * flatFileItemReaderStep4 의 .lineTokenizer(new CustomerFileLineTokenizer()) 설정 추가
 */
public class CustomerFileLineTokenizer implements LineTokenizer {
    private String delimiter = ",";
    private String[] names = new String[] {
            "firstName", "middleInitial", "lastName",
            "addressNumber", "street", "city", "state", "zipCode"};

    private FieldSetFactory fieldSetFactory = new DefaultFieldSetFactory();

    /**
     * 각 레코드를 전달받는 메소드를 통해 구현
     * @param record
     * @return
     */
    @Override
    public FieldSet tokenize(String record) {
        /* 구분자로 레코드 가져오기 */
        String[] fields = record.split(delimiter);

        List<String> parseFields = new ArrayList<>();

        for (int i = 0; i < fields.length; i++) {
            if (i == 4) {
                parseFields.set(i - 1, parseFields.get(i - 1) + " " + fields[i]);
            } else {
                parseFields.add(fields[i]);
            }
        }

        return fieldSetFactory.create(parseFields.toArray(new String[0]), names);
    }
}

 

  • 2) Reader 메서드 추가
...
    /**
     * 4) CustomerFileLineTokenizer.java 설정 추가버전
     * @return
     */
    @Bean
    @StepScope
    public FlatFileItemReader<Customer> flatFileItemReaderStep4() {
        Resource inputFile = new ClassPathResource("input/customerFixedWidth.txt");

        return new FlatFileItemReaderBuilder<Customer>()
                .name("FlatFileItemReaderStep")
                .lineTokenizer(new CustomerFileLineTokenizer())
                .resource(inputFile)
                .targetType(Customer.class)
                .build();
    }
...

 

 

ItemWriter

...
    /**
     * ItemWriter.write 메서드에 전달된 List 내 각 아이템에 대해 출력한다.
     * @return
     */
    @Bean
    public ItemWriter<Customer> flatFileItemWriterStep() {
        return (items) -> items.forEach(System.out::println);
    }
...

 

 

교재 '스프링배치 완벽가이드' 책 예제 따라 Job 생성해보기

 

 

반응형

Designed by JB FACTORY