스케줄러 (@Scheduled)
📁 적용 코드:
FileCleanupScheduler.java,DemoApplication.java
1. 스케줄러란?
서버가 주기적으로 자동 실행하는 작업. 사용자 요청 없이 서버가 알아서 돌린다.
일반 API 요청:
사용자 클릭 → Controller → Service → 결과 응답
스케줄러:
정해진 시간 도달 → Spring이 자동으로 메서드 호출 → 작업 수행
(사용자 요청 없음)
실무 활용 예시
| 작업 | 주기 | 설명 |
|---|---|---|
| 고아 파일 정리 | 매일 새벽 4시 | 연결 안 된 파일 물리 삭제 (이 프로젝트) |
| 메일 발송 | 매일 오전 9시 | 알림 이메일 일괄 발송 |
| 데이터 백업 | 매일 새벽 2시 | DB 덤프 |
| 캐시 갱신 | 1시간마다 | 인기 게시글 순위 등 |
| 임시 데이터 삭제 | 매주 일요일 | 탈퇴 유예 기간 지난 계정 삭제 |
2. Spring Boot 스케줄러 설정
① @EnableScheduling 선언 (1회)
// DemoApplication.java (이 프로젝트 실제 코드)
@EnableScheduling // ← 이 한 줄로 스케줄러 기능 활성화
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@EnableScheduling이 없으면@Scheduled를 붙여도 아무 일도 안 일어남.
② @Scheduled 메서드 작성
@Component // Bean으로 등록되어야 Spring이 관리 가능
public class MyScheduler {
@Scheduled(cron = "0 0 4 * * *") // 매일 새벽 4시에 실행
public void myTask() {
// 여기에 자동으로 실행할 로직 작성
}
}
규칙
@Scheduled메서드는 반환값 void, 파라미터 없음- Bean으로 등록된 클래스(
@Component,@Service등) 안에 있어야 함 @EnableScheduling이 선언되어 있어야 함
3. @Scheduled 옵션
cron 표현식 (가장 많이 사용)
@Scheduled(cron = "초 분 시 일 월 요일")
| 필드 | 범위 | 설명 |
|---|---|---|
| 초 | 0-59 | |
| 분 | 0-59 | |
| 시 | 0-23 | |
| 일 | 1-31 | |
| 월 | 1-12 | |
| 요일 | 0-7 (0,7=일) 또는 MON-SUN |
| 특수문자 | 의미 | 예시 |
|---|---|---|
* |
매번 | * * * * * * = 매초 |
? |
사용 안 함 (일/요일 중 하나에) | |
/ |
간격 | 0/10 * * * * * = 10초마다 |
- |
범위 | 0 0 9-18 * * * = 9시~18시 매 정각 |
, |
목록 | 0 0 9,18 * * * = 9시, 18시 |
자주 쓰는 cron 예시
@Scheduled(cron = "0 0 4 * * *") // 매일 새벽 4시
@Scheduled(cron = "0 0 0 * * MON") // 매주 월요일 자정
@Scheduled(cron = "0 0/30 * * * *") // 30분마다
@Scheduled(cron = "0 0 9-18 * * MON-FRI") // 평일 9시~18시 매 정각
기타 옵션
// fixedRate: 이전 실행 시작 시점 기준 밀리초 간격
@Scheduled(fixedRate = 60000) // 1분마다 (이전 실행 시작 후 60초)
// fixedDelay: 이전 실행 완료 시점 기준 밀리초 간격
@Scheduled(fixedDelay = 60000) // 이전 실행 끝나고 1분 후
// initialDelay: 서버 시작 후 최초 실행까지 대기 시간
@Scheduled(fixedRate = 60000, initialDelay = 5000) // 서버 시작 5초 후부터 1분마다
| 옵션 | 기준 | 적합한 상황 |
|---|---|---|
cron |
정해진 시각 | “매일 새벽 4시”, “매주 월요일” |
fixedRate |
시작~시작 간격 | “30초마다 상태 체크” (작업이 빨리 끝나는 경우) |
fixedDelay |
종료~시작 간격 | “이전 작업 끝나고 1분 후” (작업 시간이 불확실한 경우) |
4. 이 프로젝트 적용 — FileCleanupScheduler
왜 필요한가?
에디터에서 이미지를 업로드하면 즉시 물리 파일이 저장됨.
하지만 사용자가 글을 등록하지 않고 이탈하면 → 연결 안 된 고아 파일이 남음.
고아 파일이 생기는 시나리오:
1. 에디터에서 이미지 업로드 → 파일 저장 (refId=0, 임시 상태)
→ 글 등록 안 하고 이탈 → refId=0인 파일이 디스크에 남음
2. 글 수정 시 에디터 이미지 삭제 → 화면에서만 제거
→ 서버에는 파일이 남아있고, 글 저장 시 고아 처리(refId=0)
3. 글 삭제 → 파일은 고아 처리(refId=0)만 함
→ 물리 파일은 그대로 남아있음
이런 고아 파일들을 스케줄러가 주기적으로 정리한다.
실제 코드
// FileCleanupScheduler.java
@Component
@RequiredArgsConstructor
public class FileCleanupScheduler {
private final FileService fileService;
// 매일 새벽 4시에 실행
@Scheduled(cron = "0 0 4 * * *")
public void cleanupOrphanFiles() {
// 24시간 이전의 고아 파일만 삭제 (작성 중인 파일이 바로 삭제되지 않도록 유예)
LocalDateTime timeLimit = LocalDateTime.now().minusHours(24);
int count = fileService.deleteOrphanFiles(timeLimit);
}
}
// FileService.deleteOrphanFiles() — 스케줄러에서 호출
@Transactional
public int deleteOrphanFiles(LocalDateTime timeLimit) {
// 1. 고아 파일 조회 (refId=0 + 수정일 < 24시간 전)
List<FileEntity> orphans = fileRepository.findByRefIdAndUpdatedAtBefore(0L, timeLimit);
if (orphans.isEmpty()) return 0;
// 2. 물리 파일 삭제 (디스크에서 삭제)
for (FileEntity file : orphans) {
Path filePath = uploadPath.resolve(file.getStoredFileName());
Files.deleteIfExists(filePath);
}
// 3. DB에서 하드 삭제
fileRepository.deleteAll(orphans);
return orphans.size();
}
동작 흐름
매일 새벽 4시
↓
FileCleanupScheduler.cleanupOrphanFiles() 자동 호출
↓
fileService.deleteOrphanFiles(24시간 전)
↓
DB 조회: refId=0 AND updatedAt < 24시간 전인 파일들
↓
물리 파일 삭제 (uploads/ 폴더에서 삭제)
↓
DB 하드 삭제 (files 테이블에서 DELETE)
왜 바로 삭제하지 않고 스케줄러를 쓰는가?
| 방식 | 장점 | 단점 |
|---|---|---|
| 즉시 삭제 | 고아 파일 없음 | 글 작성 중 취소하면? 트랜잭션 불일치 위험 |
| 스케줄러 삭제 (이 프로젝트) | 트랜잭션 안전, 유예 기간 | 일시적으로 고아 파일 존재 |
에디터 이미지 업로드 → 글 등록은 별개의 요청이므로 트랜잭션으로 묶을 수 없음.
24시간 유예를 두면 사용자가 작성 중인 파일이 삭제되는 것을 방지.
5. 스케줄러 vs 배치
| 스케줄러 (@Scheduled) | 배치 (Spring Batch) | |
|---|---|---|
| 복잡도 | 간단 | 복잡 |
| 적합한 작업 | 단순 반복 작업 | 대량 데이터 처리, 단계별 처리 |
| 재시도/복구 | 직접 구현 | 프레임워크 제공 |
| 모니터링 | 직접 구현 | 프레임워크 제공 |
| 이 프로젝트 | ✅ 사용 | ❌ 불필요 |
고아 파일 정리 정도의 단순 작업은
@Scheduled로 충분.
수백만 건의 데이터 마이그레이션, 정산 같은 작업은 Spring Batch 고려.