[공부] 프로그래밍/Spring・Spring Boot (JAVA)
Spring Batch 구현 - 3
woodisco
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