You will be fine

<Spring Batch> 예제 프로그램 따라하기

by BFine
반응형

 

스케줄러 형태로 되어있는 배치는 많이 봤는데 Spring Batch를 사용한 배치 프로그램은 본적이 없는 것 같다.

궁금하기도 하고 공부해뒀다가 나중에 시간될때 적용해봐야겠다!!!

 

예제 : spring.io/guides/gs/batch-processing/

 

가. 개요  

 a. 의존성

  -  Spring boot 4.3 , Spring Batch Starter로 간단하게 구현할 수 있다. 

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-batch'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'org.hsqldb:hsqldb'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.batch:spring-batch-test'
}

  -  여기서는 데이터를 저장하기 위해 내장형 DB HSQL을 사용한다.

 

 b. 로직

  -  csv 파일에 있는 이름을 읽어서 대문자로 변경하고 DB에 저장한다. 그리고 DB에서 값을 읽어 보여준다.  

 

 c. 파일 

  -  sql 파일과 csv 파일을 사용하는데 resources 폴더에 추가하자

  -  sample-date.csv 

Jill,Doe
Joe,Doe
Justin,Doe
Jane,Doe
John,Doe

 -  schema-all.sql

DROP TABLE people IF EXISTS;

CREATE TABLE people  (
    person_id BIGINT IDENTITY NOT NULL PRIMARY KEY,
    first_name VARCHAR(20),
    last_name VARCHAR(20)
);

 

