package com.interplug.qcast.batch.master; import com.fasterxml.jackson.core.type.TypeReference; import com.interplug.qcast.biz.displayItem.DisplayItemService; import com.interplug.qcast.biz.displayItem.dto.BomSyncResponse; import com.interplug.qcast.util.InterfaceQsp; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecutionListener; import org.springframework.batch.core.Step; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemWriter; import org.springframework.batch.item.support.ListItemReader; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.transaction.PlatformTransactionManager; /** Bom Item 마스터 동기화 배치 */ @Configuration public class BomConfiguration implements JobExecutionListener { private final DisplayItemService displayItemService; private final InterfaceQsp interfaceQsp; List bomSyncList; @Value("${qsp.master-bom-batch-url}") private String qspInterfaceUrl; public BomConfiguration(DisplayItemService displayItemService, InterfaceQsp interfaceQsp) { this.displayItemService = displayItemService; this.interfaceQsp = interfaceQsp; } @Bean public Job bomJob(JobRepository jobRepository, Step bomStep) { return new JobBuilder("bomJob", jobRepository).start(bomStep).build(); } @Bean public Step bomStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) throws Exception { return new StepBuilder("bomStep", jobRepository) .chunk(100, transactionManager) .reader(bomReader()) .processor(bomProcessor()) .writer(bomWriter()) .build(); } @Bean @StepScope public ListItemReader bomReader() throws Exception { this.bomSyncList = interfaceQsp.callApiData( HttpMethod.GET, qspInterfaceUrl, null, new TypeReference>() {}); if (bomSyncList == null) { return new ListItemReader<>(Collections.emptyList()); } // 2026-05-20 청크 경계에서 같은 키 A/D 가 분리되어 A 가 먼저 MERGE 된 뒤 // 다음 청크의 D 가 그것을 삭제해버리는 리스크를 차단하기 위해, // 전체 리스트를 statCd='D' 우선으로 정렬한다. (Spring Batch 는 청크를 순차 처리하므로 // 모든 D 청크가 끝난 뒤에 A/U 청크가 시작되어 전역적으로 'A 가 이김' 이 보장됨) bomSyncList.sort( Comparator.comparingInt((BomSyncResponse b) -> "D".equals(b.getStatCd()) ? 0 : 1)); return new ListItemReader<>(bomSyncList); } @Bean public ItemProcessor bomProcessor() { return item -> item; } @Bean public ItemWriter bomWriter() { return items -> { displayItemService.setBomSyncSave(new ArrayList<>(items.getItems())); }; } }