대부분 스프링 배치의 내용은 '기억보다는 기록을' 블로그를 참고하여 공부하였습니다.
배치에 대해 공부하고자 하시는분은 이 글 보다 아래 블로그를 보는게 훨씬 도움이 됩니다
https://jojoldu.tistory.com/
글은 총 2편으로 이어질 예정이며 지금까지 정리한 방법을 제외하고 더 좋은 방법을 공부하게 된다면 3편까지 이어질 수 있습니다.
특정 데이터셋을 조회하여 총합계, 통계, 합계 등을 구해야하는 배치는 어떻게 진행해야될까
에 대해 고민하면서 삽질한 경험을 바탕으로 정리하고자 글을 씁니다.@ToString
@Getter
@Setter
@NoArgsConstructor
@Entity
public class Pay {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long amount;
private String txName;
private LocalDateTime txDateTime;
public Pay(Long amount, String txName, String txDateTime) {
this.amount = amount;
this.txName = txName;
this.txDateTime = LocalDateTime.parse(txDateTime, FORMATTER);
}
public Pay(Long amount, String txName, LocalDateTime txDateTime) {
this.amount = amount;
this.txName = txName;
this.txDateTime = txDateTime;
}
public Pay(Long id, Long amount, String txName, String txDateTime) {
this.id = id;
this.amount = amount;
this.txName = txName;
this.txDateTime = LocalDateTime.parse(txDateTime, FORMATTER);
}
}
@Entity
@Getter
@NoArgsConstructor
public class TotalPay {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long sum;
private LocalDate date;
public TotalPay(Long sum, String date) {
this.sum = sum;
this.date = LocalDate.parse(date,FORMATTER);
}
public void addSum(Long item) {
this.sum += sum;
}
}
Pay
, TotalPay
가 있는 상황에서 특정 날짜의 Pay들의 Amount를 합산해서 저장해야하는 문제 상황이 있다고 가정했습니다.Repository 클래스는 생략하겠습니다.
@Slf4j
@Configuration
@RequiredArgsConstructor
public class PayTotalJobConfiguration {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static final String JOB_NAME = "PayTotalJob";
public static final String BEAN_PREFIX = JOB_NAME + "_";
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
private final EntityManagerFactory entityManagerFactory;
private final TotalPayRepository totalPayRepository;
private final DataSource dataSource;
//Pay를 돌면서 계속해서 추가할 total값
private Long total = 0L;
private final static int chunkSize = 1000;
@Bean(JOB_NAME)
public Job job() {
return jobBuilderFactory.get(JOB_NAME)
.start(step(null))
.next(step2(null))
.build();
}
@Bean(BEAN_PREFIX + "step")
@JobScope
public Step step(@Value("#{jobParameters[requestDate]}") String requestDate) {
return stepBuilderFactory.get(BEAN_PREFIX + "step")
.<Pay, Pay>chunk(chunkSize)
.reader(reader(null))
.writer(writer())
.build();
}
@Bean(BEAN_PREFIX + "reader")
@StepScope
public JpaPagingItemReader<Pay> reader(@Value("#{jobParameters[requestDate]}") String requestDate) {
return new JpaPagingItemReaderBuilder<Pay>()
.name(BEAN_PREFIX + "reader")
.entityManagerFactory(entityManagerFactory)
.pageSize(chunkSize)
.queryString("select p from Pay p where to_char(tx_date_time,'yyyy-mm-dd') = '" + requestDate + "'")
.build();
}
@Bean(BEAN_PREFIX + "writer")
@StepScope
public ItemWriter<Pay> writer() {
return list -> {
for(Pay pay : list) {
log.info("Current = {}", pay);
total += pay.getAmount();
}
};
}
@Bean(BEAN_PREFIX + "step2")
@JobScope
public Step step2(@Value("#{jobParameters[requestDate]}") String requestDate) {
return stepBuilderFactory.get(BEAN_PREFIX + "step2")
.<TotalPay, TotalPay>chunk(1)
.reader(reader2(null))
.writer(writer2())
.build();
}
@Bean(BEAN_PREFIX + "reader2")
@StepScope
public CustomCreateTotalPayReader reader2(@Value("#{jobParameters[requestDate]}") String requestDate) {
return new CustomCreateTotalPayReader(requestDate,total);
}
@Bean(BEAN_PREFIX + "writer2")
@StepScope
public JpaItemWriter<TotalPay> writer2() {
JpaItemWriter<TotalPay> jpaItemWriter = new JpaItemWriter<>();
jpaItemWriter.setEntityManagerFactory(entityManagerFactory);
return jpaItemWriter;
}
}
public class CustomCreateTotalPayReader implements ItemReader<TotalPay> {
private static int count;
private String requestDate;
private Long total;
public CustomCreateTotalPayReader(String requestDate, Long total) {
this.requestDate = requestDate;
this.total = total;
}
@Override
public TotalPay read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
TotalPay totalPay = null;
if(count < 1) {
totalPay = new TotalPay(total, requestDate);
count++;
}
return totalPay;
}
}
Long total
에 값을 추가한다.TotalPay
를 받아오는 CustomCreateTotalPayReader
를 만든다.CustomCreateTotalPayReader
에서 가져온 TotalPay
를 JpaItemWriter를 통해 저장한다.@Slf4j
@Configuration
@RequiredArgsConstructor
public class PayTotalJobSecondConfiguration {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static final String JOB_NAME = "PayTotalSecondJob";
public static final String BEAN_PREFIX = JOB_NAME + "_";
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
private final EntityManagerFactory entityManagerFactory;
private final TotalPayRepository totalPayRepository;
private Long total = 0L;
private final static int chunkSize = 1000;
@Bean(JOB_NAME)
public Job job() {
return jobBuilderFactory.get(JOB_NAME)
.start(step(null))
.next(step2(null))
.build();
}
@Bean(BEAN_PREFIX + "step")
@JobScope
public Step step(@Value("#{jobParameters[requestDate]}") String requestDate) {
return stepBuilderFactory.get(BEAN_PREFIX + "step")
.<Pay, Pay>chunk(chunkSize)
.reader(reader(null))
.writer(writer())
.build();
}
@Bean(BEAN_PREFIX + "reader")
@StepScope
public JpaPagingItemReader<Pay> reader(@Value("#{jobParameters[requestDate]}") String requestDate) {
return new JpaPagingItemReaderBuilder<Pay>()
.name(BEAN_PREFIX + "reader")
.entityManagerFactory(entityManagerFactory)
.pageSize(chunkSize)
.queryString("select p from Pay p where to_char(tx_date_time,'yyyy-mm-dd') = '" + requestDate + "'")
.build();
}
@Bean(BEAN_PREFIX + "writer")
@StepScope
public ItemWriter<Pay> writer() {
return list -> {
for(Pay pay : list) {
log.info("Current = {}", pay);
total += pay.getAmount();
}
};
}
@Bean(BEAN_PREFIX + "step2")
@JobScope
public Step step2(@Value("#{jobParameters[requestDate]}") String requestDate) {
return stepBuilderFactory.get(BEAN_PREFIX + "step2")
.tasklet((contribution, chunkContext) -> {
TotalPay totalPay = new TotalPay(total,requestDate);
totalPayRepository.save(totalPay);
return RepeatStatus.FINISHED;
}).build();
}
}
Long total
에 값을 추가하는 로직은 첫번째 삽질과 다를게 없다.return stepBuilderFactory.get(BEAN_PREFIX + "step2")
.tasklet((contribution, chunkContext) -> {
TotalPay totalPay = new TotalPay(total,requestDate);
totalPayRepository.save(totalPay);
return RepeatStatus.FINISHED;
}).build();
}