SpringBoot

[SpringBoot] Spring Batch 이용권 만료

코카멍멍 2023. 12. 17. 19:56
반응형

스프링 Batch 구조

스프링의 구조는 Job, Step, Reader, Processor, Writer구조로 이루어져 있습니다.

 

 

Step 처리 방식

Step은 아이템 리더를 사용해 각 아이템을 개별적으로 읽은 다음 아이템 프로세서에게 전달해 필요한 처리를 수행합니다. 이 작업을 청크 사이즈가 될 때까지 반복하고 청크가 하나 완성이 되면 목록을 아이템 Writer를 통해 전달합니다.

 

 

ItemReader

ItemReader를 통해 스프링 배치가 아이템 리더에 리드 메소드를 호출하여 해당 메소드는 스텝 내에서 처리할 아이템 한 개를 반환하는 것입니다.

Batch를 사용할 때 한번에 너무 많은 데이터를 가져오면 메모리 부족과 같은 문제가 발생할 수도 있습니다. 그래서 자원을 효율적으로 사용하기 위해 batch에서는 2가지 방법을 사용해 batch 처리를 합니다. Cursor ItemReader, Paging ItemReader 방식이 있습니다.

 

 

Cursor ItemReader

커서는 하나의 커넥션으로 가기 때문에 수행 작업이 오래 가면 connection이 끊길 수 있습니다.

Paging ItemReader

페이징은 페이지라고 부르는 청크 크기만큼의 레코드를 가져옵니다.
한페이지 단위로 연결하고 끊기 때문에 안정성이 Cursor에 비해 높습니다.

ItemWriter

아이템 Writer는 데이터를 쓰는데 사용합니다.
아이템 Writer는 아이템 프로세서와 달리 아이템을 개별적으로 쓰는 것이 아닌 Chunk단위로 씁니다.

아이템 Writer 인터페이스를 보면 파라미터가 list인자인 것을 확인할 수 있습니다.

 

 


이용권 만료 배치 코드

Batch 실행 구조는 사용자들의 이용권 만료시간을 확인하고 만료시간이 지났으면 이용권을 만료 상태로 변경하는 로직입니다.

Step

@Bean  
public Step expirePassesStep() {  
    return this.stepBuilderFactory.get("expirePassesStep")  
        .<PassEntity, PassEntity>chunk(CHUNK_SIZE)  
        .reader(expirePassesItemReader())  
        .processor(expirePassesItemProcessor())  
        .writer(expirePassesItemWriter())  
        .build();  
}

Job

@Bean  
public Job expirePassesJob() {  
    return this.jobBuilderFactory.get("expirePassesJob")  
        .start(expirePassesStep())  
        .build();  
}

ItemReader

현재 작업은 Paging기반이 아닌 Cursor 기반으로 작업합니다.
그 이유는 페이지를 조회하기로 했는데 데이터를 읽어올 때 status가 progressed인 것들만 읽어와서 나중에 처리합니다. status값이 progressed에서 expired로 변경이 되게 됩니다. offset을 기반으로 row를 가져오는데 그렇게 되면 paging을 읽어올 때 특정 row들이 누락되는 현상이 생깁니다.

스프링 배치 시 문제점

 

Spring Batch Paging Reader 사용시 같은 조건의 데이터를 읽고 수정할때 문제

안녕하세요. 이번 시간에는 Spring Batch를 사용하시는 분들이 자주 묻는 질문 중 하나인 같은 조건의 데이터를 읽고 수정할때 어떻게 해야하는지 에 대해서 소개드리려고 합니다. 모든 코드는 Githu

jojoldu.tistory.com

ItemProcessor 부분

아이템 프로세서에서는 비즈니스 로직을 수행합니다. 이번 로직에서는 만료된 이용권을 EXPIRED로 변경하는 로직입니다.

ItemWriter 부분

내부 로직을 확인했을 때 EntityManager를 감싸고 있는 Wrapper 역할을 합니다.


이용권 만료 테스트 코드

Job 단위의 테스트를 지원하는 테스트 클래스

