-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature : 매일 04시에 회원 별 미션 업데이트 batch 기능 개발 (#90)
* build : spring batch 의존성 추가 * fix : batch 사용 시 DataSource Bean 이름 기본 값 dataSource * refactor : 생성자 접근 제한자 변경 * feature : Batch 를 위한 설정 * feature : Batch 실행 시 발생할 수 있는 공통 Exception * feature : mission batch 개발 * feature : mission batch database 개발 * test : mission batch 테스트 (미완) * test : mission batch 테스트 미완 (임시 주석 처리) * refactor : @configuration -> @TestConfiguration 으로 변경 및 테스트 용 embedded datasource 추가 * test : MemberMissionBatch 테스트 - memberMissionReSettingBatchStep 실행 안되는 오류 있음 * refactor : memberJpaEntityRowMapper -> memberBatchEntityRowMapper 로 변경 * chore * refactor : Step 간 데이터 공유 리팩토링 * test : MemberMissionReSettingStepExecutionListener 클래스 로딩 추가
- Loading branch information
1 parent
4a1fd06
commit 0481f14
Showing
27 changed files
with
910 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
src/main/java/univ/earthbreaker/namu/batch/BatchConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package univ.earthbreaker.namu.batch; | ||
|
||
import java.time.Clock; | ||
import java.time.ZoneId; | ||
|
||
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.scheduling.annotation.EnableScheduling; | ||
|
||
@Configuration | ||
@EnableScheduling | ||
@EnableBatchProcessing | ||
public class BatchConfig { | ||
|
||
private static final ZoneId KOREA_TIME_ZONE = ZoneId.of("Asia/Seoul"); | ||
|
||
@Bean("koreaTimeClockInBatch") | ||
public Clock clock() { | ||
return Clock.system(KOREA_TIME_ZONE); | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
src/main/java/univ/earthbreaker/namu/batch/BatchException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package univ.earthbreaker.namu.batch; | ||
|
||
import org.jetbrains.annotations.NotNull; | ||
|
||
public class BatchException extends RuntimeException { | ||
|
||
private BatchException(String message) { | ||
super(message); | ||
} | ||
|
||
public static @NotNull BatchException isStepExecutionNull() { | ||
return new BatchException("[Mission 세팅 JOB] : StepExecution 가 null 값으로, 제대로 설정되지 않았습니다"); | ||
} | ||
|
||
public static @NotNull BatchException pagingQueryCreationFailed(String message) { | ||
return new BatchException(String.format("[Mission 세팅 JOB] : Paging Query 생성에 실패했습니다 - %s", message)); | ||
} | ||
|
||
public static @NotNull BatchException retrieveStepExecutionDataFailed() { | ||
return new BatchException("@BeforeStep 이 정상 동작하지 않았습니다"); | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
src/main/java/univ/earthbreaker/namu/batch/mission/AbstractExecutionContextManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package univ.earthbreaker.namu.batch.mission; | ||
|
||
import org.springframework.batch.core.JobExecution; | ||
import org.springframework.batch.core.StepExecution; | ||
import org.springframework.batch.item.ExecutionContext; | ||
|
||
import univ.earthbreaker.namu.batch.BatchException; | ||
|
||
public abstract class AbstractExecutionContextManager<T> { | ||
|
||
static final String MISSIONS_PROMOTION_KEY = "SHARED_MISSIONS"; | ||
|
||
private StepExecution stepExecution; | ||
|
||
protected AbstractExecutionContextManager() { | ||
} | ||
|
||
protected void putDataToStepExecutionContext(String key, T value) { | ||
validateStepExecutionIsNull(); | ||
ExecutionContext stepExecutionContext = stepExecution.getExecutionContext(); | ||
stepExecutionContext.put(key, value); | ||
} | ||
|
||
/** | ||
* ClassCastException 을 발생시킬 가능성이 있는 일반적인 캐스트와 달리, | ||
* AbstractExecutionContextManager 클래스를 상속한 클래스들은 | ||
* T 타입으로 제한되기 때문에 해당 캐스트는 안전합니다 | ||
* @param key ExecutionContext 에 저장한 <b>공유 데이터</b>의 key 값 | ||
* @return 공유 데이터 | ||
*/ | ||
@SuppressWarnings("unchecked") | ||
protected T getDataFromJobExecutionContext(String key) { | ||
validateStepExecutionIsNull(); | ||
JobExecution jobExecution = stepExecution.getJobExecution(); | ||
ExecutionContext jobExecutionContext = jobExecution.getExecutionContext(); | ||
return (T)jobExecutionContext.get(key); | ||
} | ||
|
||
private void validateStepExecutionIsNull() { | ||
if (stepExecution == null) { | ||
throw BatchException.isStepExecutionNull(); | ||
} | ||
} | ||
|
||
protected void setCurrentStepExecution(StepExecution stepExecution) { | ||
this.stepExecution = stepExecution; | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
src/main/java/univ/earthbreaker/namu/batch/mission/FixMissionBatchEntity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package univ.earthbreaker.namu.batch.mission; | ||
|
||
import java.io.Serializable; | ||
|
||
public record FixMissionBatchEntity( | ||
Long missionNo, | ||
String missionActivity, | ||
String missionType | ||
) implements Serializable { | ||
} |
33 changes: 33 additions & 0 deletions
33
src/main/java/univ/earthbreaker/namu/batch/mission/LoadMissionStepDecider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package univ.earthbreaker.namu.batch.mission; | ||
|
||
import org.jetbrains.annotations.NotNull; | ||
import org.springframework.batch.core.JobExecution; | ||
import org.springframework.batch.core.StepExecution; | ||
import org.springframework.batch.core.job.flow.FlowExecutionStatus; | ||
import org.springframework.batch.core.job.flow.JobExecutionDecider; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
public class LoadMissionStepDecider implements JobExecutionDecider { | ||
|
||
static final String SPECIAL_STEP = "SPECIAL_STEP"; | ||
static final String NORMAL_STEP = "NORMAL_STEP"; | ||
|
||
private final MissionDateSupport missionDateSupport; | ||
|
||
public LoadMissionStepDecider(MissionDateSupport missionDateSupport) { | ||
this.missionDateSupport = missionDateSupport; | ||
} | ||
|
||
@Override | ||
public @NotNull FlowExecutionStatus decide( | ||
@NotNull JobExecution jobExecution, | ||
StepExecution stepExecution | ||
) { | ||
if (missionDateSupport.isSpecialDate()) { | ||
return new FlowExecutionStatus(SPECIAL_STEP); | ||
} else { | ||
return new FlowExecutionStatus(NORMAL_STEP); | ||
} | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
src/main/java/univ/earthbreaker/namu/batch/mission/LoadNormalMissionTasklet.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package univ.earthbreaker.namu.batch.mission; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import org.jetbrains.annotations.NotNull; | ||
import org.springframework.batch.core.StepContribution; | ||
import org.springframework.batch.core.configuration.annotation.StepScope; | ||
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 univ.earthbreaker.namu.database.core.mission.FixMissionJpaEntity; | ||
import univ.earthbreaker.namu.database.core.mission.FixMissionJpaRepository; | ||
|
||
@Component | ||
@StepScope | ||
public class LoadNormalMissionTasklet extends AbstractExecutionContextManager<SharedFixMissions> implements Tasklet { | ||
|
||
private final FixMissionJpaRepository fixMissionJpaRepository; | ||
|
||
public LoadNormalMissionTasklet(FixMissionJpaRepository fixMissionJpaRepository) { | ||
super(); | ||
this.fixMissionJpaRepository = fixMissionJpaRepository; | ||
} | ||
|
||
@Override | ||
public RepeatStatus execute( | ||
@NotNull StepContribution contribution, | ||
@NotNull ChunkContext chunkContext | ||
) { | ||
List<FixMissionJpaEntity> defaultMissions = fixMissionJpaRepository.findDefaultMissionsByRandom(); | ||
List<FixMissionJpaEntity> todayMissions = fixMissionJpaRepository.findTodayMissionsByRandom(); | ||
|
||
List<FixMissionJpaEntity> fixMissionJpaEntities = new ArrayList<>(); | ||
fixMissionJpaEntities.addAll(defaultMissions); | ||
fixMissionJpaEntities.addAll(todayMissions); | ||
|
||
super.setCurrentStepExecution(chunkContext.getStepContext().getStepExecution()); | ||
super.putDataToStepExecutionContext(MISSIONS_PROMOTION_KEY, SharedFixMissions.from(fixMissionJpaEntities)); | ||
|
||
return RepeatStatus.FINISHED; | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
src/main/java/univ/earthbreaker/namu/batch/mission/LoadSpecialMissionTasklet.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package univ.earthbreaker.namu.batch.mission; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import org.jetbrains.annotations.NotNull; | ||
import org.springframework.batch.core.StepContribution; | ||
import org.springframework.batch.core.configuration.annotation.StepScope; | ||
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 univ.earthbreaker.namu.database.core.mission.FixMissionJpaEntity; | ||
import univ.earthbreaker.namu.database.core.mission.FixMissionJpaRepository; | ||
|
||
@Component | ||
@StepScope | ||
public class LoadSpecialMissionTasklet extends AbstractExecutionContextManager<SharedFixMissions> implements Tasklet { | ||
|
||
private final FixMissionJpaRepository fixMissionJpaRepository; | ||
|
||
public LoadSpecialMissionTasklet(FixMissionJpaRepository fixMissionJpaRepository) { | ||
super(); | ||
this.fixMissionJpaRepository = fixMissionJpaRepository; | ||
} | ||
|
||
@Override | ||
public RepeatStatus execute( | ||
@NotNull StepContribution contribution, | ||
@NotNull ChunkContext chunkContext | ||
) { | ||
List<FixMissionJpaEntity> defaultMissions = fixMissionJpaRepository.findDefaultMissionsByRandom(); | ||
List<FixMissionJpaEntity> todayMissions = fixMissionJpaRepository.findTodayMissionsByRandom(); | ||
List<FixMissionJpaEntity> specialMissions = fixMissionJpaRepository.findSpecialMissions(); | ||
|
||
List<FixMissionJpaEntity> fixMissionJpaEntities = new ArrayList<>(); | ||
fixMissionJpaEntities.addAll(defaultMissions); | ||
fixMissionJpaEntities.addAll(todayMissions); | ||
fixMissionJpaEntities.addAll(specialMissions); | ||
|
||
super.setCurrentStepExecution(chunkContext.getStepContext().getStepExecution()); | ||
super.putDataToStepExecutionContext(MISSIONS_PROMOTION_KEY, SharedFixMissions.from(fixMissionJpaEntities)); | ||
|
||
return RepeatStatus.FINISHED; | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
src/main/java/univ/earthbreaker/namu/batch/mission/MemberBatchEntity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package univ.earthbreaker.namu.batch.mission; | ||
|
||
public record MemberBatchEntity( | ||
long memberNo, | ||
String nickname, | ||
int level, | ||
String status | ||
) { | ||
} |
107 changes: 107 additions & 0 deletions
107
src/main/java/univ/earthbreaker/namu/batch/mission/MemberMissionBatchConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package univ.earthbreaker.namu.batch.mission; | ||
|
||
import java.time.LocalDate; | ||
|
||
import org.springframework.batch.core.Job; | ||
import org.springframework.batch.core.Step; | ||
import org.springframework.batch.core.configuration.annotation.JobScope; | ||
import org.springframework.batch.core.configuration.annotation.StepScope; | ||
import org.springframework.batch.core.job.builder.JobBuilder; | ||
import org.springframework.batch.core.job.flow.JobExecutionDecider; | ||
import org.springframework.batch.core.listener.ExecutionContextPromotionListener; | ||
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.batch.item.database.JdbcPagingItemReader; | ||
import org.springframework.beans.factory.annotation.Qualifier; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.transaction.PlatformTransactionManager; | ||
|
||
@Configuration | ||
public class MemberMissionBatchConfig { | ||
|
||
private final JobRepository jobRepository; | ||
private final JobExecutionDecider jobExecutionDecider; | ||
|
||
public MemberMissionBatchConfig( | ||
JobRepository jobRepository, | ||
JobExecutionDecider jobExecutionDecider | ||
) { | ||
this.jobRepository = jobRepository; | ||
this.jobExecutionDecider = jobExecutionDecider; | ||
} | ||
|
||
@Bean("memberMissionBatch") | ||
public Job memberMissionBatch() { | ||
return new JobBuilder("memberMissionBatch", jobRepository) | ||
.start(jobExecutionDecider) | ||
.from(jobExecutionDecider) | ||
.on(LoadMissionStepDecider.NORMAL_STEP) | ||
.to(normalStep(null, null)) | ||
.next(memberMissionReSettingBatchStep(null, null, null, null, null)) | ||
.from(jobExecutionDecider) | ||
.on(LoadMissionStepDecider.SPECIAL_STEP) | ||
.to(specialStep(null, null)) | ||
.next(memberMissionReSettingBatchStep(null, null, null, null, null)) | ||
.end() | ||
.build(); | ||
} | ||
|
||
@Bean("normalStep") | ||
@JobScope | ||
public Step normalStep( | ||
@Qualifier("loadNormalMissionTasklet") Tasklet loadNormalMissionTasklet, | ||
PlatformTransactionManager transactionManager | ||
) { | ||
return new StepBuilder("normalStep", jobRepository) | ||
.tasklet(loadNormalMissionTasklet, transactionManager) | ||
.listener(contextPromotionListener()) | ||
.build(); | ||
} | ||
|
||
@Bean("specialStep") | ||
@JobScope | ||
public Step specialStep( | ||
@Qualifier("loadSpecialMissionTasklet") Tasklet loadSpecialMissionTasklet, | ||
PlatformTransactionManager transactionManager | ||
) { | ||
return new StepBuilder("specialStep", jobRepository) | ||
.tasklet(loadSpecialMissionTasklet, transactionManager) | ||
.listener(contextPromotionListener()) | ||
.build(); | ||
} | ||
|
||
@Bean("memberMissionReSettingBatchStep") | ||
@JobScope | ||
public Step memberMissionReSettingBatchStep( | ||
@Value("#{jobParameters[chunkSize]}") Integer chunkSize, | ||
JdbcPagingItemReader<MemberBatchEntity> memberItemReader, | ||
MemberMissionItemWriter memberMissionItemWriter, | ||
MemberMissionReSettingStepExecutionListener memberMissionReSettingStepExecutionListener, | ||
PlatformTransactionManager transactionManager | ||
) { | ||
return new StepBuilder("memberMissionReSettingBatchStep", jobRepository) | ||
.<MemberBatchEntity, MemberBatchEntity>chunk(chunkSize, transactionManager) | ||
.reader(memberItemReader) | ||
.writer(memberMissionItemWriter) | ||
.listener(memberMissionReSettingStepExecutionListener) | ||
.build(); | ||
} | ||
|
||
@Bean | ||
public ExecutionContextPromotionListener contextPromotionListener() { | ||
ExecutionContextPromotionListener promotionListener = new ExecutionContextPromotionListener(); | ||
promotionListener.setKeys(new String[] {AbstractExecutionContextManager.MISSIONS_PROMOTION_KEY}); | ||
return promotionListener; | ||
} | ||
|
||
@Bean | ||
@JobScope | ||
public MissionDateSupport missionDateSupport( | ||
@Value("#{jobParameters[batchDate]}") LocalDate batchDate | ||
) { | ||
return new MissionDateSupport(batchDate); | ||
} | ||
} |
Oops, something went wrong.