데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      [SpringBatch 연재 04] FlatFileItemReader로 단순 파일 읽고, FlatFileItemWriter로 파일에 쓰기

      KIDO 24.10.15
      2,125 9 2
      DEVOTEE 요약
      FlatFileItemReader는 Spring Batch에서 텍스트 파일로부터 데이터를 읽는 기본적인 ItemReader로, 고정 길이, 구분자 기반, 멀티라인 등 다양한 형식의 텍스트 파일을 지원하며, 간단하고 효율적으로 사용할 수 있는 장점이 있습니다. FlatFileItemWriter는 데이터를 텍스트 파일로 출력하는 ItemWriter로, 다양한 설정을 통해 원하는 형식으로 파일을 생성할 수 있으나, 복잡한 구조의 데이터를 출력할 때는 설정이 복잡해질 수 있습니다. FlatFileItemReader/Writer 사용 예시로, CSV 파일을 읽어 탭으로 구분된 새로운 파일로 저장하는 배치 작업을 구성할 수 있습니다.

      FlatFileItemReader 개요

      • FlatFileItemReader는 Spring Batch에서 제공하는 기본적인 ItemReader로, 텍스트 파일로부터 데이터를 읽습니다.

      • 고정 길이, 구분자 기반, 멀티라인 등 다양한 형식의 텍스트 파일을 지원하며, 다음과 같은 장점을 가집니다.

      • 간단하고 효율적인 구현: 설정 및 사용이 간편하며, 대규모 데이터 처리에도 효율적입니다.

      • 다양한 텍스트 파일 형식 지원: 고정 길이, 구분자 기반, 멀티라인 등 다양한 형식의 텍스트 파일을 읽을 수 있습니다.

      • 확장 가능성: 토크나이저, 필터 등을 통해 기능을 확장할 수 있습니다.

      • 사용처: 고정 길이, 구분자 기반, 멀티라인 등 다양한 형식의 텍스트 파일 데이터 처리

      • 장점: 간단하고 효율적인 구현, 다양한 텍스트 파일 형식 지원

      • 단점: 복잡한 데이터 구조 처리에는 적합하지 않음


      FlatFileItemReader 주요 구성 요소

      • Resource: 읽을 텍스트 파일을 지정합니다.

      • LineMapper: 텍스트 파일의 각 라인을 Item으로 변환하는 역할을 합니다.

      • LineTokenizer: 텍스트 파일의 각 라인을 토큰으로 분리하는 역할을 합니다.

      • FieldSetMapper: 토큰을 Item의 속성에 매핑하는 역할을 합니다.

      • SkippableLineMapper: 오류 발생 시 해당 라인을 건너뛸 수 있도록 합니다.

      • LineCallbackHandler: 라인별로 처리를 수행할 수 있도록 합니다.

      • ReadListener: 읽기 시작, 종료, 오류 발생 등의 이벤트를 처리할 수 있도록 합니다.


      샘플코드

      Customer 모델 생성하기

      public class Customer {
      
          private String name;
          private int age;
          private String gender;
      
          // getter/setter methods...
      }
      • 읽어들인 정보를 Customer 객체에 매핑할 수 있도록 객체를 정의한다.

      FlatFileItemReader 빈 생성

      • 다음과 같이 FlatFileItemReader를 생성하고, Customer 객체에 등록하여 반환한다.

          @Bean
          public FlatFileItemReader<Customer> flatFileItemReader() {
      
              return new FlatFileItemReaderBuilder<Customer>()
                      .name("FlatFileItemReader")
                      .resource(new ClassPathResource("./customer.csv"))
                      .encoding(ENCODING)
                      .delimited().delimiter(",")
                      .names("name", "age", "gender")
                      .targetType(Customer.class)
                      .build();
          }
      • rexource

        • ClassPathResource("./customers.csv")

        • 클래스 패스 내부에 존재하는 csv 파일을 읽어들인다.

      • encoding

        • 파일 데이터의 인코딩을 추가한다.

      • delimited

        • 구분자로 설정되어 있음을 의미한다.

      • delimiter

        • 구분자를 무엇으로할지 지정한다.

      • names

        • 구분자로 구분된 데이터의 이름을 지정한다.

      • targetType

        • 구분된 데이터를 어느 모델에 넣을지 클래스 타입을 지정한다.


      수행되는 샘플코드 전체 소스

      • 이제 CSV 플랫파일을 이용하여 단순히 다시 새로운 플랫파일로 저장하고 탭으로 구분된 배치를 작성해보자.

      package com.schooldevops.springbatch.batchsample.jobs.flatfilereader;
      
      
      import com.schooldevops.springbatch.batchsample.jobs.models.Customer;
      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.launch.support.RunIdIncrementer;
      import org.springframework.batch.core.repository.JobRepository;
      import org.springframework.batch.core.step.builder.StepBuilder;
      import org.springframework.batch.item.file.FlatFileItemReader;
      import org.springframework.batch.item.file.FlatFileItemWriter;
      import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
      import org.springframework.batch.item.file.builder.FlatFileItemWriterBuilder;
      import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
      import org.springframework.batch.item.file.mapping.DefaultLineMapper;
      import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
      import org.springframework.batch.item.file.transform.LineTokenizer;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.core.io.ClassPathResource;
      import org.springframework.core.io.FileSystemResource;
      import org.springframework.transaction.PlatformTransactionManager;
      
      @Slf4j
      @Configuration
      public class FlatFileItemJobConfig {
      
          /**
           * CHUNK 크기를 지정한다.
           */
          public static final int CHUNK_SIZE = 100;
          public static final String ENCODING = "UTF-8";
          public static final String FLAT_FILE_CHUNK_JOB = "FLAT_FILE_CHUNK_JOB";
      
          @Bean
          public FlatFileItemReader<Customer> flatFileItemReader() {
      
              return new FlatFileItemReaderBuilder<Customer>()
                      .name("FlatFileItemReader")
                      .resource(new ClassPathResource("./customer.csv"))
                      .encoding(ENCODING)
                      .delimited().delimiter(",")
                      .names("name", "age", "gender")
                      .targetType(Customer.class)
                      .build();
          }
      
          @Bean
          public FlatFileItemWriter<Customer> flatFileItemWriter() {
              return new FlatFileItemWriterBuilder<Customer>()
                      .name("flatFileItemWriter")
                      .resource(new FileSystemResource("./output/customer_new.csv"))
                      .encoding(ENCODING)
                      .delimited().delimiter("\t")
                      .names("Name", "Age", "Gender")
                      .build();
          }
      
      
          @Bean
          public Step flatFileStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
              log.info("------------------ Init flatFileStep -----------------");
      
              return new StepBuilder("flatFileStep", jobRepository)
                      .<Customer, Customer>chunk(CHUNK_SIZE, transactionManager)
                      .reader(flatFileItemReader())
                      .writer(flatFileItemWriter())
                      .build();
          }
      
          @Bean
          public Job flatFileJob(Step flatFileStep, JobRepository jobRepository) {
              log.info("------------------ Init flatFileJob -----------------");
              return new JobBuilder(FLAT_FILE_CHUNK_JOB, jobRepository)
                      .incrementer(new RunIdIncrementer())
                      .start(flatFileStep)
                      .build();
          }
      }


      WrapUp

      • 지금까지 단순한 FlatFile을 읽는다 이는 csv 형식의 파일이며, 이 파일을 읽어 탭으로 구분된 파일을 새로 작성하는 배치를 작성했다.

      • FlatFileItemReader로 파일을 읽었다. 이때 파일의 특성에 따라 FlatFileItemReaderBuilder를 이용하여 쉽게 구성했다.




      FlatFileItemWriter 개요

      • FlatFileItemWriter는 Spring Batch에서 제공하는 ItemWriter 인터페이스를 구현하는 클래스이다.

      • 데이터를 텍스트 파일로 출력하는 데 사용된다.


      구성 요소

      • Resource: 출력 파일 경로를 지정한다.

      • LineAggregator: Item을 문자열로 변환하는 역할을 한다.

      • HeaderCallback: 출력 파일 헤더를 작성하는 역할을 한다.

      • FooterCallback: 출력 파일 푸터를 작성하는 역할을 한다.

      • Delimiter: 항목 사이 구분자를 지정한다.

      • AppendMode: 기존 파일에 추가할지 여부를 지정한다.


      장점

      • 간편성: 텍스트 파일로 데이터를 출력하는 간편한 방법을 제공한다.

      • 유연성: 다양한 설정을 통해 원하는 형식으로 출력 파일을 만들 수 있다.

      • 성능: 대량의 데이터를 빠르게 출력할 수 있다.


      단점

      • 형식 제약: 텍스트 파일 형식만 지원한다.

      • 복잡한 구조: 복잡한 구조의 데이터를 출력할 경우 설정이 복잡해질 수 있다.

      • 오류 가능성: 설정 오류 시 출력 파일이 손상될 수 있다.


      샘플

      FlatFileItemWriter 작성하기

          @Bean
          public FlatFileItemWriter<Customer> flatFileItemWriter() {
      
              return new FlatFileItemWriterBuilder<Customer>()
                      .name("flatFileItemWriter")
                      .resource(new FileSystemResource("./output/customer_new.csv"))
                      .encoding(ENCODING)
                      .delimited().delimiter("\t")
                      .names("Name", "Age", "Gender")
                      .append(false)
                      .lineAggregator(new CustomerLineAggregator())
                      .headerCallback(new CustomerHeader())
                      .footerCallback(new CustomerFooter(aggregateInfos))
                      .build();
          }
          
      • FlatFileItemWriterBuilder: 파일로 결과를 쓰기하기 위한 빌더 객체

      • name: FlatFileItemWriter의 이름을 지정한다.

      • resource: 저장할 최종 파일 이름이다.

      • encoding: 저장할 파일의 인코딩 타입

      • delimited().delimiter: 각 필드를 구분할 딜리미터를 지정한다.

      • append: true인경우 기존 파일에 첨부한다. false인경우 새로운 파일을 만든다.

      • lineAggregator: 라인 구분자를 지정한다.

      • headerCallback: 출력 파일의 헤더를 지정할 수 있도록 한다.

      • footerCallback: 출력 파일의 푸터를 지정할 수 있도록 한다.

      CustomerLineAggregator 작성하기

      package com.schooldevops.springbatch.batchsample.jobs.flatfilereader;
      
      import com.schooldevops.springbatch.batchsample.jobs.models.Customer;
      import org.springframework.batch.item.file.transform.LineAggregator;
      
      public class CustomerLineAggregator implements LineAggregator<Customer> {
          @Override
          public String aggregate(Customer item) {
              return item.getName() + "," + item.getAge();
          }
      }
      • LineAggregator은 FlatFile에 저장할 아이템들을 스트링으로 변환하는 방법을 지정하는 것이다.

      • 위와 같이 aggregate를 구현하여 아이템을 스트링 문자로 변경하는 것을 확인할 수 있다.

      CustomerHeader 작성하기

      package com.schooldevops.springbatch.batchsample.jobs.flatfilereader;
      
      import org.springframework.batch.item.file.FlatFileHeaderCallback;
      
      import java.io.IOException;
      import java.io.Writer;
      
      public class CustomerHeader implements FlatFileHeaderCallback {
          @Override
          public void writeHeader(Writer writer) throws IOException {
              writer.write("ID,AGE");
          }
      }
      • FlatFileHeaderCallback은 writeHeader를 구현하고, 출력될 파일의 헤더를 달아주는 역할을 한다.

      CustomerFooter 작성하기

      package com.schooldevops.springbatch.batchsample.jobs.flatfilereader;
      
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.batch.item.file.FlatFileFooterCallback;
      
      import java.io.IOException;
      import java.io.Writer;
      import java.util.concurrent.ConcurrentHashMap;
      
      @Slf4j
      public class CustomerFooter implements FlatFileFooterCallback {
          ConcurrentHashMap<String, Integer> aggregateCustomers;
      
          public CustomerFooter(ConcurrentHashMap<String, Integer> aggregateCustomers) {
              this.aggregateCustomers = aggregateCustomers;
          }
      
          @Override
          public void writeFooter(Writer writer) throws IOException {
              writer.write("총 고객 수: " + aggregateCustomers.get("TOTAL_CUSTOMERS"));
              writer.write(System.lineSeparator());
              writer.write("총 나이: " + aggregateCustomers.get("TOTAL_AGES"));
          }
      }
      • FlatFileFooterCallback 은 푸터를 작성할때 사용한다.

      • 결과를 집계하여 총 고객수와 총 나이를 출력하고 있다.

      • 이때 전달된 HashMap을 전달하여, 결과를 출력하고 있음을 확인하자.

      AggregateCustomerProcessor 작성하기

      package com.schooldevops.springbatch.batchsample.jobs.flatfilereader;
      
      import com.schooldevops.springbatch.batchsample.jobs.models.Customer;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.batch.item.ItemProcessor;
      
      import java.util.Map;
      import java.util.concurrent.ConcurrentHashMap;
      
      @Slf4j
      public class AggregateCustomerProcessor implements ItemProcessor<Customer, Customer> {
      
          ConcurrentHashMap<String, Integer> aggregateCustomers;
      
          public AggregateCustomerProcessor(ConcurrentHashMap<String, Integer> aggregateCustomers) {
              this.aggregateCustomers = aggregateCustomers;
          }
      
          @Override
          public Customer process(Customer item) throws Exception {
              aggregateCustomers.putIfAbsent("TOTAL_CUSTOMERS", 0);
              aggregateCustomers.putIfAbsent("TOTAL_AGES", 0);
      
              aggregateCustomers.put("TOTAL_CUSTOMERS", aggregateCustomers.get("TOTAL_CUSTOMERS") + 1);
              aggregateCustomers.put("TOTAL_AGES", aggregateCustomers.get("TOTAL_AGES") + item.getAge());
              return item;
          }
      }
      • ItemProcessor은 process 메소드를 구현하였으며, 각 아이템을 하나씩 읽고 아이템의 내용을 집계하고 있다.


      전체 소스

      package com.schooldevops.springbatch.batchsample.jobs.flatfilereader;
      
      
      import com.schooldevops.springbatch.batchsample.jobs.models.Customer;
      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.launch.support.RunIdIncrementer;
      import org.springframework.batch.core.repository.JobRepository;
      import org.springframework.batch.core.step.builder.StepBuilder;
      import org.springframework.batch.item.ItemProcessor;
      import org.springframework.batch.item.file.FlatFileItemReader;
      import org.springframework.batch.item.file.FlatFileItemWriter;
      import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
      import org.springframework.batch.item.file.builder.FlatFileItemWriterBuilder;
      import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
      import org.springframework.batch.item.file.mapping.DefaultLineMapper;
      import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
      import org.springframework.batch.item.file.transform.LineTokenizer;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.core.io.ClassPathResource;
      import org.springframework.core.io.FileSystemResource;
      import org.springframework.transaction.PlatformTransactionManager;
      
      import java.util.Map;
      import java.util.concurrent.ConcurrentHashMap;
      
      @Slf4j
      @Configuration
      public class FlatFileItemJobConfig {
      
          /**
           * CHUNK 크기를 지정한다.
           */
          public static final int CHUNK_SIZE = 100;
          public static final String ENCODING = "UTF-8";
          public static final String FLAT_FILE_WRITER_CHUNK_JOB = "FLAT_FILE_WRITER_CHUNK_JOB";
      
          private ConcurrentHashMap<String, Integer> aggregateInfos = new ConcurrentHashMap<>();
      
          private final ItemProcessor<Customer, Customer> itemProcessor = new AggregateCustomerProcessor(aggregateInfos);
          @Bean
          public FlatFileItemReader<Customer> flatFileItemReader() {
      
              return new FlatFileItemReaderBuilder<Customer>()
                      .name("FlatFileItemReader")
                      .resource(new ClassPathResource("./customer.csv"))
                      .encoding(ENCODING)
                      .delimited().delimiter(",")
                      .names("name", "age", "gender")
                      .targetType(Customer.class)
                      .build();
          }
      
          @Bean
          public FlatFileItemWriter<Customer> flatFileItemWriter() {
      
              return new FlatFileItemWriterBuilder<Customer>()
                      .name("flatFileItemWriter")
                      .resource(new FileSystemResource("./output/customer_new.csv"))
                      .encoding(ENCODING)
                      .delimited().delimiter("\t")
                      .names("Name", "Age", "Gender")
                      .append(false)
                      .lineAggregator(new CustomerLineAggregator())
                      .headerCallback(new CustomerHeader())
                      .footerCallback(new CustomerFooter(aggregateInfos))
                      .build();
          }
      
      
          @Bean
          public Step flatFileStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
              log.info("------------------ Init flatFileStep -----------------");
      
              return new StepBuilder("flatFileStep", jobRepository)
                      .<Customer, Customer>chunk(CHUNK_SIZE, transactionManager)
                      .reader(flatFileItemReader())
                      .processor(itemProcessor)
                      .writer(flatFileItemWriter())
                      .build();
          }
      
          @Bean
          public Job flatFileJob(Step flatFileStep, JobRepository jobRepository) {
              log.info("------------------ Init flatFileJob -----------------");
              return new JobBuilder(FLAT_FILE_WRITER_CHUNK_JOB, jobRepository)
                      .incrementer(new RunIdIncrementer())
                      .start(flatFileStep)
                      .build();
          }
      }


      WrapUp

      • 지금까지 FlatFileItemWriter 를 작성하였다.

      • FlatFileItemWriter은 파일을 이용하여 결과를 작성하고, 이 결과를 csv, tsv등과 같은 형식화된 파일등으로 보낼때 매우 유용하다.

      • 헤더, 푸터등을 적용하여 파일형식으로 포매팅 할 수 있음을 확인하였다.

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      KIDO 님의 최신 블로그

      더보기