[Spring Batch] ItemWriter 가 List<T> 를 전달받으려면? (Spring Batch 5)

2024. 7. 1. 20:10·Spring

스프링 배치를 사용하다보면 ItemWriter 에 List 같은 Collection 을 전달할 때가 있습니다.

저는 테스트를 위해 숙소 예약건 생성을 배치로 처리하는 과정에서 필요했었는데요, 이 과정에서 만난 문제와 해결 방법을 기록하기 위해 글을 작성했습니다.

마주친 문제 : ItemWriter 는 이미 Chunk 으로 받는다

설명은 Spring Batch 5 에서 구현체 JdbcBatchItemWriter 기준으로 설명합니다.

해당 구현체의 `writer()` 메서드는 아래와 같습니다.

구현체 JdbcBatchItemWriter 의 write() 메서드 살펴보기

public void write(final Chunk<? extends T> chunk) throws Exception {
        if (!chunk.isEmpty()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Executing batch with " + chunk.size() + " items.");
            }

            int[] updateCounts;
            int value;
            if (!this.usingNamedParameters) {
                updateCounts = (int[])this.namedParameterJdbcTemplate.getJdbcOperations().execute(this.sql, (ps) -> {
                    Chunk.ChunkIterator var3 = chunk.iterator();

                    while(var3.hasNext()) {
                        T item = var3.next();
                        this.itemPreparedStatementSetter.setValues(item, ps);
                        ps.addBatch();
                    }

                    return ps.executeBatch();
                });
            } else if (chunk.getItems().get(0) instanceof Map && this.itemSqlParameterSourceProvider == null) {
                updateCounts = this.namedParameterJdbcTemplate.batchUpdate(this.sql, (Map[])chunk.getItems().toArray(new Map[chunk.size()]));
            } else {
                SqlParameterSource[] batchArgs = new SqlParameterSource[chunk.size()];
                value = 0;

                Object item;
                for(Chunk.ChunkIterator var5 = chunk.iterator(); var5.hasNext(); batchArgs[value++] = this.itemSqlParameterSourceProvider.createSqlParameterSource(item)) {
                    item = var5.next();
                }

                updateCounts = this.namedParameterJdbcTemplate.batchUpdate(this.sql, batchArgs);
            }

            if (this.assertUpdates) {
                for(int i = 0; i < updateCounts.length; ++i) {
                    value = updateCounts[i];
                    if (value == 0) {
                        throw new EmptyResultDataAccessException("Item " + i + " of " + updateCounts.length + " did not update any rows: [" + chunk.getItems().get(i) + "]", 1);
                    }
                }
            }

            this.processUpdateCounts(updateCounts);
        }

    }

여기서 핵심은 이 부분입니다.

