스케줄러란?
특정 작업을 예약된 시점에 수행 가능하도록 스프링 프레임워크에서 제공하는 기능
사옹 예 ) 예약 알림, 이메일 전송, 피드 알림 등등
- 프로젝트 설정

- 스케줄러 사용 시 옵션 정리
fixedRate | 이전 작업 시작 시점 기준, 주기적으로 실행 | fixedRate = 5000 | 작업 시작 후 5초마다 실행 |
fixedDelay | 이전 작업 종료 시점 기준, 주기적으로 실행 | fixedDelay = 5000 | 작업 종료 후 5초 뒤 실행 |
initialDelay | 작업 시작 전 대기 시간 설정 | fixedRate = 5000, initialDelay = 10000 | 10초 대기 후 5초마다 실행 |
cron | Cron 표현식을 사용해 일정 예약 | cron = "0 0/5 * * * *" | 매 5분마다 실행 |
zone | 타임존 설정 (기본은 시스템 타임존 사용) | cron = "0 0 12 * * ?", zone = "America/New_York" | 뉴욕 시간 기준, 매일 오후 12시 실행 |
0 0 12 * * ? | 매일 오후 12시 실행 |
0 0/5 * * * * | 매 5분마다 실행 |
0 0 8-17 * * MON-FRI | 월-금 오전 8시~오후 5시 매 정시 실행 |
0 0 0 1 * ? | 매달 1일 자정 실행 |
0 0 0 * * ? | 매일 자정 실행 |
- @Scheduler VS TaskScheduler 차이
스케줄 방식 | 고정된 주기로 실행 (fixedRate, cron 등) | 동적으로 실행 시간 설정 가능 |
시간 설정 시점 | 애플리케이션 시작 시점 | 런타임에 동적 시간 설정 가능 |
동적 스케줄링 | 미지원 (시간 변경 불가) | 지원 (RequestBody 등 외부 값 반영) |
주기 설정 방법 | @Scheduled(fixedRate = 1000) 등 사용 | taskScheduler.schedule(task, time) 사용 |
비동기 처리 | 별도 설정 필요 (@Async 추가) | 별도 설정 필요(@Async) |
동시 실행 작업 수 | 단일 스레드(순차 실행) | 멀티 스레드 처리 가능 (풀 크기 조절 -> ThreadPoolTaskScheduler 활용) |
사용 사례 | 정해진 시간마다 실행(매일 자정, 매 5초마다) | 예약된 시간 기반 작업(요청 시간 1분 후 실행) |
유연성 | 낮음 — 설정된 시간에만 실행 | 높음 — 동적 시간 설정, 작업 취소 가능 |
주요 어노테이션/메소드 | @Scheduled | taskScheduler.schedule() |
예시 | 고정 시간에 로그 남기기, 매일 데이터 백업 | 사용자가 요청한 시간에 메일 발송, 특정 시간 이후 작업 실행 |
- 비동기 처리 관련 차이
ThreadPoolTaskScheduler(config) + TaskScheduler + @Aysync |
정기적인 작업 + 동시성 제어 + 비동기 처리 | 복잡한 작업 + 대규모 작업 시 아용 |
@Async + TaskScheduler | 동적 작업 예약 + 비동기 처리 | 복잡한 작업 시 사용 |
@Async + @Scheduled | 고정된 주기 작업 (크론 표현식 사용) + 비동기 처리 |
간단한 작업 시 사용 |
여기서 더 깊게 보자!
ThreadPoolTaskScheduler(config) + TaskScheduler + @Async 와 @Async + TaskScheduler 의 차이
구분 | ThreadPoolTaskScheduler(config) + TaskScheduler + @Async | @Async + TaskScheduler |
스레드 관리 | ThreadPoolTaskScheduler를 통해 스레드 풀을 세밀하게 제어 | TaskScheduler로 주기적으로 작업을 예약 가능 |
주기적 작업 실행 | 비동기 처리를 위해 @Async와 함께 사용할 수 있음 | 메소드가 비동기적으로 실행되도록 @Async 사용 |
비동기 실행 | 스레드 관리와 설정 가능 | 간단한 비동기 처리에 유용 |
유연성 | 스레드 관리와 설정 가능 | 간단한 비동기 처리에 유용 |
복잡성 | 설정이 더 복잡하지만, 많은 설정과 스케줄링에 적합 | 간단하고 직관적이나 유연성 부족 |
Spring Boot에서는
기본적으로 @EnableScheduling과 TaskScheduler가 자동으로 설정되므로, @Configuration 클래스를 별도로 작성하지 않아도 된다.
Spring Boot는 ThreadPoolTaskScheduler 구현체를 자동으로 빈으로 등록해주기 때문에
TaskScheduler 인터페이스를 사용하여 주입할 수 있게 해준다.
- Spring Boot는 기본적으로 ThreadPoolTaskScheduler를 사용하여 TaskScheduler 빈을 자동으로 등록
- @Autowired로 TaskScheduler를 주입받으면, Spring이 내부적으로 ThreadPoolTaskScheduler 빈을 주입
- 별도로 @Configuration 파일을 작성할 필요 없이 @Autowired로 바로 TaskScheduler를 사용할 수 있음
나는 간단한 테스트이므로 @Async + @Scheduled, @Async + TaskScheduler 케이스만 실습 진행할 예정
[테스트 프로젝트]

