SpringFramework - Scheduler에 대해 알아보자

StrayCat·2026년 2월 25일

Scheduler란?

  • 특정 시간 또는 주기마다 자동으로 실행되는 작업을 정의하는 기능입니다.

Cron 표현식 이해하기

Spring의 @Scheduled 어노테이션은 Cron 표현식으로 실행 시점을 지정합니다.

순서: 초 / 분 / 시 / 일 / 월 / 요일
예시: "0 0 1 * * *"  →  매일 새벽 1시 0분 0초
필드허용 값예시
0~590
0~5930
0~231
1~31* (매일)
1~12* (매월)
요일0~7 (0,7=일요일)*

공식 문서: CronExpression - Spring Docs


예시 구현 흐름

[Scheduler 실행]
   ↓
DB에서 전체 관심상품 목록 조회
   ↓
각 상품 제목으로 네이버 쇼핑 API 검색 (1초 간격 > 네이버 정책)
   ↓
검색 결과의 최저가(lprice)로 DB 업데이트

코드 구현

1. @EnableScheduling 활성화

스케줄러 기능을 사용하려면 메인 클래스에 어노테이션을 추가해야 합니다.

@EnableScheduling   // 스케줄러 활성화
@EnableJpaAuditing  // 시간 자동 변경 활성화
@SpringBootApplication
public class MyselectshopApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyselectshopApplication.class, args);
    }
}

2. Scheduler 클래스

@Slf4j(topic = "Scheduler")   // 로그 출력 시 태그명 지정
@Component                     // 스프링 빈으로 등록
@RequiredArgsConstructor
public class Scheduler {

    private final NaverApiService naverApiService;
    private final ProductService productService;
    private final ProductRepository productRepository;

    // 초, 분, 시, 일, 월, 요일 순서
    @Scheduled(cron = "0 0 1 * * *") // 매일 새벽 1시 실행
    public void updatePrice() throws InterruptedException {
        log.info("가격 업데이트 실행");

        // 1. 모든 관심상품 조회
        List productList = productRepository.findAll();

        for (Product product : productList) {
            // 2. 네이버 API 호출 제한을 고려하여 1초 대기
            TimeUnit.SECONDS.sleep(1);

            // 3. 상품 제목으로 네이버 쇼핑 API 검색
            String title = product.getTitle();
            List itemDtoList = naverApiService.searchItems(title);

            if (itemDtoList.size() > 0) {
                ItemDto itemDto = itemDtoList.get(0); // 첫 번째(가장 관련성 높은) 결과 사용

                // 4. 해당 상품의 최저가 업데이트
                Long id = product.getId();
                try {
                    productService.updateBySearch(id, itemDto);
                } catch (Exception e) {
                    log.error(id + " : " + e.getMessage()); // 개별 실패는 로그만 남기고 계속 진행
                }
            }
        }
    }
}

3. ProductService - updateBySearch

// 스케줄러에서 호출하는 최저가 업데이트 메서드
@Transactional
public void updateBySearch(Long id, ItemDto itemDto) {
    // 상품 조회, 없으면 예외 발생
    Product product = productRepository.findById(id).orElseThrow(
        () -> new NullPointerException("해당 상품은 존재하지 않습니다.")
    );
    // 최저가 업데이트
    product.updateByItemDto(itemDto);
}

4. Product Entity - updateByItemDto

// 네이버 API 응답으로 최저가(lprice) 갱신
public void updateByItemDto(ItemDto itemDto) {
    this.lprice = itemDto.getLprice();
}

핵심 포인트 정리

TimeUnit.SECONDS.sleep(1) 을 사용하는 이유

  • 네이버 Open API는 초당 호출 횟수 제한(Rate Limit)이 있습니다.
  • 상품이 많을 경우 빠르게 연속 호출하면 API 오류가 발생할 수 있으므로, 1초 간격으로 요청을 분산시킵니다.

개별 try-catch 처리 이유

  • 특정 상품 하나에서 오류가 발생하더라도, 나머지 상품들의 업데이트가 중단되지 않도록 예외를 개별적으로 처리합니다.
  • 오류는 로그로 기록하여 추적이 가능하게 합니다.

@Component로 등록하는 이유

  • @Scheduled가 동작하려면 해당 클래스가 스프링 컨테이너에 빈으로 등록되어 있어야 합니다.
  • @Component는 이를 위한 가장 기본적인 방법입니다.

기타

규모가 커지면 외부 스케줄러 고려

단일 서버에서는 @Scheduled가 충분하지만, 멀티 인스턴스 환경(서버가 n개)에서는
모든 서버에서 동시에 스케줄러가 실행되는 문제가 발생할 수 있습니다.

이런 경우에는 다음과 같은 도구를 검토할 수 있습니다.

  • ShedLock : 분산 환경에서 스케줄러가 단 한 번만 실행되도록 잠금 처리
  • Quartz Scheduler : 더 정교한 작업 관리, 이력 추적 등이 필요한 경우
  • Spring Batch : 대량 데이터 처리가 필요한 배치 작업에 적합

학습 단계에서는 @Scheduled의 구조와 Cron 표현식을 확실히 이해하는 것이 우선입니다.
규모가 커진 환경에서는 Scheduler가 문제가 될 수 있다는 것을 기억.

RestTemplate 관련 안내

현재 코드에서 NaverApiService 내부적으로 사용 중인 RestTemplate은 Spring 5부터 유지보수 모드로 전환되었습니다.
신규 프로젝트에서는 WebClient (Spring WebFlux) 또는 RestClient (Spring 6.1 / Boot 3.2+에서 추가)를 사용하는 방향이 권장됩니다.
다만 레거시 코드베이스에서는 RestTemplate이 여전히 많이 사용되므로, 두 방식 모두 파악해두는 것이 좋습니다.

profile
알면 좋은 것보단 잊어버리기 싫은 것들을 기록합니다.

0개의 댓글