if (!this.usingNamedParameters) {
    updateCounts = (int[])this.namedParameterJdbcTemplate.getJdbcOperations().execute(this.sql, (ps) -> {
        Chunk.ChunkIterator var3 = chunk.iterator();

        while(var3.hasNext()) {
            T item = var3.next();
            this.itemPreparedStatementSetter.setValues(item, ps);
            ps.addBatch();
        }

        return ps.executeBatch();
});

write() 메서드 가 전달받은 Chunk(? extends T) chunk는 ItemReader 또는 ItemProcessor 가 전달한 아이템의 청크입니다.

여기서 청크는 List를 감싼 Wrapper 클래스 입니다.

이미 write() 메서드는 매개변수를 List<Item> 타입으로 받기 때문에 바깥에서 List<Item> 을 전달하면 매개변수에는 List<List<Item>> 을 받는 모양이 됩니다.
하지만 구현체는 List<Item> 에서 Item 객체를 하나씩 꺼내 처리하고 있고, 이 때문에 바깥에서 List<Item> 전달하면 단일 객체인 Item 의 타입이 아닌 List<Item> 타입이기 때문에 에러를 만나게 되는 것이었습니다.

따라서 문제를 해결하기 위해선 List<T> 로 전달받은 객체는 ItemWriter 에게 Chunk<T> 로 전달되야 합니다.
for loop 로 하나씩 Chunk로 감싸서 구헌체인 JdbcBatchItemWriter 에게 전달하면 됩니다.

해결 방법: 하나씩 꺼내서 JdbcBatchItemWriter 에게 Chunk로 전달하기

@Bean  
@StepScope  
public ItemWriter<List<YourItem>> saveByJdbc(JdbcBatchItemWriter<YourItem> jdbcBatchItemWriter) {  
    return yourItemChunks -> {  
        for (List<YourItem> yourItem : yourItemChunks) {  
            jdbcBatchItemWriter.write(new Chunk<>(yourItem));  
        }  
    };  
}  

@Bean  
@StepScope  
public JdbcBatchItemWriter<YourItem> jdbcWriter() {  
    return new JdbcBatchItemWriterBuilder<YourItem>()  
            .dataSource(dataSource)  
            ...
            .build();  
}

그럼 Step 의 코드는 이렇게 되겠죠?

Step 코드 전체 예시

@Bean
public Step step(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
    return new StepBuilder("ExampleStep", jobRepository)
            .<OriginalItem, List<YourItem>>chunk(1000, transactionManager)
            .reader(readItems())
            .processor(yourProcessor())
            .writer(saveByJdbc(jdbcWriter()))
            .build();
}

도움이 되셨길 바라며 포스팅을 마치겠습니다.
읽어주셔서 감사합니다.

참고

향로님 블로그: Spring Batch ItemWriter에 List 전달하기 (https://jojoldu.tistory.com/140)

'Spring' 카테고리의 다른 글

Spring Event Deep Dive  (2) 2025.01.16
[Spring] MockMvc 사용 시 Page 인터페이스의 직렬화 문제  (0) 2024.12.31
[Spring] 회원탈퇴 시 Kakao OAuth2 연결끊기: REST API로 연결끊기 (OpenFeign)  (2) 2024.10.30
[Spring] 직렬화/역직렬화 시 'is' prefix 가 안붙는 이유  (2) 2024.10.30
[Spring] Response DTO 직렬화 문제  (1) 2024.06.13
'Spring' 카테고리의 다른 글
  • [Spring] MockMvc 사용 시 Page 인터페이스의 직렬화 문제
  • [Spring] 회원탈퇴 시 Kakao OAuth2 연결끊기: REST API로 연결끊기 (OpenFeign)
  • [Spring] 직렬화/역직렬화 시 'is' prefix 가 안붙는 이유
  • [Spring] Response DTO 직렬화 문제
옐리yelly
옐리yelly
  • 옐리yelly
    개발 갤러리
    옐리yelly
  • 전체
    오늘
    어제
    • 모든 글 보기 (84)
      • Project (22)
      • Java (4)
      • Spring (8)
      • Kubernetes (6)
      • Docker (2)
      • JPA (2)
      • Querydsl (2)
      • MySQL (9)
      • ElasticSearch (7)
      • DevOps (4)
      • Message Broker (3)
      • Git & GitHub (2)
      • Svelte (1)
      • Python (8)
        • Python Distilled (4)
        • Anaconda (1)
        • Django (0)
        • pandas (3)
      • Algorithm (1)
      • Computer Science (0)
      • 내 생각 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    mybatis
    Project
    pymysql
    blue-green 배포
    devops
    JPA
    pandas
    커넥션 풀
    argocd
    프로젝트
    nks
    MySQL
    RabbitMQ
    elasticsearch
    Python
    비사이드
    docker
    ncloud
    svelte
    querydsl
    리팩토링
    Message Broker
    데드락
    포텐데이
    OOP
    k8s
    성능 테스트
    Spring
    예약 시스템
    gitops
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
옐리yelly
[Spring Batch] ItemWriter 가 List<T> 를 전달받으려면? (Spring Batch 5)
상단으로

티스토리툴바