나. 클래스

 a. Person

  -  Parsing error at line: 1 in resource=[class path resource [sample-data.csv]], input=[Jill,Doe]

       => FlatFileItemReaderlineMapper에서 오류가 발생한다.   

  -  위의 오류는 디폴트 생성자가 없기 때문에 발생한다. 소스코드 내부를 찾아 들어가보면 type.newInstance()

       객체를 만드는 것을 볼수있는데 얘는 디폴트생성자로 객체를 만들기 때문에 오류가 발생한다.  

  -  이부분을 방지하기 위해 꼭! 디폴트 생성자를 만들자!! (@NoArgsConstructor

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Person {
    private String lastName;
    private String firstName;
}

 

 b. PersonItemProcessor

  -  ItemProceesor를 상속받아 핵심로직을 처리한다. 같은이름의 인터페이스가 있으니 주의하자!

@Slf4j
public class PersonItemProcessor implements ItemProcessor<Person,Person> {

    @Override
    public Person process(Person item){
        final String firstName = item.getFirstName().toUpperCase();
        final String lastName = item.getLastName().toUpperCase();

        final Person transformedPerson = new Person(firstName, lastName);

        log.info("Converting ({}) into ({})",item,transformedPerson);

        return transformedPerson;
    }
}

 

 c. BatchConfiguration

  -  배치의 동작순서가 딱 보이는 Bean 설정 클래스이다.

@Configuration
@EnableBatchProcessing
@RequiredArgsConstructor
public class BatchConfiguration {

    public final JobBuilderFactory jobBuilderFactory;
    public final StepBuilderFactory stepBuilderFactory;

     					. . . 
 }

  -  @EnableBatchProcessing 어노테이션을 통해 dataSource를 가져오고 batch 처리와 관련 Bean들을

        자동으로 가져오며 아래에 선언하는 여러 @Bean들을 연결시켜준다.

  -  내부에 설정할 Bean들을 확인해보자

 @Bean
 public FlatFileItemReader<Person> reader() {
   return new FlatFileItemReaderBuilder<Person>()
        .name("personItemReader")
        .resource(new ClassPathResource("sample-data.csv"))
        .delimited()
        .names(new String[]{"firstName", "lastName"})
        .fieldSetMapper(new BeanWrapperFieldSetMapper<Person>() {{
          setTargetType(Person.class);
        }})
        .build();
 }

 @Bean
 public PersonItemProcessor processor() {
    return new PersonItemProcessor();
 }

 @Bean
 public JdbcBatchItemWriter<Person> writer(DataSource dataSource) {
     return new JdbcBatchItemWriterBuilder<Person>()
       .itemSqlParameterSourceProvider
       (new BeanPropertyItemSqlParameterSourceProvider<>())
       .sql("INSERT INTO people (first_name, last_name) 
             VALUES (:firstName, :lastName)")
       .dataSource(dataSource)
       .build();
 }

  -  되게 직관적으로 메서드 이름만 봐도 어떻게 쓰일지가 한눈에 보인다.

  -  먼저 reader()IteamReader를 생성하고 csv 파일로부터 데이터를 읽어서 Person 객체 형태로 파싱한다.

        => reader의 역할은 어떤 데이터를 읽어서 자바프로그램으로 처리할 수 있도록 객체로 만드는 것이다.

  -  processor()는 핵심 로직을 처리한다.

  -  writer()는 처리된 데이터를 말그대로 쓰는 역할을 한다. 결과를 반영하는 역할이다.

 @Bean
 public Step step1(JdbcBatchItemWriter<Person> writer) {
    return stepBuilderFactory.get("step1")
       .<Person, Person> chunk(10)
       .reader(reader())
       .processor(processor())
       .writer(writer)
       .build();
}

 -  아래의 step1 메서드를 보면 위의 Bean들을 하나의 Step으로 묶어주는게 보인다. 

 -  여기서 chunk의 제네릭은 Inuput, Output 을 의미한다. 여기서는 결과도 Person이므로 동일하다.

 -  즉 Step은 read -> process -> writer 형태의 단위를 의미하고 여러개가 될 수 있다.

 @Bean
 public Job importUserJob(JobCompletionNotificationListener listener, Step step1) {
      
      return jobBuilderFactory.get("importUserJob")
        .incrementer(new RunIdIncrementer())
        .listener(listener)
        .flow(step1)
        .end()
        .build();
 }

 -  여기서 당황스러울수 있는 부분은 JobCompletionNotificationListener 이 클래스이다.

     => 마치 Library 클래스 같지만 커스텀 클래스이다. 레퍼런스에는 미국식이라 그런지 미리 생성한걸 보여주지

           않고 아래에 만드는걸 보여줘서 한참을 찾았다;; 어순이 목적어가 뒤에 나오는 거랑 비슷한걸까..

 -  Job을 생성하는 단계로 listenerstep을 등록한다. Step과 마찬가지로 여러개 등록이 가능하다.

 

 d. JobCompletionNotificationListener

  -  위의 Job을 생성할때 등록하는 listener 클래스를 만들자

  -  여기서는 afterJob만 사용하지만 JobExecutionListenerSupport 살펴보면 beforeJob도 가지고 있다.

        => 마치 AOP처럼 전처리와 후처리를 한다.

  -  BatchStatus를 통해 Job의 처리 상태를 확인할 수 있다.

  -  afterJob은 결과값을 확인하고 알림(슬랙)을 보내는 용도로 사용하면 좋을 것 같다.

@Slf4j
@Component
@RequiredArgsConstructor
public class JobCompletionNotificationListener extends JobExecutionListenerSupport {

   private final JdbcTemplate jdbcTemplate;

   @Override
   public void afterJob(JobExecution jobExecution) {

        if(jobExecution.getStatus() == BatchStatus.COMPLETED) {
             log.info("### JOB 끝남 ####");
                
             jdbcTemplate.query("SELECT first_name, last_name FROM people",
                (rs, row) -> new Person(
                  rs.getString(1),
                  rs.getString(2))
               ).forEach(person -> log.info(" <" + person + "> 디비에 등록됨")); 
         }
   }
}

 

다. 테스트 

 a. 실행

  -  결과 로그를 확인해보면 ...

 b. 구조 

  -  어떻게 흘러가는지 그려보았다.

-  하나의 Job이 처리되는 모습을 그려보았다. 여기서 Job은 또 여러개가 될 수 있다.

 

예전에 Spring Batch 볼때는 꽤 어려웠던걸로 기억했었는데 예제 프로그램을 따라서 해보니까 쉬운 예제라

그럴 수 있지만되게 간단하면서도 직관적으로 역할이 분배 되어있고 구성도 딱딱 맞게 고정 되어있어서 놀라웠다.

확실히 스케쥴러만 사용하는것보다 구성만으로도 사용할 가치가 충분히 있어보인다.

레퍼런스 보면서 조금씩 세부적으로 정리해봐야겠다.

 

2021.09.19 - [공부(2021)/Spring Batch] - 0. 스프링 배치란?

2021.09.21 - [공부(2021)/Spring Batch] - 1. JobParameters, ExecutionContext

2021.09.22 - [공부(2021)/Spring Batch] - 2. JobRepository, Config Customizing

2021.09.27 - [공부(2021)/Spring Batch] - 3. 배치 테이블 Embedded DB(H2)로 따로 관리하기

2021.10.02 - [공부(2021)/Spring Batch] - 4. Chunk 기반 배치, ItemReader

2021.10.03 - [공부(2021)/Spring Batch] - 5. ItemProcessor, ItemWriter

반응형

블로그의 정보

57개월 BackEnd

BFine

활동하기