#29 pass: add passes job

This commit is contained in:
haerong22
2022-12-28 01:38:53 +09:00
parent 336832b1a7
commit 07d0f5137a
6 changed files with 258 additions and 3 deletions

View File

@@ -21,15 +21,20 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-batch'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// hibernate-types
implementation 'com.vladmihalcea:hibernate-types-52:2.19.2'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// mapstruct
implementation 'org.mapstruct:mapstruct:1.5.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.2.Final'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.batch:spring-batch-test'

View File

@@ -0,0 +1,32 @@
package com.example.passbatch.job.pass;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@RequiredArgsConstructor
public class AddPassesJobConfig {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
private final AddPassesTasklet addPassesTasklet;
@Bean
public Job addPassesJob() {
return jobBuilderFactory.get("addPassesJob")
.start(addPassesStep())
.build();
}
@Bean
public Step addPassesStep() {
return stepBuilderFactory.get("addPassesStep")
.tasklet(addPassesTasklet)
.build();
}
}

View File

@@ -0,0 +1,61 @@
package com.example.passbatch.job.pass;
import com.example.passbatch.repository.pass.*;
import com.example.passbatch.repository.user.UserGroupMappingEntity;
import com.example.passbatch.repository.user.UserGroupMappingRepository;
import lombok.RequiredArgsConstructor;
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;
import java.util.stream.Collectors;
@Slf4j
@Component
@RequiredArgsConstructor
public class AddPassesTasklet implements Tasklet {
private final PassRepository passRepository;
private final BulkPassRepository bulkPassRepository;
private final 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;
// 대량 이용권 정보를 돌면서 user group 에 속한 userId 를 조회하고 해당 userId로 이용권을 추가
for (BulkPassEntity bulkPassEntity : bulkPassEntities) {
final List<String> userIds = userGroupMappingRepository.findByUserGroupId(bulkPassEntity.getUserGroupId())
.stream()
.map(UserGroupMappingEntity::getUserId).collect(Collectors.toList());
count += addPasses(bulkPassEntity, userIds);
bulkPassEntity.setStatus(BulkPassStatus.COMPLETED);
}
log.info("AddPassesTasklet - execute: 이용권 {}건 추가 완료, startedAt={}", count, startedAt);
return RepeatStatus.FINISHED;
}
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();
}
}

View File

@@ -0,0 +1,25 @@
package com.example.passbatch.repository.pass;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.mapstruct.ReportingPolicy;
import org.mapstruct.factory.Mappers;
// ReportingPolicy.IGNORE: 일치하지 않은 필드를 무시
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface PassModelMapper {
PassModelMapper INSTANCE = Mappers.getMapper(PassModelMapper.class);
// 필드명이 같지 않거나 custom하게 매핑해주기 위해서는 @Mapping을 추가
@Mapping(target = "status", qualifiedByName = "defaultStatus")
@Mapping(target = "remainingCount", source = "bulkPassEntity.count")
PassEntity toPassEntity(BulkPassEntity bulkPassEntity, String userId);
// BulkPassStatus와 관계 없이 PassStatus값을 설정
@Named("defaultStatus")
default PassStatus status(BulkPassStatus status) {
return PassStatus.READY;
}
}

View File

@@ -0,0 +1,95 @@
package com.example.passbatch.job.pass;
import com.example.passbatch.repository.pass.*;
import com.example.passbatch.repository.user.UserGroupMappingEntity;
import com.example.passbatch.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)
public 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 String userGroupId = "GROUP";
final String userId = "A1000000";
final Integer packageSeq = 1;
final Integer count = 10;
final LocalDateTime now = LocalDateTime.now();
final BulkPassEntity bulkPassEntity = new BulkPassEntity();
bulkPassEntity.setPackageSeq(packageSeq);
bulkPassEntity.setUserGroupId(userGroupId);
bulkPassEntity.setStatus(BulkPassStatus.READY);
bulkPassEntity.setCount(count);
bulkPassEntity.setStartedAt(now);
bulkPassEntity.setEndedAt(now.plusDays(60));
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("GROUP")))
.thenReturn(List.of(userGroupMappingEntity));
RepeatStatus repeatStatus = addPassesTasklet.execute(stepContribution, chunkContext);
// then
// execute의 return 값인 RepeatStatus 값을 확인
assertEquals(RepeatStatus.FINISHED, repeatStatus);
// 추가된 PassEntity 값을 확인
ArgumentCaptor<List> passEntitiesCaptor = ArgumentCaptor.forClass(List.class);
verify(passRepository, times(1)).saveAll(passEntitiesCaptor.capture());
final List<PassEntity> passEntities = passEntitiesCaptor.getValue();
assertEquals(1, passEntities.size());
final PassEntity passEntity = passEntities.get(0);
assertEquals(packageSeq, passEntity.getPackageSeq());
assertEquals(userId, passEntity.getUserId());
assertEquals(PassStatus.READY, passEntity.getStatus());
assertEquals(count, passEntity.getRemainingCount());
}
}

View File

@@ -0,0 +1,37 @@
package com.example.passbatch.repository.pass;
import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.*;
class PassModelMapperTest {
@Test
void test_toPassEntity() {
// given
final LocalDateTime now = LocalDateTime.now();
final String userId = "A1000000";
BulkPassEntity bulkPassEntity = new BulkPassEntity();
bulkPassEntity.setPackageSeq(1);
bulkPassEntity.setUserGroupId("GROUP");
bulkPassEntity.setStatus(BulkPassStatus.COMPLETED);
bulkPassEntity.setCount(10);
bulkPassEntity.setStartedAt(now.minusDays(60));
bulkPassEntity.setEndedAt(now);
// when
final PassEntity passEntity = PassModelMapper.INSTANCE.toPassEntity(bulkPassEntity, userId);
// then
assertEquals(1, passEntity.getPackageSeq());
assertEquals(PassStatus.READY, passEntity.getStatus());
assertEquals(10, passEntity.getRemainingCount());
assertEquals(now.minusDays(60), passEntity.getStartedAt());
assertEquals(now, passEntity.getEndedAt());
}
}