본문으로 건너뛰기
Advertisement

15.2 Job, Step, Tasklet 라이프사이클

@EnableBatchProcessing을 설정하고 Java Config로 Job/Step을 빈으로 선언하는 것이 Spring Batch의 표준 구성 방법입니다. Spring Boot 3(Batch 5.x)부터는 @EnableBatchProcessing 없이도 자동 구성이 활성화됩니다.

1. Job과 Step 선언 (Spring Batch 5 스타일)

@Configuration
@RequiredArgsConstructor
public class CouponInactiveBatchConfig {

private final JobRepository jobRepository;
private final PlatformTransactionManager transactionManager;
private final DataSource dataSource;

// 1. Job 선언
@Bean
public Job inactiveCouponJob(Step inactiveCouponStep) {
return new JobBuilder("inactiveCouponJob", jobRepository)
.incrementer(new RunIdIncrementer()) // 매 실행마다 고유한 ID 생성
.flow(inactiveCouponStep)
.end()
.build();
}

// 2. Step 선언 (Chunk 방식)
@Bean
public Step inactiveCouponStep() {
return new StepBuilder("inactiveCouponStep", jobRepository)
.<Coupon, Coupon>chunk(1000, transactionManager) // 1000건씩 한 청크
.reader(couponItemReader())
.processor(couponItemProcessor())
.writer(couponItemWriter())
.build();
}

// 3-1. Reader: DB에서 만료된 쿠폰 1000건씩 읽기
@Bean
public JdbcPagingItemReader<Coupon> couponItemReader() {
return new JdbcPagingItemReaderBuilder<Coupon>()
.name("couponItemReader")
.dataSource(dataSource)
.selectClause("SELECT id, user_id, status, expire_date")
.fromClause("FROM coupons")
.whereClause("WHERE status = 'ACTIVE' AND expire_date < NOW()")
.sortKeys(Map.of("id", Order.ASCENDING))
.rowMapper(new BeanPropertyRowMapper<>(Coupon.class))
.pageSize(1000)
.build();
}

// 3-2. Processor: 상태값 변경
@Bean
public ItemProcessor<Coupon, Coupon> couponItemProcessor() {
return coupon -> {
coupon.setStatus("INACTIVE"); // 만료 처리
return coupon;
};
}

// 3-3. Writer: 변경된 쿠폰 일괄 업데이트
@Bean
public JdbcBatchItemWriter<Coupon> couponItemWriter() {
return new JdbcBatchItemWriterBuilder<Coupon>()
.dataSource(dataSource)
.sql("UPDATE coupons SET status = :status WHERE id = :id")
.beanMapped()
.build();
}
}

2. Job 수동 트리거 (API or Scheduler)

@RestController
@RequiredArgsConstructor
public class BatchController {

private final JobLauncher jobLauncher;
private final Job inactiveCouponJob;

@PostMapping("/admin/batch/inactive-coupons")
public ResponseEntity<String> runBatch() throws Exception {
JobParameters params = new JobParametersBuilder()
.addLong("time", System.currentTimeMillis()) // 매번 다른 파라미터로 재실행 허용
.toJobParameters();

jobLauncher.run(inactiveCouponJob, params);
return ResponseEntity.ok("배치 실행 완료");
}
}
Advertisement