JpaPagingItemReader를 사용하여 reader 에서 읽은 데이터를, writer 에서 해당 데이터 state 값을 업데이트하는 경우라면, 아래와 같은 상황을 고려해야 합니다.
JpaPagingItemReader는 내부적으로 OFFSET 기반 페이징을 사용하기 때문에, 데이터가 처리 도중 변경되면 페이징이 어긋날 수 있습니다. 예를 들어, writer에서 state를 변경하면 다음 페이지 조회 시 이미 처리된 데이터가 다시 조회되거나, 일부 데이터가 누락될 수 있습니다.
reader 쿼리에 이미 처리된 데이터를 제외하는 조건을 추가하는 것이 가장 쉬운 방법입니다.
state가 이미 변경된 데이터는 제외하고 조회하도록 조건을 추가합니다.
String query = """
SELECT d
FROM OrderDelivery d
JOIN FETCH d.order o
WHERE d.cancelFlag = 'N'
AND o.status = '03'
AND d.state = '6' -- 상태가 6인 데이터만 조회
AND d.updatedAt <= :dateLimit
""";
ItemWriter에서 state 값을 변경하고 저장합니다.
public class OrderDeliveryWriter implements ItemWriter<OrderDelivery> {
@Override
public void write(List<? extends OrderDelivery> items) {
for (OrderDelivery delivery : items) {
delivery.setState("7"); // 상태 변경
}
}
}
JpaPagingItemReader 대신 JpaCursorItemReader를 사용하는 것도 좋은 방법입니다.
Cursor 방식은 한 번에 데이터를 로드하지 않고 스트리밍 방식으로 처리하기 때문에 데이터 변경으로 인한 페이징 문제를 피할 수 있습니다.
@Bean
public JpaCursorItemReader<OrderDelivery> jpaCursorItemReader(EntityManagerFactory entityManagerFactory) {
return new JpaCursorItemReaderBuilder<OrderDelivery>()
.name("jpaCursorItemReader")
.entityManagerFactory(entityManagerFactory)
.queryString("""
SELECT d
FROM OrderDelivery d
JOIN FETCH d.order o
WHERE d.cancelFlag = 'N'
AND o.status = '03'
AND d.state = '6'
AND d.updatedAt <= :dateLimit
""")
.parameterValues(Map.of("dateLimit", LocalDateTime.now().minusDays(7)))
.build();
}
데이터 변경으로 인한 페이징 문제를 완전히 해결하려면, 스냅샷 테이블을 사용하는 것도 고려할 수 있습니다.
데이터가 매우 많고 단순한 상태 변경 작업이라면 벌크 업데이트 쿼리를 사용하는 것도 고려할 수 있습니다.
Spring Batch의 JdbcBatchItemWriter를 사용하면 더 빠르게 처리할 수 있습니다.
String updateQuery = """
UPDATE OrderDelivery d
SET d.state = '7'
WHERE d.cancelFlag = 'N'
AND d.state = '6'
AND d.updatedAt <= :dateLimit
""";
@Bean
public JdbcBatchItemWriter<OrderDelivery> jdbcBatchItemWriter(DataSource dataSource) {
return new JdbcBatchItemWriterBuilder<OrderDelivery>()
.dataSource(dataSource)
.sql(updateQuery)
.beanMapped()
.build();
}

제 상황에서는 WHERE 조건 추가 또는 JpaCursorItemReader를 추천합니다! 😊