End-to-end 테스트를 지원하는 job launcher test-utils

  • JobLauncherTestUtils : 스프링 배치 테스트에 필요한 유틸 기능
  • JobRepositoryTestUtils : 데이터베이스에 저장된 JobExcution을 생성/삭제 지원
  • StepScopeTestExecutionListener : 배치 단위 테스트 시 StepScope 컨텍스트를 생성, 해당 컨텍스트를 통해 JobParamerter 등을 단위 테스트에서 DI 받을 수 있음
  • JobSopceTestExecutionListener : 배치 단위 테스트 시 JobScope 컨텍스트를 생성, 해당 컨텍스트를 통해 JobParameter 등을 단위 테스트에서 DI 받을 수 있음

@SpringBatchTest으로 위 코드를 자동으로 구성할 수 있습니다.
https://cheese10yun.github.io/spring-batch-test-2/

 

Spring Batch Test 작성 방법 및 고찰 - Yun Blog | 기술 블로그

Spring Batch Test 작성 방법 및 고찰 - Yun Blog | 기술 블로그

cheese10yun.github.io

 

테스트 코드 작성

@Slf4j  
@SpringBootTest  
@SpringBatchTest  
@ActiveProfiles("test")  
@ContextConfiguration(classes = {ExpirePassesJobConfig.class, TestBatchConfig.class})  
public class ExpirePassesJobConfigTest {  
    @Autowired  
    private JobLauncherTestUtils jobLauncherTestUtils;  

    @Autowired  
    private PassRepository passRepository;  

    @Test  
    public void test_expirePassesStep() throws Exception {  
        // given  
        addPassEntities(10);  

        // when  
        JobExecution jobExecution = jobLauncherTestUtils.launchJob();  
        JobInstance jobInstance = jobExecution.getJobInstance();  
        List<PassEntity> all = passRepository.findAll();  

        // then  
        assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());  
        assertEquals("expirePassesJob", jobInstance.getJobName());  
        assertEquals(all.size(), 10);  
        assertTrue(isNotExpirePass(all));  

    }  

    private void addPassEntities(int size) {  
        final LocalDateTime now = LocalDateTime.now();  
        final Random random = new Random();  

        List<PassEntity> passEntities = new ArrayList<>();  
        for (int i = 0; i < size; ++i) {  
            PassEntity passEntity = new PassEntity();  
            passEntity.setPassSeq(i+1);  
            passEntity.setUserId("A" + 1000000 + i);  
            passEntity.setStatus(PassStatus.PROGRESSED);  
            passEntity.setRemainingCount(random.nextInt(11));  
            passEntity.setStartedAt(now.minusDays(60));  
            passEntity.setEndedAt(now.minusDays(1));  
            passEntity.setGymId("1");  
            passEntity.setPassId("1");  
            passEntity.setPackageSeq(1);  
            passEntities.add(passEntity);  
        }  
        passRepository.saveAll(passEntities);  
    }  

    private boolean isNotExpirePass(List<PassEntity> passes) {  
        return passes.parallelStream()  
            .anyMatch(pass -> !pass.getExpiredAt().equals(PassStatus.EXPIRED));  
    }  
}

테스트 코드

총 4가지를 검증했습니다.

  • 실행이 잘 완료 됐는지
  • job Name 확인
  • 작업한 ROW 갯수 확인
  • 이용권 만료 확인

테스트 결과

참조

https://jojoldu.tistory.com/455

 

10. Spring Batch 가이드 - Spring Batch 테스트 코드

배치 애플리케이션이 웹 애플리케이션 보다 어려운 점을 꼽자면 QA를 많이들 얘기합니다. 일반적으로 웹 애플리케이션의 경우 전문 테스터 분들 혹은 QA 분들이 전체 기능을 검증을 해주시는 반

jojoldu.tistory.com

 

https://fastcampus.co.kr/courses/211368

 

커리어 성장을 위한 최고의 실무교육 아카데미 | 패스트캠퍼스

성인 교육 서비스 기업, 패스트캠퍼스는 개인과 조직의 실질적인 '업(業)'의 성장을 돕고자 모든 종류의 교육 콘텐츠 서비스를 제공하는 대한민국 No. 1 교육 서비스 회사입니다.

fastcampus.co.kr

 

반응형