스프링 배치를 사용하다보면 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 |