Spring Quartz 사용

Chooooo·2023년 12월 18일
0

인턴생활

목록 보기
18/19
post-thumbnail

스케쥴러 Quartz를 사용하는 이유?

간단하게 스프링에서 제공해주는 @Scheduled를 통해서 사용하려고 했는데.. 왜 Quartz를 사용하는지 궁금해졌다.

아마 이유는...

장점

  • In-memory Job Scheduler
  • DB를 이용한 Clustering

만약 여러 개의 배치서버가 도는 경우 한쪽 서버에서만 특정 Job이 수행되도록 해줘야 한다. 물론 개발자가 이러한 기능을 개발할 수 있겠지만 Quartz는 이러한 기능을 DB를 통한 Clustering 방식으로 손쉽게 사용할 수 있도록 지원해주고 있다.

또한 필요에 따르면 DB를 이용하지 않고 Memory에서 가능하도록 하는 기능도 제공해주고 있다.

  • 그만큼 실제 개발자가 오랜 시간이 걸리는 기능들을 Quartz를 통해 적은 시간으로 구성 가능하게 만드는 것을 도와주고 있다.

특히

복잡한 스케줄일 때는 Spring Quartz를 통해 작업하는 것이 좋다고 한다.

Scheduler

스케줄을 관리하는 객체

JobDetail

Job을 실행시키기 위한 필요 정보를 담고 있다. JobDetail을 만들기 위해 필요한 것은 Job의 이름, Job의 파라미터인 JobDataMap, Job을 언제 동작시킬지에 대한 정보 Trigger가 필요하다.

Trigger

Job을 언제 동작시킬지, 몇번 반복시킬지에 대한 정보를 담고 있다. 날짜 정보는 흔히 사용하는 Cron 형식으로 구성 가능하다.

JobDataMap

Job을 동작시키는데 필요한 parameter를 담고 있다. 흔히 날짜, 횟수에 대한 정보를 담아서 동작시킨다.

JobListener

Job이 실행될 때의 이벤트 정보를 담고 있다. Job이 실행되기 전, 중단, 실행 완료 후 각종 이벤트를 넣어줄 수 있다.

TriggerListener

Trigger가 실행될 때의 이벤트 정보를 담고 있다.

[Quartz docs]

😎 스케쥴러 구현

스프링부트와 Quartz 스케줄러 통합하기

스프링부트 애플리케이션에서 복잡한 스케줄링 요구사항을 처리해야 할 때 Quartz 스케줄러는 강력하고 유연한 해결책을 제공한다.

이번에는 인턴에서 진행하는 프로젝트 기능 구현 중 Quartz 스케줄러를 스프링부트와 통합하는 방법을 단계별로 설명하고자 한다.

Quartz 스케쥴러란?

이전 포스터에서도 Quartz에 대해서 정리했지만, 나 역시 한번 더 상기시키고자 공부 한번 더 하려고 한다.

🐳 Quartz는 Java 애플리케이션을 위한 강력한 스케줄링 라이브러리이다. 복잡한 스케줄링 요구사항을 지원하며, 한 번 설정하면 특정 시간에 반복적으로 작업을 실행할 수 있다.

왜 Quartz 스케줄러를 사용하는가?

  • 복잡한 스케줄링 : Quartz는 복잡한 스케줄링 요구사항을 쉽게 구현할 수 있도록 한다.
  • 지속성과 클러스터링 지원 : Quartz는 지속성을 지원하며, 클러스터 환경에서의 스케줄링을 위한 기능을 제공한다.
  • 유연한 트리거 옵션 : 다양한 유형의 트리거를 제공하여, 정확한 타이밍으로 작업을 실행할 수 있다.

내가 하고자 한 기능

😓 임시 채팅방에서 마지막 채팅으로부터 48시간 동안 채팅이 없다면, 해당 임시 채팅방을 삭제할 것이다. 이 작업(Job)을 매 시간마다 돌아가도록 할 것이다.

일단 해당 기능은 스프링의 내장된 @Scheduled 어노테이션으로 충분하기에
처음에는 @Scheduled 어노테이션으로 해당 스케줄러를 구현하였다.

  • 의존성 추가

나는 maven 프로젝트이기에 pom.xml에 Quartz 의존성을 추가하였다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

import itDia.chat.repository.user.ChatRoomRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Component
@RequiredArgsConstructor
public class ScheduledTasks {

    private final ChatRoomRepository chatRoomRepository;

    @Scheduled(fixedRate = 3600000) // 3600000ms = 1시간
//    @Scheduled(fixedRate = 60000) // 60000ms = 1분
    public void deleteInactiveChatRooms() {
        LocalDateTime cutoffDateTime = LocalDateTime.now().minusHours(48);
        chatRoomRepository.deleteChatRoomsInactiveFor48Hours(cutoffDateTime);
    }
}

@Modifying
    @Transactional
    @Query("delete from ChatRoom cr " +
            "where cr.id in (" +
            "    select cr.id from ChatRoom cr " +
            "    left join RealChatRoom rcr on cr.tradeNo = rcr.roomTitle " +
            "    left join ChatMessage cm on rcr.roomIndex = cm.roomIndex " +
            "    group by cr.id " +
            "    having max(cm.regDate) < :cutoffDateTime or max(cm.regDate) is null" +
            ")")
    void deleteChatRoomsInactiveFor48Hours(@Param("cutoffDateTime") LocalDateTime cutoffDateTime);

