ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Batch 구현 - 3
    [공부] 프로그래밍/Spring・Spring Boot (JAVA) 2024. 4. 24. 15:32

     

    ♧ 전체 코드 : https://github.com/woodisco/pass-batch

    GitHub - woodisco/pass-batch

    Contribute to woodisco/pass-batch development by creating an account on GitHub.

    github.com

     

    이용권 일괄 지급 배치

    ① AddPassesJopConfig 작성

    package com.fastcampus.pass.job.pass;
    
    import org.springframework.batch.core.Job;
    import org.springframework.batch.core.Step;
    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.core.step.tasklet.Tasklet;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.transaction.PlatformTransactionManager;
    
    @Configuration
    public class AddPassesJopConfig {
        private final AddPassesTasklet addPassesTasklet;
    
        public AddPassesJopConfig(AddPassesTasklet addPassesTasklet) {
            this.addPassesTasklet = addPassesTasklet;
        }
    
        @Bean
        public Job addPassesJob(JobRepository jobRepository, Step addPassesStep) {
            return new JobBuilder("addPassesJob", jobRepository)
                    .start(addPassesStep)
                    .build();
        }
    
        @Bean
        public Step addPassesStep(JobRepository jobRepository,
                                  PlatformTransactionManager transactionManager,
                                  Tasklet addPassesTasklet) {
            return new StepBuilder("addPassesStep", jobRepository)
                    .tasklet(addPassesTasklet, transactionManager)
                    .build();
        }
    }

     
    ② AddPassesTasklet 작성 (Bean으로 등록)

    package com.fastcampus.pass.job.pass;
    
    import com.fastcampus.pass.repository.pass.*;
    import com.fastcampus.pass.repository.user.UserGroupMappingEntity;
    import com.fastcampus.pass.repository.user.UserGroupMappingRepository;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.batch.core.StepContribution;
    import org.springframework.batch.core.scope.context.ChunkContext;
    import org.springframework.batch.core.step.tasklet.Tasklet;
    import org.springframework.batch.repeat.RepeatStatus;
    import org.springframework.stereotype.Component;
    
    import java.time.LocalDateTime;
    import java.util.ArrayList;
    import java.util.List;
    
    @Slf4j
    @Component
    public class AddPassesTasklet implements Tasklet {
    
        private final PassRepository passRepository;
        private  final BulkPassRepository bulkPassRepository;
        private  final UserGroupMappingRepository userGroupMappingRepository;
    
        public AddPassesTasklet(PassRepository passRepository, BulkPassRepository bulkPassRepository, UserGroupMappingRepository userGroupMappingRepository) {
            this.passRepository = passRepository;
            this.bulkPassRepository = bulkPassRepository;
            this.userGroupMappingRepository = userGroupMappingRepository;
        }
    
    
        @Override
        public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
            // 이용권 시작 일시 1일 전 user group 내 각 사용자에게 이용권을 추가해줍니다.
            final LocalDateTime startedAt = LocalDateTime.now().minusDays(1);
            final List<BulkPassEntity> bulkPassEntities = bulkPassRepository.findByStatusAndStartedAtGreaterThan(BulkPassStatus.READY, startedAt);
    
            int count = 0;
            for (BulkPassEntity bulkPassEntity : bulkPassEntities) {
                // user group에 속한 userId들을 조회합니다.
                final List<String> userIds = userGroupMappingRepository.findByUserGroupId(bulkPassEntity.getUserGroupId())
                        .stream().map(UserGroupMappingEntity::getUserId).toList();
    
                // 각 userId로 이용권을 추가합니다.
                count += addPasses(bulkPassEntity, userIds);
                // pass 추가 이후 상태를 COMPLETED로 업데이트합니다.
                bulkPassEntity.setStatus(BulkPassStatus.COMPLETED);
            }
    
            log.info("AddPassesTasklet - execute: 이용권 {}건 추가 완료, startedAt={}", count, startedAt);
            return RepeatStatus.FINISHED;
        }
    
        // bulkPass의 정보로 pass 데이터를 생성합니다.
        private int addPasses(BulkPassEntity bulkPassEntity, List<String> userIds) {
            List<PassEntity> passEntities = new ArrayList<>();
            for (String userId : userIds) {
                PassEntity passEntity = PassModelMapper.INSTANCE.toPassEntity(bulkPassEntity, userId);
                passEntities.add(passEntity);
            }
    
            return passRepository.saveAll(passEntities).size();
        }
    }
    @Component :
    @Component 주석은 클래스를 Spring 컴포넌트로 선언하는 데 사용됩니다. 이를 통해 Spring에게 클래스를 Bean으로 취급하고 해당 Bean을 관리하도록 지시합니다.
    Spring 애플리케이션 콘텍스트에서 컴포넌트 스캐닝이 활성화되어 있는 경우, Spring은 자동으로 @Component로 주석이 달린 클래스를 감지하고 Spring 빈으로 등록합니다.

     
    ③ AddPassesTaskletTest 작성

    package com.fastcampus.pass.job.pass;
    
    import com.fastcampus.pass.repository.pass.*;
    import com.fastcampus.pass.repository.user.UserGroupMappingEntity;
    import com.fastcampus.pass.repository.user.UserGroupMappingRepository;
    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.extension.ExtendWith;
    import org.mockito.ArgumentCaptor;
    import org.mockito.InjectMocks;
    import org.mockito.Mock;
    import org.mockito.junit.jupiter.MockitoExtension;
    import org.springframework.batch.core.StepContribution;
    import org.springframework.batch.core.scope.context.ChunkContext;
    import org.springframework.batch.repeat.RepeatStatus;
    
    import java.time.LocalDateTime;
    import java.util.List;
    
    import static org.junit.jupiter.api.Assertions.assertEquals;
    import static org.mockito.ArgumentMatchers.*;
    import static org.mockito.Mockito.*;
    
    @Slf4j
    @ExtendWith(MockitoExtension.class)
    class AddPassesTaskletTest {
    
        @Mock
        private StepContribution stepContribution;
    
        @Mock
        private ChunkContext chunkContext;
    
        @Mock
        private PassRepository passRepository;
    
        @Mock
        private BulkPassRepository bulkPassRepository;
    
        @Mock
        private UserGroupMappingRepository userGroupMappingRepository;
    
        // @InjectMocks 클래스의 인스턴스를 생성하고 @Mock으로 생성된 객체를 주입합니다.
        @InjectMocks
        private  AddPassesTasklet addPassesTasklet;
    
        @Test
        public void test_execute() throws Exception {
            // given
            final LocalDateTime now = LocalDateTime.now();
            final String userId = "A1000000";
            final String userGroupId = "GROUP";
    
            BulkPassEntity bulkPassEntity = new BulkPassEntity();
            bulkPassEntity.setPackageSeq(1);
            bulkPassEntity.setUserGroupId(userGroupId);
            bulkPassEntity.setStatus(BulkPassStatus.COMPLETED);
            bulkPassEntity.setCount(10);
            bulkPassEntity.setStartedAt(now.minusDays(60));
            bulkPassEntity.setEndedAt(now);
    
            final UserGroupMappingEntity userGroupMappingEntity = new UserGroupMappingEntity();
            userGroupMappingEntity.setUserGroupId(userGroupId);
            userGroupMappingEntity.setUserId(userId);
    
            // when
            when(bulkPassRepository.findByStatusAndStartedAtGreaterThan(eq(BulkPassStatus.READY), any())).thenReturn(List.of(bulkPassEntity));
            when(userGroupMappingRepository.findByUserGroupId(eq(userGroupId))).thenReturn(List.of(userGroupMappingEntity));
    
            RepeatStatus repeatStatus = addPassesTasklet.execute(stepContribution, chunkContext);
    
            // then
            assertEquals(RepeatStatus.FINISHED, repeatStatus);
    
            ArgumentCaptor<List> passEntitiesCaptor = ArgumentCaptor.forClass(List.class);
            // passRepository.saveAll이 1번 호출되었고, 매개변수가 passEntitiesCaptor로 캡처되었는지 확인합니다.
            verify(passRepository, times(1)).saveAll(passEntitiesCaptor.capture());
            final List<PassEntity> passEntities = passEntitiesCaptor.getValue();
    
            assertEquals(1, passEntities.size());
            final PassEntity passEntity = passEntities.get(0);
            assertEquals(1, passEntity.getPackageSeq());
            assertEquals(userId, passEntity.getUserId());
            assertEquals(PassStatus.READY, passEntity.getStatus());
            assertEquals(10, passEntity.getRemainingCount());
        }
    }

     

    출처 : 패스트캠퍼스 10개 프로젝트로 완성하는 백엔드 웹개발(Java/Spring) 초격차 패키지 Online
     

    '[공부] 프로그래밍 > Spring・Spring Boot (JAVA)' 카테고리의 다른 글

    Spring Batch 구현 - 4  (0) 2024.04.25
    Mockito 기반의 테스트  (0) 2024.04.24
    Spring Batch 구현 - 2  (0) 2024.04.18
    Spring Batch 구현 - 1  (0) 2024.04.16
    Scheduler 구현  (0) 2024.04.09
Designed by Tistory.