스프링 배치를 사용하다보면 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 (0) | 2025.01.16 |
---|---|
[Spring] MockMvc 사용 시 Page 인터페이스의 직렬화 문제 (0) | 2024.12.31 |
[Spring] 회원탈퇴 시 Kakao OAuth2 연결끊기: REST API로 연결끊기 (OpenFeign) (2) | 2024.10.30 |
[Spring] 직렬화/역직렬화 시 'is' prefix 가 안붙는 이유 (1) | 2024.10.30 |
[Spring] Response DTO 직렬화 문제 (1) | 2024.06.13 |