위 스케줄러를 이제 Quartz 스케줄러를 사용해서 진행해볼 것이다.

1. Quartz 구성

Quartz Job, Trigger를 정의한다.

ChatRoomCleanupQuartzJob 작업을 사용하여 48시간 동안 활동이 없는 거래 전 임시 채팅방을 삭제한다.

import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import static org.quartz.SimpleScheduleBuilder.simpleSchedule;

@Configuration
public class QuartzSchedulerConfig {

    @Bean
    public JobDetail chatRoomCleanupJobDetail() {
        return JobBuilder.newJob(ChatRoomCleanupQuartzJob.class)
                .withIdentity("chatRoomCleanupJob")
                .storeDurably()  // 작업이 트리거와 연결되지 않아도 저장되어야 함?
                .build();
    }

    @Bean
    public Trigger chatRoomCleanupTrigger(JobDetail jobDetail) {   // 작업이 언제 실행될 지 정의
        return TriggerBuilder.newTrigger()
                .forJob(jobDetail)
                .withIdentity("ChatRoomCleanupTrigger")
                .startNow() // 애플리케이션 시작 시 즉시 작업 실행
                .withSchedule(createCronSchedule())
//                .withSchedule(simpleSchedule().withIntervalInHours(1).repeatForever())
                .build();
    }

    public CronScheduleBuilder createCronSchedule() {
        // 매시간 정각에 실행되는 Cron 스케줄
        return CronScheduleBuilder.cronSchedule("0 * * * * ?");
    }

}

2. 작업 구현

ChatRoomCleanupQuartzJob 클래스에서 실제 삭제 로직을 구현한다. 이 클래스는 Quartz의 Job 인터페이스를 구현한다.

@RequiredArgsConstructor
public class ChatRoomCleanupQuartzJob implements Job {

    private final ChatRoomRepository chatRoomRepository;

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        LocalDateTime cutoffDateTime = LocalDateTime.now().minusDays(20);
        chatRoomRepository.deleteChatRoomsInactiveFor48Hours(cutoffDateTime);
    }
}

3. 스케줄러 자동 시작

스프링 부트에서는 SchedulerFactoryBean을 사용하여 Quartz 스케줄러를 자동으로 시작한다. 별도의 스케줄러 시작 코드는 필요하지 않아!!!!

😊 만약 스케줄러가 여러 개라면 ?

여러 개의 다른 작업(Job)을 관리해야 하는 경우가 발생할 수 있다.
예를 들어, 하나의 작업은 임시 채팅방을 정리하는 반면, 다른 작업은 일정 시간마다 데이터를 백업할 수 있다. 이러한 다양한 요구사항을 충족하기 위해서는 여러 개의 스케줄러를 설정하고 관리할 필요가 있다.

여러 스케줄러 설정하기

Quartz 스케줄러를 사용하여 여러 작업을 설정하는 것은 간단하다. 각 작업에 대해 별도의 JobDetail, Trigger를 정의하면 된다!!!

@Configuration
public class QuartzSchedulerConfig {

    @Bean
    public JobDetail firstJobDetail() {
        return JobBuilder.newJob(FirstJob.class)
                .withIdentity("FirstJob")
                .storeDurably()
                .build();
    }

    @Bean
    public Trigger firstJobTrigger(JobDetail firstJobDetail) {
        return TriggerBuilder.newTrigger()
                .forJob(firstJobDetail)
                .withIdentity("FirstJobTrigger")
                .withSchedule(SimpleScheduleBuilder.repeatHourlyForever())
                .build();
    }

    @Bean
    public JobDetail secondJobDetail() {
        return JobBuilder.newJob(SecondJob.class)
                .withIdentity("SecondJob")
                .storeDurably()
                .build();
    }

    @Bean
    public Trigger secondJobTrigger(JobDetail secondJobDetail) {
        return TriggerBuilder.newTrigger()
                .forJob(secondJobDetail)
                .withIdentity("SecondJobTrigger")
                .withSchedule(CronScheduleBuilder.cronSchedule("0 0 0 * * ?")) // 매일 자정에 실행
                .build();
    }
}

이 설정에서는 두 개의 작업(FirstJob, SecondJob)과 각 작업에 대한 트리거를 정의한다. 첫 번째 작업은 매 시간 반복되고, 두 번째 작업은 매일 자정에 실행된다.

역시 이렇게 각 작업에 대해 JobDetail과 Trigger를 설정해놓으면 스프링부트에서는 SchedulerFactoryBean을 통해 스케줄러가 자동으로 시작된다.

  • 따라서 개별 스케줄러를 수동으로 시작할 필요가 없다. 스프링부트는 정의된 모든 JobDetail과 Trigger를 자동으로 감지하여 스케줄러에 등록하고 스케줄러를 실행한다.

🐳 결론

Quartz 스케줄러는 스프링 부트 애플리케이션에서 복잡한 스케줄링 요구사항을 해결하는데 매우 유용하다. 또한 각 작업은 독립적으로 설정되며, 스프링부트는 이를 자동으로 스케줄러에 등록하여 관리한다.

이 글에서는 Quartz를 사용하여 마지막 채팅으로부터 48시간이 지난 임시 채팅방을 삭제하는 기능을 구현하는 방법을 간단하게 작성해보았다. 이러한 접근 방식은 애플리케이션의 효율성을 높이고, 자원을 절약하는데 크게 기여할 수 있다.

profile
back-end, 지속 성장 가능한 개발자를 향하여

0개의 댓글