본문 바로가기
개발/Spring

[Spring] 스프링 배치(Spring Batch) 가이드 따라가기 (7)

by devhooney 2022. 12. 23.
728x90

스프링 배치 가이드

 

- 지난 포스팅에서 튜토리얼 보고 따라해봤는데, 이번에는 개념부터 천천히 공부해보자.

- 책을 보고 공부하려 했으나, 스프링과 부트의 배치 사용 문법이 많이 달라 이동욱님의 블로그를 보고 공부했다.

https://devhooney.tistory.com/140

 

[Spring] 스프링 배치(Spring Batch) 가이드 따라가기 (5)

스프링 배치 가이드 - 지난 포스팅에서 튜토리얼 보고 따라해봤는데, 이번에는 개념부터 천천히 공부해보자. - 책을 보고 공부하려 했으나, 스프링과 부트의 배치 사용 문법이 많이 달라 이동욱

devhooney.tistory.com

 

 

728x90

 

 

1. ItemProcessor

- ItemProcessor는 Reader에서 넘겨준 데이터를 개별 건으로 가공하고 처리한다.

- ItemProcessor를 사용하는 방법은 2가지이다.

  • 변환
    • Reader에서 읽은 데이터를 원하는 타입으로 변환해서 Writer에 넘겨줄 수 있다.
  • 필터
    • Reader에서 넘겨준 데이터를 Writer로 넘겨줄 지 결정할 수 있다.
    • null을 반환하면 Writer에 전달되지 않는다.

 

 

2. 사용 방법

- ItemProcessor는 인터페이스이고, 두 개의 제네릭 타입이 필요하다.

public interface ItemProcessor<I, O> {

   /**
    * Process the provided item, returning a potentially modified or new item for continued
    * processing.  If the returned result is {@code null}, it is assumed that processing of the item
    * should not continue.
    * 
    * A {@code null} item will never reach this method because the only possible sources are:
    * <ul>
    *     <li>an {@link ItemReader} (which indicates no more items)</li>
    *     <li>a previous {@link ItemProcessor} in a composite processor (which indicates a filtered item)</li>
    * </ul>
    * 
    * @param item to be processed, never {@code null}.
    * @return potentially modified or new item for continued processing, {@code null} if processing of the
    *  provided item should not continue.
    * @throws Exception thrown if exception occurs during processing.
    */
   @Nullable
   O process(@NonNull I item) throws Exception;
}

 

- I: ItemReader에서 받을 데이터 타입

- O: ItemWriter에 보낼 데이터 타입

 

- Reader에서 읽은 데이터가 ItemProcessor의 process를 통과해서 Writer에 전달된다.

 

 

3. 변환

- Reader에서 읽은 타입을 변환하여 Writer에 전달

- 예시 코드(Teacher라는 도메인 클래스를 읽어와 Name 필드 (String 타입)을 Wrtier에 넘겨주도록 구성한 코드)

@Slf4j
@RequiredArgsConstructor
@Configuration
public class ProcessorConvertJobConfiguration {
    public static final String JOB_NAME = "ProcessorConvertBatch";
    public static final String BEAN_PREFIX = JOB_NAME + "_";

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;
    private final EntityManagerFactory emf;

    @Value("${chunkSize:1000}")
    private int chunkSize;

    @Bean(JOB_NAME)
    public Job job() {
        return jobBuilderFactory.get(JOB_NAME)
                .preventRestart()
                .start(step())
                .build();
    }

    @Bean(BEAN_PREFIX + "step")
    @JobScope
    public Step step() {
        return stepBuilderFactory.get(BEAN_PREFIX + "step")
                .<Teacher, String>chunk(chunkSize)
                .reader(reader())
                .processor(processor())
                .writer(writer())
                .build();
    }

    @Bean
    public JpaPagingItemReader<Teacher> reader() {
        return new JpaPagingItemReaderBuilder<Teacher>()
                .name(BEAN_PREFIX+"reader")
                .entityManagerFactory(emf)
                .pageSize(chunkSize)
                .queryString("SELECT t FROM Teacher t")
                .build();
    }

    @Bean
    public ItemProcessor<Teacher, String> processor() {
        return Teacher::getName;
    }

    private ItemWriter<String> writer() {
        return items -> {
            for (String item : items) {
                log.info("Teacher Name={}", item);
            }
        };
    }
}

 

- 실행하면

- 잘 진행됐다.

 

 

4. 필터

- 필터는

 

Writer에 값을 넘길지 말지를 Processor에서 판단하는 것

- 예제 코드 (Teacher의 Id가 짝수인 경우 필터링하는 예제)

 @Value("${chunkSize:1000}")
    private int chunkSize;

    @Bean(JOB_NAME)
    public Job job() {
        return jobBuilderFactory.get(JOB_NAME)
                .preventRestart()
                .start(step())
                .build();
    }

    @Bean(BEAN_PREFIX + "step")
    @JobScope
    public Step step() {
        return stepBuilderFactory.get(BEAN_PREFIX + "step")
                .<Teacher, Teacher>chunk(chunkSize)
                .reader(reader())
                .processor(processor())
                .writer(writer())
                .build();
    }

    @Bean
    public JpaPagingItemReader<Teacher> reader() {
        return new JpaPagingItemReaderBuilder<Teacher>()
                .name(BEAN_PREFIX+"reader")
                .entityManagerFactory(emf)
                .pageSize(chunkSize)
                .queryString("SELECT t FROM Teacher t")
                .build();
    }

    @Bean
    public ItemProcessor<Teacher, Teacher> processor() {
        return teacher -> {

            boolean isIgnoreTarget = teacher.getId() % 2 == 0L;
            if(isIgnoreTarget){
                log.info(">>>>>>>>> Teacher name={}, isIgnoreTarget={}", teacher.getName(), isIgnoreTarget);
                return null;
            }

            return teacher;
        };
    }

    private ItemWriter<Teacher> writer() {
        return items -> {
            for (Teacher item : items) {
                log.info("Teacher Name={}", item.getName());
            }
        };
    }
}

 

- 실행하면

 

- 짝수를 건너서 1, 3만 출력됐다.

 

 

- jojoldu 이동욱님의 블로그를 참고해서 한번 씩 따라해봤는데, 정말 기초수준의 맛만 본거 같다.

- 회사에서 프리랜서 분이 배치를 이용하여 크롤링 작업을 하는 코드를 작성했는데, 보고 공부를 더 해봐야겠다.

 

 

- 참고

https://jojoldu.tistory.com/347?category=902551 

 

 

 

728x90