앞 포스팅에서 학습한 spring Batch를 프로젝트에 적용해보고자 한다.
라이브러리 등록
implementation 'org.springframework.boot:spring-boot-starter-batch'
배치 5.0이론
5.0 이후
@EnableBatchProcessing 과
private final JobLauncher jobLauncher;
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
선언없이 사용한다
→ JobBuilder, StepBuilder 로 대체
JobLauncher jobLauncher; 는 해당 배치를 호출하는 부분에서 사용
@EnableBatchProcessing or extends DefaultBatchConfiguration
를 사용하게 되면 Spring Boot Auto Configure에서 자동으로 메타 데이터를 만들어 주지 않는다 ! 기본 설정을 막아버린다.
실제 코드
package com.ssafy.today.global.batch;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
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.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
@EnableBatchProcessing
@Slf4j
public class BatchConfig {
@Bean
public Job simpleJob1(JobRepository jobRepository, Step simpleStep1) {
return new JobBuilder("simpleJob", jobRepository)
.start(simpleStep1)
.build();
}
@Bean
public Step simpleStep1(JobRepository jobRepository, Tasklet testTasklet, PlatformTransactionManager platformTransactionManager){
return new StepBuilder("simpleStep1", jobRepository)
.tasklet(testTasklet, platformTransactionManager).build();
}
@Bean
public Tasklet testTasklet(){
return ((contribution, chunkContext) -> {
log.info("step1 지납니다~");
return RepeatStatus.FINISHED;
});
}
}
이 스프링 배치 구성 코드는 간단한 작업(Job)과 단계(Step)를 생성하고, Tasklet을 사용해 특정 작업을 수행합니다. 여기서는 세 가지 주요 컴포넌트를 설정하고 있습니다:
Job: JobBuilder를 사용하여 simpleJob이라는 이름의 Job을 생성하고, 이 Job이 simpleStep1 단계를 수행하도록 설정합니다.
Step: StepBuilder를 사용하여 simpleStep1이라는 Step을 생성하고, 이 Step에서 testTasklet Tasklet을 실행하도록 구성합니다. 이 때, 트랜잭션 관리는 PlatformTransactionManager를 통해 이루어집니다.
Tasklet: Tasklet은 실제 비즈니스 로직을 수행하는 단위로, 여기서는 로그를 출력하는 간단한 로직을 포함하고 있습니다. Tasklet은 RepeatStatus.FINISHED를 반환하여 작업의 완료를 알립니다.
package com.ssafy.today.global.scheduler;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
@RequiredArgsConstructor
public class SchedulerConfig {
private final JobLauncher jobLauncher;
private final Job job;
@Scheduled(cron = "0 0 19 * * ?")
public void findYetUser() {
try {
// JobParameters 생성
JobParameters params = new JobParametersBuilder()
.addLong("time", System.currentTimeMillis(), true)
.toJobParameters();
// Job 실행
jobLauncher.run(job, params);
log.info("스케줄러 실행한다 ~");
} catch (Exception e) {
log.error("배치 작업 실행 중 에러 발생", e);
}
}
}
해당 스케줄러에서 JobLauncher를 가져와 정의한 Job 을 실행한다 !
그렇다면 Job 이 여러개라면?
@Configuration
@EnableBatchProcessing
public class BatchConfig {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
public Job jobOne(Step stepOne) {
return jobBuilderFactory.get("jobOne")
.start(stepOne)
.build();
}
@Bean
public Job jobTwo(Step stepTwo) {
return jobBuilderFactory.get("jobTwo")
.start(stepTwo)
.build();
}
}
두개의 잡을 생성해봤다.@Component
public class SchedulerConfig {
@Autowired
private JobLauncher jobLauncher;
@Autowired
@Qualifier("jobOne")
private Job jobOne;
@Autowired
@Qualifier("jobTwo")
private Job jobTwo;
@Scheduled(cron = "0 0 19 * * ?") // 매일 19시에 JobOne 실행
public void runJobOne() {
executeJob(jobOne);
}
@Scheduled(cron = "0 30 19 * * ?") // 매일 19시 30분에 JobTwo 실행
public void runJobTwo() {
executeJob(jobTwo);
}
private void executeJob(Job job) {
try {
JobParameters params = new JobParametersBuilder()
.addLong("time", System.currentTimeMillis(), true)
.toJobParameters();
jobLauncher.run(job, params);
} catch (Exception e) {
log.error("Error executing job", e);
}
}
}
@Qualifier 를 사용해 Job 이름을 명시해준다면, 원하는 Job을 가져와 사용할수 있다.
이후 FCM에 해당 Batch process를 직접 적용했다.
public class BatchConfig {
private final MemberRepository memberRepository;
private final PushMessageService pushMessageService;
@Bean
public Job Job1(JobRepository jobRepository, Step Step1) {
return new JobBuilder("Job1", jobRepository)
.start(Step1)
.build();
}
@Bean
public Step Step1(JobRepository jobRepository, Tasklet Tasklet, PlatformTransactionManager platformTransactionManager){
return new StepBuilder("Step1", jobRepository)
.tasklet(Tasklet, platformTransactionManager).build();
}
@Bean
public Tasklet Tasklet() {
return ((contribution, chunkContext) -> {
log.info("Job1 시작한다 ~");
// 금일 일기를 작성하지 않은 멤버들을 뽑아온다.
List<Member> members = memberRepository.findYetWrite();
// 알림을 호출한다.
List<PushMessageRequest> pushMessageRequestList = new ArrayList<>();
int cnt = 0;
for (Member member : members) {
cnt++;
pushMessageRequestList.add(
PushMessageRequest.builder()
.token(member.getDeviceToken())
.title("당일의 일기는 아직 인가요?")
.body("오늘의 일기를 작성해보세요")
.diaryId(0L)
.build()
);
// 100 명씩 끊어서 보낸다
if ((cnt % 100) == 0) {
cnt = 0;
pushMessageService.sendPushMessageBulk(pushMessageRequestList);
pushMessageRequestList = new ArrayList<>();
Thread.sleep(10000);
}
}
// 남은 member 들의 알림을 보낸다
pushMessageService.sendPushMessageBulk(pushMessageRequestList);
return RepeatStatus.FINISHED;
});
}
}
FCM 알림을 보낼때 최대 100개의 알림만 처리가 가능하다고 한다.
때문에 가져온 사용자의 정보를 100개씩 끊어서 FireBase에 요청을 보내도록 처리했다.

이후 일기를 작성하지 않았고, 오후 8시에 해당 알림을 받는것을 확인했다.