- SchedulerbatchApplication.java
package com.study.schedulerbatch;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
@EnableAsync
public class SchedulerbatchApplication {
public static void main(String[] args) {
SpringApplication.run(SchedulerbatchApplication.class, args);
}
}
- SchedulerController.java
package com.study.schedulerbatch.scheduler.controller;
import com.study.schedulerbatch.scheduler.service.SchedulerService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/scheduler/log")
public class SchedulerController {
private final SchedulerService schedulerService;
@PostMapping("/set")
public ResponseEntity<?> setSchedulerLog(@RequestBody Map<String,Object> logObject) {
try {
// API 호출 시간 기록
LocalDateTime apiTime = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedTime = apiTime.format(formatter);
log.info("[API-REQUEST-TIME (setTaskSchedulerLog)] ▶▶▶▶▶ " + formattedTime);
// @Async + @Scheduled
// @Scheduled 어노테이션이 붙은 메소드에는 파라미터를 사용할 수 없다고 한다ㅠㅜ
// @Scheduled 어노테이션이 붙은 메소드는
// schedulerService.setSchedulerLog();
// @Async + taskScheduler
schedulerService.setTaskSchedulerLog(logObject);
return new ResponseEntity<Map<String,?>>(logObject, HttpStatus.CREATED);
} catch (Exception e) {
log.error(e.getLocalizedMessage());
return new ResponseEntity<String>(e.getLocalizedMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
- SchedulerService.java
package com.study.schedulerbatch.scheduler.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RequiredArgsConstructor
@Service
public class SchedulerService {
private final TaskScheduler taskScheduler;
/**
* scheduler 를 이용한 API 호출 시점 + 50초 뒤 log 기록하는 메소드
* @param
* @return logObject
*/
// @Scheduled 옵션들 정리 *****************************************************************************
// [ 바로 실행되게끔 설정하는 경우 (서버 시작 시 바로 실행) ] => 무한 실행 위험!! - 사용 X - 정리만!!
// 1. fixedRate = 0 : 서버 시작 시 바로 실행 후, 이후 주기에 맞게 실행
// @Scheduled(fixedRate = 0) // 바로 실행되고 이후 주기마다 실행
// 2. cron 표현식에서 "0"으로 시작하는 경우
// - 예: "0 * * * * ?" : 매분 0초에 실행되며, 서버 시작 후 첫 실행도 바로 일어남
// @Scheduled(cron = "0 * * * * ?") // 매분 0초에 실행
// 3. initialDelay = 0, fixedDelay = 0 : 서버 시작 후 즉시 실행 =
// @Scheduled(initialDelay = 0, fixedDelay = 0) // 서버 시작 후 즉시 실행
// [ 주기적 실행 옵션 경우 ]
// 1. fixedRate: 호출 시 첫 실행은 바로 발생하고, 이전 작업의 "시작 시간" 기준 - 일정 시간 간격으로 반복 실행
// @Scheduled(fixedRate = 1000) // 이전 작업 시작 후 1초 마다 반복 실행
// 2. fixedDelay: 호출 시 첫 실행은 바로 발생하고, 이전 작업의 "종료 시간" 기준, 지정된 시간 간격으로 반복 실행
// @Scheduled(fixedDelay = 1000) // 이전 작업 완료 후 1초 마다 반복 실행
// 3. initialDelay: 지정된 시간 후에 첫 번째 실행, 이후에는 fixedRate 또는 fixedDelay에 따라 실행
// @Scheduled(initialDelay = 5000, fixedRate = 1000) // 5초 후 첫 실행, 그 후 매 1초마다 실행
// 4. cron: cron 표현식을 사용한 복잡한 스케줄링 (초단위까지 조정 가능)
// @Scheduled(cron = "0 0/1 * * * ?") // 매 1분마다 실행
// ***************************************************************************************************
// api 호출 후 바로 실행 + 1분 마다 다시 실행
@Scheduled(fixedDelay = 1000*60)
@Async
// public void setSchedulerLog(Map<String,Object> logObject) {
public void setSchedulerLog() {
LocalDateTime apiTime = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedTime = apiTime.format(formatter);
String date = formattedTime;
String content = "@Schduled 어노테이션이 붙은 메소드에는 파라미터를 사용할 수 없다고 한다 ㅠㅠ";
Map<String,Object> logObject = new HashMap<>();
logObject.put("date",date);
logObject.put("content",content);
log.info("[setSchedulerLog]");
log.info(
"date : " + date
+ " , content : " + content
+ " , log object : " + logObject
);
}
/**
* @Async + taskscheduler 를 이용한 API request Body 타겟 시간 + 1분 뒤 log 기록하는 메소드
* @param logObject
* @return logObject
*/
public void setTaskSchedulerLog(Map<String,Object> logObject) {
String dateStr = (String) logObject.get("date"); // 입력받은 시간
LocalDateTime dateTime = LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
LocalDateTime targetDate = dateTime.plusMinutes(1); // 입력받은 시간 + 1분 계산
String content = (String) logObject.get("content");
// 1분 후 시간을 Instant로 변환 (TaskScheduler는 Instant 객체를 사용)
// Instant는 절대적인 시점을 나타내므로, LocalDateTime을 Instant로 변환하여 사용
Instant scheduledTime = targetDate.atZone(ZoneId.systemDefault()).toInstant();
log.info("[setTaskSchedulerLog]");
log.info("setTaskSchedulerLog - scheduledTime : " + targetDate);
// 정해진 시간에 한 번만 실행시키는 경우
taskScheduler.schedule(() -> {
asyncTaskSchedulerLog(content);
}, scheduledTime);
// api 호출 시 첫 실행시키고, 1분마다 반복실행 시키는 경우
/*
taskScheduler.scheduleAtFixedRate() -> {
asyncTaskSchedulerLog(content);
}, 1000 * 60);
*/
// 첫 실행자체를 정해진 시간에 실행시키고 그 이후부터 1분 마다 반복실행 시키는 경우
/*
taskScheduler.schedule(() -> {
asyncTaskSchedulerLog(content);
}, Date.from(firstExecutionTime.atZone(scheduledTime, 1000 * 60)); // 매개변수 (실행로직, Date.from(첫 실행 시작 시점, 이후 반복 주기))
*/
}
@Async
public void asyncTaskSchedulerLog(String content) {
log.info(
"setTaskSchedulerLog - runTime : " + LocalDateTime.now()
+ " , content : " + content
);
}
}
[ 결과 ]
025-03-09T19:08:52.336+09:00 INFO 11196 --- [schedulerbatch] [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2025-03-09T19:08:52.340+09:00 INFO 11196 --- [schedulerbatch] [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2025-03-09T19:08:52.409+09:00 WARN 11196 --- [schedulerbatch] [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2025-03-09T19:08:52.739+09:00 INFO 11196 --- [schedulerbatch] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2025-03-09T19:08:52.747+09:00 INFO 11196 --- [schedulerbatch] [ main] c.s.s.SchedulerbatchApplication : Started SchedulerbatchApplication in 2.071 seconds (process running for 2.435)
2025-03-09T19:08:52.749+09:00 INFO 11196 --- [schedulerbatch] [ main] o.s.b.a.b.JobLauncherApplicationRunner : Running default command line with: []
2025-03-09T19:08:52.752+09:00 INFO 11196 --- [schedulerbatch] [ task-1] c.s.s.s.service.SchedulerService : [setSchedulerLog]
2025-03-09T19:08:52.752+09:00 INFO 11196 --- [schedulerbatch] [ task-1] c.s.s.s.service.SchedulerService : date : 2025-03-09 19:08:52 , content : @Schduled 어노테이션이 붙은 메소드에는 파라미터를 사용할 수 없다고 한다 ㅠㅠ , log object : {date=2025-03-09 19:08:52, content=@Schduled 어노테이션이 붙은 메소드에는 파라미터를 사용할 수 없다고 한다 ㅠㅠ}
2025-03-09T19:08:57.892+09:00 INFO 11196 --- [schedulerbatch] [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-03-09T19:08:57.893+09:00 INFO 11196 --- [schedulerbatch] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2025-03-09T19:08:57.893+09:00 INFO 11196 --- [schedulerbatch] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 0 ms
2025-03-09T19:08:57.976+09:00 INFO 11196 --- [schedulerbatch] [nio-8080-exec-1] c.s.s.s.controller.SchedulerController : [API-REQUEST-TIME (setTaskSchedulerLog)] ▶▶▶▶▶ 2025-03-09 19:08:57
2025-03-09T19:08:57.976+09:00 INFO 11196 --- [schedulerbatch] [nio-8080-exec-1] c.s.s.s.service.SchedulerService : [setTaskSchedulerLog]
2025-03-09T19:08:57.977+09:00 INFO 11196 --- [schedulerbatch] [nio-8080-exec-1] c.s.s.s.service.SchedulerService : setTaskSchedulerLog - scheduledTime : 2025-03-09T19:11
2025-03-09T19:09:52.761+09:00 INFO 11196 --- [schedulerbatch] [ task-2] c.s.s.s.service.SchedulerService : [setSchedulerLog]
2025-03-09T19:09:52.761+09:00 INFO 11196 --- [schedulerbatch] [ task-2] c.s.s.s.service.SchedulerService : date : 2025-03-09 19:09:52 , content : @Schduled 어노테이션이 붙은 메소드에는 파라미터를 사용할 수 없다고 한다 ㅠㅠ , log object : {date=2025-03-09 19:09:52, content=@Schduled 어노테이션이 붙은 메소드에는 파라미터를 사용할 수 없다고 한다 ㅠㅠ}
2025-03-09T19:10:52.765+09:00 INFO 11196 --- [schedulerbatch] [ task-3] c.s.s.s.service.SchedulerService : [setSchedulerLog]
2025-03-09T19:10:52.765+09:00 INFO 11196 --- [schedulerbatch] [ task-3] c.s.s.s.service.SchedulerService : date : 2025-03-09 19:10:52 , content : @Schduled 어노테이션이 붙은 메소드에는 파라미터를 사용할 수 없다고 한다 ㅠㅠ , log object : {date=2025-03-09 19:10:52, content=@Schduled 어노테이션이 붙은 메소드에는 파라미터를 사용할 수 없다고 한다 ㅠㅠ}
2025-03-09T19:11:00.006+09:00 INFO 11196 --- [schedulerbatch] [ scheduling-1] c.s.s.s.service.SchedulerService : setTaskSchedulerLog - runTime : 2025-03-09T19:11:00.006481500 , content : 스케줄러 테스트
2025-03-09T19:11:52.779+09:00 INFO 11196 --- [schedulerbatch] [ task-4] c.s.s.s.service.SchedulerService : [setSchedulerLog]
2025-03-09T19:11:52.779+09:00 INFO 11196 --- [schedulerbatch] [ task-4] c.s.s.s.service.SchedulerService : date : 2025-03-09 19:11:52 , content : @Schduled 어노테이션이 붙은 메소드에는 파라미터를 사용할 수 없다고 한다 ㅠㅠ , log object : {date=2025-03-09 19:11:52, content=@Schduled 어노테이션이 붙은 메소드에는 파라미터를 사용할 수 없다고 한다 ㅠㅠ}
2025-03-09T19:12:48.608+09:00 INFO 11196 --- [schedulerbatch] [ionShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown : Commencing graceful shutdown. Waiting for active requests to complete
2025-03-09T19:12:48.612+09:00 INFO 11196 --- [schedulerbatch] [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown : Graceful shutdown complete
2025-03-09T19:12:48.615+09:00 INFO 11196 --- [schedulerbatch] [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2025-03-09T19:12:48.617+09:00 INFO 11196 --- [schedulerbatch] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariCP - Shutdown initiated...
※ 정리
@Scheduled
(1) 한 번만 실행하는 경우는 없으며, 특정 주기마다 반복 실행 시키는 것만 가능
(2) 이 어노테이션으로 실행시킬 메소드에는 파라미터를 사용할 수 없음
(3) BootApplication 실행 시 (서버 띄울 때) 자동 실행
(4) 고정된 시점의 작업
TaskScheduler
(1) 한 번만 실행하는 경우 + 반복 실행시키는 경우도 가능
(2) 메소드 안에서 TaskScheduler 객체의 메소드를 실행시키는 것이기 때문에 당연히 파라미터 사용 가능
(3) API 요청 시 실행 됨
(4) 동적으로 request 받은 시점의 작업
(5) 스레드 관리가 가능하여 대규모 어플리케이션 및 작업 시 유리 -> ThreadPoolTaskScheduler - config
└
package com.example.scheduler.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.TaskScheduler;
@Configuration
public class SchedulerConfig {
/**
* ThreadPoolTaskScheduler를 Bean으로 등록하여 스케줄링 작업을 관리
* - 대규모 작업에서 스레드 수를 효율적으로 관리
* - ThreadPoolTaskScheduler는 스레드 풀 크기, 이름 등을 설정할 수 있어 대규모 작업 시 유용
*
* @return TaskScheduler Bean
*/
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
// 스레드 풀의 크기 설정 (최대 10개의 스레드를 사용할 수 있도록 설정)
scheduler.setPoolSize(10); // 스레드 풀 크기 설정
// 스레드 이름에 접두사를 설정하여 어떤 스레드가 작업을 수행 중인지 확인 가능
scheduler.setThreadNamePrefix("scheduler-task-");
// 스케줄러를 초기화
scheduler.initialize();
// 초기화된 scheduler를 반환
return scheduler;
}
}
'API' 카테고리의 다른 글
Spring AOP 설명(Annotaion, Class) 및 예제 (1) | 2025.01.17 |
---|---|
#2 TEST CODE 작성 : Todo API 단위테스트, 통합테스트 (0) | 2025.01.02 |
#1 TEST CODE 작성 : Todo API 만들기 (1) | 2024.12.14 |
[spring webSocket - 스프링 웹소켓] : 간이 채팅방 구현 (4) | 2024.09.16 |
WebSocketHandler - 오버라이드 메소드, override method 정리 (0) | 2024.09.16 |