SpringBatch 사용하기 (1) csv 파일 읽어 DB에 저장

반응형
728x90
반응형

SpringBatch 사용을 위한 스프링 설정

(1) 의존성 추가

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-batch</artifactId>
</dependency>

 

(2) 스프링부트 Application 파일에 어노테이션 추가

@EnableBatchProcessing // 배치 사용을 위한 선언
@SpringBootApplication
public class BatchApplication {
    public static void main(String[] args) {
        SpringApplication.run(BatchApplication.class, args);
    }
}

 

 

SpringBatch 의 Job 등록

import com.backend.batch.day01.dto.TempLibraryDto;
import com.backend.batch.day01.reader.LibraryCsvReader;
import com.backend.batch.day01.repository.TempLibraryJpaRepository;
import com.backend.batch.day01.writer.LibraryCsvWriter;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Slf4j
@Configuration
@RequiredArgsConstructor
public class FileItemReaderJobConfig {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;
    private final TempLibraryJpaRepository tempLibraryJpaRepository;
    private final LibraryCsvReader libraryCsvReader;
    private final LibraryCsvProcessor libraryCsvProcessor;
    private final LibraryCsvWriter libraryCsvWriter;

    private static final int chunkSize = 100;

    @Bean
    public Job csvFileItemReaderJob() {
        return jobBuilderFactory.get("csvFileItemReaderJob")
                .start(csvFileItemReaderStep())
                .build();
    }

    @Bean
    public Step csvFileItemReaderStep() {
        return stepBuilderFactory.get("csvFileItemReaderStep")
                .<TempLibraryDto, TempLibraryDto>chunk(chunkSize)
                .reader(libraryCsvReader.csvFileItemReader())
                .processor(libraryCsvProcessor)
                .writer(libraryCsvWriter)
                .build();
    }
}

위 코드에서 csvFileItemReaderJob라는 이름의 Job을 생성하였고, csvFileItemReaderStep라는 이름의 Step을 생성하였다. 하나의 Job 안에는 여러 Step이 존재할 수 있는데, 위 코드에서는 csvFileItemReaderJob이 csvFileItemReaderStep 을 start 메소드를 통해 호출하고 있다.

 

csvFileItemReaderStep 메소드 안의 reader, writer 메소드를 호출함으로써 Reader와 Writer를 지정할 수 있다. csv 파일을 읽어오는 행위를 Reader에서 실행할 것이고 읽어온 데이터를 DB에 저장하는 행위를 Writer에서 실행할 것이라고 예상할 수 있다.

 

- .<TempLibraryDto, TempLibraryDto>chunk(chunkSize)

 

SpringBatch에서 chunk의 의미는, 데이터 덩어리이다. 배치를 실행하는 중에 여러 row가 처리되는데 이때 chunk size 만큼 처리될 때마다 커밋을 실행한다. chunk size만큼 트랜잭션을 실행한다는 의미이다. Reader 에서 1개씩 읽어온 row가 chunk size만큼 실행 완료되면 해당 row를 한꺼번에 묶어서 writer로 보내지고, writer 에서는 1개의 row가 아닌 받아온 chunk size만큼의 데이터를 적재하는 것이다.


<TempLibraryDto, TempLibraryDto>의 의미는, Reader에서 읽어올 타입이 TempLibraryDto이며, Writer에서 넘겨줄 타입이 TempLibraryDto이라는 의미이다.

 

 

FlatFileItemReader

@RequiredArgsConstructor
@Configuration
public class LibraryCsvReader {
    @Bean
    public FlatFileItemReader<TempLibraryDto> csvFileItemReader() {
        /* file read */
        FlatFileItemReader<TempLibraryDto> flatFileItemReader = new FlatFileItemReader<>();
        flatFileItemReader.setResource(new ClassPathResource("/temp.csv"));

        flatFileItemReader.setLinesToSkip(1); // header line skip
        flatFileItemReader.setEncoding("MS949"); // encoding

        /* read하는 데이터를 내부적으로 LineMapper을 통해 Mapping */
        DefaultLineMapper<TempLibraryDto> defaultLineMapper = new DefaultLineMapper<>();

        /* delimitedLineTokenizer : setNames를 통해 각각의 데이터의 이름 설정 */
        DelimitedLineTokenizer delimitedLineTokenizer = new DelimitedLineTokenizer();
        delimitedLineTokenizer.setNames("libraryNm", "bigLocal", "smallLocal", "libraryType");
        delimitedLineTokenizer.setStrict(false); // csv 파일의 컬럼과 불일치 허용

        defaultLineMapper.setLineTokenizer(delimitedLineTokenizer);

        /* beanWrapperFieldSetMapper : Tokenizer에서 가지고온 데이터들을 VO로 바인드하는 역할 */
        BeanWrapperFieldSetMapper<TempLibraryDto> beanWrapperFieldSetMapper = new BeanWrapperFieldSetMapper<>();
        beanWrapperFieldSetMapper.setTargetType(TempLibraryDto.class);

        defaultLineMapper.setFieldSetMapper(beanWrapperFieldSetMapper);

        /* lineMapper 지정 */
        flatFileItemReader.setLineMapper(defaultLineMapper);

        return flatFileItemReader;
    }
}

