조건 | 설명 | 파일 만료시간 정보 삭제 기능 | |
---|---|---|---|
1 | 대용량 데이터 | 대량의 데이터를 가져오거나, 전달하거나, 계산하는 등의 처리를 할 수 있어야 한다. | 대량의 데이터를 삭제한다. |
2 | 자동화 | 심각한 문제 해결을 제외하고 사용자 개입 없이 실행되어야 한다. | Scheduler를 이용하여 일정 시간마다 자동으로 실행하도록 한다. |
3 | 견고성 | 잘못된 데이터를 충돌 및 중단 없이 처리할 수 있어야 한다. | 현재 날짜보다 이전인 데이터만 지워주면 되기 때문에 ‘잘못된 데이터’ 가 존재할 수 없다. |
4 | 신뢰성 | 로깅 및 추적을 통해 무엇이 잘못되었는 지를 추적할 수 있어야 한다. | — |
5 | 성능 | 지정한 시간 안에 처리를 완료하거나 동시에 실행되는 다른 어플을 방해하지 않도록 수행되어야 한다. | — |
배치와 스케줄러는 비교 대상이 아님!
만약 단순한 Scheduling에 따른 작업이 필요하시다면 단연코 Spring Scheduler를 추천합니다. Spring Quartz는 좀 더 Scheduling의 세밀한 제어가 필요할 때, 그리고 만들어야하는 Scheduling 서비스 노드가 멀티이기 때문에 클러스터링이 필요할 때 여러분이 만들고자 하는 서비스에 도움이 될 것입니다.
- 출처
[[Spring] Scheduler 어떤걸 사용해야 할까 ? - Spring Scheduler와 Spring Quartz](https://sabarada.tistory.com/113)
@EnableScheduling
어노테이션 붙인다.@EnableScheduling
@SpringBootApplication
public class Application() {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Scheduled
어노테이션을 붙여주면 된다. 스케줄링을 할 메서드는 아래 두 개의 조건을 만족해야 한다public class Scheculer() {
@Scheduled(fixedDelay = 1000) // scheduler 끝나는 시간 기준으로 1000 간격으로 실행
public void scheduleFixedDelayTask() {
System.out.println(
"Fixed delay task - " + System.currentTimeMillis() / 1000);
}
@Scheduled(fixedRate = 1000) // scheduler 시작하는 시간 기준으로 1000 간격으로 실행
public void scheduleFixedRateTask() {
System.out.println(
"Fixed rate task - " + System.currentTimeMillis() / 1000);
}
@Scheduled(cron = "0 15 10 15 * ?") // cron에 따라 실행
public void scheduleTaskUsingCronExpression() {
long now = System.currentTimeMillis() / 1000;
System.out.println(
"schedule tasks using cron jobs - " + now);
}
@Scheduled(cron = "0 15 10 15 * ?", zone = "Europe/Paris") // cron에 TimeZone 설정 추가
}
@EnableAsync
어노테이션을 이용implementation "org.springframework.boot:spring-boot-starter-quartz”
@Configuration
public class CollectJob implements Job {
private final CollectService collectService;
public CollectJob(CollectService collectService) {
this.collectService = collectService;
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("[Collect] collect Job Start...");
}
}
@Bean
public JobDetail tistoryJobDetail() {
return JobBuilder.newJob().ofType(CollectJob.class)
.storeDurably()
.withIdentity("job_detail")
.withDescription("Invoke Tistory Job service...")
.build();
}
@Bean
public Trigger tistoryTrigger(@Qualifier("tistoryJobDetail") JobDetail job) {
return TriggerBuilder.newTrigger().forJob(job)
.withIdentity("tistory_job_trigger")
.withSchedule(cronSchedule("0 0 9 * * ?")
.inTimeZone(TimeZone.getTimeZone("Asia/Seoul")))
.build();
}
application.yml
spring:
quartz:
job-store-type: memory
build.gradle에 다음 코드를 추가한다.
build.gradle
implementation("org.springframework.boot:spring-boot-starter-batch")
implementation("org.springframework.boot:spring-boot-starter-quartz")
testImplementation 'org.springframework.batch:spring-batch-test'
아래 두줄을 application.properties에 추가한다.
#spring boot batch + scheduler
spring.batch.initialize-schema: always # batch 스키마 자동 생성
spring.batch.job.enabled=false # 시작과 동시에 실행되는건 방지
intellij 기준으로 'File>Invalidate Caches..' 로 캐시를 지우고 재시작시킨다.
main 함수가 있는 클래스에 @EnableScheduling과 @EnableBatchProcessing을 붙여준다.
MeetingDocumentApplication.java
package com.tmax.meeting.document;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@EnableScheduling
@EnableBatchProcessing
@SpringBootApplication
public class MeetingDocumentApplication {
public static void main(String[] args) {
SpringApplication.run(MeetingDocumentApplication.class, args);
}
}
batch 파일과 scheduler파일을 만든다.
batch 파일에서 수행하고자 하는 Job을 step으로 구성한다.
config / BatchConfig.java
package com.tmax.meeting.document.config;
import com.tmax.meeting.document.model.Document;
import com.tmax.meeting.document.repository.DocumentRepository;
import com.tmax.meeting.document.service.DocService;
import java.util.List;
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.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDateTime;
@Slf4j
@Configuration
@EnableBatchProcessing
public class BatchConfig {
@Autowired
public JobBuilderFactory jobBuilderFactory;
@Autowired
public StepBuilderFactory stepBuilderFactory;
@Autowired
private DocumentRepository documentRepository;
@Autowired
private DocService docService;
@Bean
public Job job() {
Job job = jobBuilderFactory.get("job")
.start(step())
.build();
return job;
}
@Bean
public Step step() {
return stepBuilderFactory.get("step")
.tasklet((contribution, chunkContext) -> {
log.info("Step!");
// 업데이트 시각이 일주일 이전인 문서 목록을 가져옴
// 1. 네이티브 쿼리 사용
List<Document> limitedDocuments = documentRepository.selectLimitedDocument();
// 2. JPQL 사용
// LocalDateTime now = LocalDateTime.now();
// LocalDateTime aWeekAgo = now.minusDays(7);
// List<Document> limitedDocuments = documentRepository.findByUpdateAtLessThan(aWeekAgo)
if (limitedDocuments.size() > 0 && limitedDocuments != null) {
for (Document document : limitedDocuments) {
// deleteDocument는 document_id를 받아 서버와 db에서 문서 삭제를 구현하는 서비스
docService.deleteDocument(document.getDocumentId());
}
}
return RepeatStatus.FINISHED;
})
.build();
}
}
업데이트 시각이 일주일 이전인 문서 목록을 가져오는 쿼리문은 네이티브 쿼리, JPA로 구현해보았는데
네이티브 쿼리 사용
respository /DocumentRespository.java
@Repository
public interface DocumentRepository extends JpaRepository<Document, Long>{
@Query(value = "SELECT * FROM document WHERE DATE(updated_at) < DATE_SUB(NOW(), INTERVAL 7 DAY)", nativeQuery = true)
List<Document> selectLimitedDocument();
}
JPA로 작성하려고 보니 날짜계산이 들어가서 어떻게 할까 하다가 Querydsl 쓰기 귀찮아서... ㅎ 네이티브쿼리로 한것이다.
날짜 계산에 DATE_SUB( )을 사용한다는거!
JPQL로 하려면 LocalDateTime을 사용해서 7일전 날짜를 변수로 하여 이보다 빠른 update_at 값을 가지는 것을 가져오게 한다.
config / BatchConfig.java 중..
import java.time.LocalDateTime;
LocalDateTime now = LocalDateTime.now();
LocalDateTime aWeekAgo = now.minusDays(7);
List<Document> limitedDocuments = documentRepository.findByUpdatedAtLessThan(aWeekAgo)
이렇게 7일전 날짜변수를 넘기면..> respository /DocumentRespository.java
import java.time.LocalDateTime;
@Repository
public interface DocumentRepository extends JpaRepository<Document, Long>{
List<Document> findByUpdatedAtLessThan(LocalDateTime aWeekAgo);
}
위 처럼 받아서 jpa를 수행한다.
batch를 수행하기 위한 scheduler를 scheduler 파일에 구성한다. (매일 오전 9시에 실행되도록)
config / BatchScheduler.java
package com.tmax.meeting.document.config;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.JobParameter;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class BatchScheduler {
@Autowired
private JobLauncher jobLauncher;
@Autowired
private BatchConfig batchConfig;
@Scheduled(cron = "0 0 9 * * *")
public void runJob() {
// job parameter 설정
Map<String, JobParameter> confMap = new HashMap<>();
confMap.put("time", new JobParameter(System.currentTimeMillis()));
JobParameters jobParameters = new JobParameters(confMap);
try {
jobLauncher.run(batchConfig.job(), jobParameters);
} catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException
| JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) {
log.error(e.getMessage());
}
}
}
위에서 jobLauncher.run()메소드는 첫번째 파라미터로 Job, 두번째 파라미터로 Job Parameter를 받고 있다.
Job Parameter의 역할은 반복해서 실행되는 Job의 유일한 ID이다.
0000-00-00 01:30:43.702 INFO 16963 --- [ scheduling-1] o.s.batch.core.step.AbstractStep : Step: [tutorialStep] executed in 14ms
... 생략 ...
0000-00-00 01:30:48.748 INFO 16963 --- [ scheduling-1] o.s.batch.core.job.SimpleStepHandler : Step already complete or not restartable, so no action to execute: StepExecution: id=1, version=3, name=tutorialStep, status=COMPLETED, exitStatus=COMPLETED, readCount=0, filterCount=0, writeCount=0 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=1, rollbackCount=0, exitDescription=
위처럼, Job Parameter에 동일한 값이 세팅되도록 하면 두번째부터 실행되는 Job의 Step은 실행되지 않는다.
[스프링] batch + scheduler로 주기적인 파일 삭제 구현
[Spring] Scheduler 어떤걸 사용해야 할까 ? - Spring Scheduler와 Spring Quartz
Quartz Scheduler vs. Spring Scheduler
[Spring] Quartz 란 ( Quartz Scheduler )
Quartz Clustering in Spring MVC