csvFileItemReader 메소드에서는 아래 행위를 실행한다.

 

  • - FlatFileItemReader 객체 생성

1) new ClassPathResource(“/temp.csv”) : src/main/resources/temp.csv 경로의 파일 지정
2) flatFileItemReader.setLinesToSkip(1) : 맨 윗줄(header)는 읽지 않고 skip 하겠다는 의미
3) flatFileItemReader.setEncoding(“MS949”) : 인코딩 지정

 

  • - DefaultLineMapper 객체 생성

1) setLineTokenizer 메소드, setFieldSetMapper 메소드 실행을 위한 선언

 

  • - DelimitedLineTokenizer 객체 생성

1) setNames(“libraryNm”, “bigLocal”, “smallLocal”, “libraryType”) : 각각의 데이터 이름 설정
2) setStrict(false) : csv 파일의 컬럼과 불일치 허용 (허용하지 않을때 개수가 다르면 에러 발생)

 

  • - BeanWrapperFieldSetMapper 객체 생성

1) setTargetType(TempLibraryDto.class) : DataType 지정 (Dto클래스)

 

  • - 처음에 생성한 FlatFileItemReader 객체에 defaultLineMapper 적용

1) flatFileItemReader.setLineMapper(defaultLineMapper);

 

 

Writer : implements ItemWriter

@RequiredArgsConstructor
@Configuration
public class LibraryCsvWriter implements ItemWriter<TempLibraryDto> {

    private final TempLibraryJpaRepository tempLibraryJpaRepository;

    @Override
    public void write(List<? extends TempLibraryDto> list) throws Exception {
        List<TempLibrary> tempLibraryList = new ArrayList<>();

        list.forEach(getTempLibrary -> {
            TempLibrary tempLibrary = new TempLibrary();
            tempLibrary.setLibraryNm(getTempLibrary.getLibraryNm());
            tempLibrary.setBigLocal(getTempLibrary.getBigLocal());
            tempLibrary.setSmallLocal(getTempLibrary.getSmallLocal());
            tempLibrary.setLibraryType(getTempLibrary.getLibraryType());

            tempLibraryList.add(tempLibrary);
        });

        tempLibraryJpaRepository.saveAll(tempLibraryList);
    }

}

 

Writer 에서는 implements ItemWriter<TempLibraryDto> 하여, write 메소드를 재구현하였다. 파라미터로 받아온 list는 Reader를 통해 csv 파일에서 읽어온 데이터가 담겨져있는 리스트이고, forEach 문을 통해 리스트에 담겨져있는 데이터들을 add 메소드를 통해 DB에 저정한다.

 

+추가

여기서 중요한 점이 있는데, JPA에 사용되는 Entity는 set 메서드 사용을 권장하지 않는다. set 메서드를 통해 저장된 데이터들이 자동으로 갱신되기 때문이다.

 

 

ItemProcessor

writer을 실행하기 전, writer에 전달할 데이터를 변경하고 싶을때 process 메소드 안에 비즈니스 로직을 적용하면 된다. Reader에서 읽은 데이터가 ItemProcessor의 process의 로직을 거치게된다. null을 리턴하면 해당 데이터는 writer로 전달되지 않는다. (데이터 필터링)

 

@Slf4j
@Configuration
public class LibraryCsvProcessor implements ItemProcessor<TempLibraryDto, TempLibraryDto> {
    @Override
    public TempLibraryDto process(TempLibraryDto tempLibraryDto) throws Exception {
        log.info("processor...");
        return tempLibraryDto;
    }
}

 

 

실행

program arguments를 지정한다.

--job.name=csvFileItemReaderJob version=6

 

반응형

Designed by JB FACTORY