ㅁ 사용자가 요청을 보내지 않아도 내가 원하는 시간에 내가 원하는 코드가 실행되게끔 등록할 수 있다.
- 생각보다 스케쥴러 기법도 많이 쓴다.
- 스프링에서도 제공하지만 오라클에서도 제공해서 쓸 수 있다. 우리는 스프링의 스케줄러를 써본다.
※ Scheduler
- 특정 날짜, 특정 시간, 일정 주기마다 어떤 작업을 묵시적으로 실행시켜주는 주체.
- 별도의 액션이 없어도 알아서 특정시간이 되면 자동 실행된다.
※ Spring Scheduler
(1) 매번 묵시적으로 실행시키고자 하는 작업들을 정의할 클래스를 만들기
- 그리고 그 클래스를 빈으로 등록한다. 빈등록은 3가지 방법이 있었다.
(2) 해당 클래스 내에 각 작업별 메소드를 작성하기
- 반환형은 반드시 void여야 하고, 매개변수는 없어야 한다. 메소드명은 상관없다.
- 메소드 위에 @Scheduled 어노테이션을 부여한다.
(3) servlet-context.xml에서 스케줄링을 사용하기 위해 task를 추가하기
※ Cron 표현식
- 작업을 실행시키고자 하는 날짜 및 시간 또는 일정주기를 지정할 때 사용하는 문법.
- 스프링에서만 쓰는 것은 아니다.
(1) 형식
- 초 분 시 일 월 요일 [연도] // 연도만 생략 가능
(2) 각 자리별 표기가능한 값이 정해져있다.
- 초 : 0~59
- 분 : 0~59
- 시 : 0~23
- 일 : 1~31
- 월 : 0~11 (JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC - 숫자 대신 영문도 가능)
- 요일 : 0~6 (SUN, MON, TUE, WED, THR, FRI, SAT)
- 연도 : 4자리 연도 생략 가능
(3) 각 형식에 작성 가능한 키워드도 있다
- ? : 설정값(특정값)을 지정하지 않을 때 사용 (일, 요일 자리에만 쓸 수 있다)
- * : 모든조건 (초, 분, 시, 일, 월)
- L : 마지막 날짜 (일, 요일 자리에만 쓸 수 있다)
- W : 가장 가까운 평일 (일)
일 자리에 10W를 하면 10과 가까운 평일을 의미함.
10일이 평일이면 10일에 실행되고
10일이 토요일이면 9일에 실행된다.
10일이 일요일이면 11일에 실행된다.
- # : 몇주째인지 (요일 자리에만 쓸 수 있다)
3#2면 수요일 2째주 (3이 수요일을 뜻한다)
- / : 주기(시작시간/단위)
분 자리에 0/10이면 0분부터 10분단위(간격)마다. 0분 10분 20분 ...
- - : 범위
시 자리에 1-3하면 1시 2시 3시
- , : 여러개 지정
(4) 예시
- 크론 표현식을 만들어주는 사이트도 있는데 스프링이랑은 조금 달라서 실행이 안될 수도 있다.
- 0 0 * * * * : 매일 모든 시간마다
- 0 0 5 * * * : 매일 5시마다(24시간 표기라 오후 5시가 아닌 새벽 5시를 뜻한다.)
- 0 0 3-5 * * * : 매일 3, 4, 5시마다
- 0 0 4,5 * * * : 매일 4,5시마다
- 0 1/30 3-5 * * * : 매일 3:01, 3:31, 4:01, 4:31, 5:01, 5:31마다
- 0 0 12 * * MON-FRI : 평일(월, 화 ,수, 목, 금) 12시마다
ㅁ com.br.spring.scheduler 패키지 생성
ㅁ com.br.spring.scheduler에 BoardScheduler (일반) 클래스 생성
package com.br.spring.scheduler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.br.spring.service.BoardService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j // 로그쓰게 @Slf4j
@RequiredArgsConstructor // final 필드에 대한 매개변수 생성자가 만들어지고, 매개변수 생성자 주입에 의해서 생성된 객체가 boardService에 대입된다.
@Component
public class BoardScheduler {
private final BoardService boardService;
@Scheduled(cron="0 10 11 * * *") // 매일 오전 11시 10분마다 로그 출력
public void esecute1() { // 반환형은 반드시 void, 매개변수 반드시 X, 메소드명은 마음대로.
log.debug("매일 오전 11시 10분마다 실행됨");
}
@Scheduled(cron="0 0/1 * * * *") // 1분마다 실행
public void execute2() {
log.debug("1분마다 매번 실행됨");
}
@Scheduled(cron="0 0 0/1 * * *") // 1시간마다 실행
public void execute3() {
log.debug("1시간 마다 매번 실행됨");
}
// 통계 정보를 위해 매일 밤 12시에 현재 게시글의 총 갯수를 로그로 기록을 남기는 스케줄러가 필요하다고 가정한다.
@Scheduled(cron="0 0 0 * * *") // 매일 밤 12시마다 실행
public void execute4() {
log.debug("현재 게시글의 총 갯수: {}", boardService.selectBoardListCount());
}
// 지금은 개발서버고 나중에 실서버에 배포하면 실서버는 24시간 계속 돌아간다.
}
ㅁ servlet-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<resources mapping="/upload/**" location="file:///upload/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<context:component-scan base-package="com.br.spring" />
<!-- webSocket 관련 등록 구문 -->
<beans:bean class="com.br.spring.handler.ChatEchoHandler" id="chatEchoHandler" />
<websocket:handlers>
<websocket:mapping handler="chatEchoHandler" path="/chat" />
<websocket:handshake-interceptors>
<beans:bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor" />
</websocket:handshake-interceptors>
<websocket:sockjs />
</websocket:handlers>
<!-- interceptor 관련 등록 구문 -->
<interceptors>
<interceptor>
<mapping path="/member/myinfo.do" />
<mapping path="/board/regist.do" />
<beans:bean class="com.br.spring.interceptor.LoginCheckInterceptor" id="LoginCheckInterceptor"/>
</interceptor>
<!--
<interceptor>
</interceptor>
-->
</interceptors>
<!-- Scheduler -->
<task:annotation-driven />
</beans:beans>
ㅁ 서버 start
- 오른쪽 아래 시간을 클릭해서 "날짜 및 시간 조정" - "자동"에서 "수동"으로 현재 시간을 11시 58분으로 설정하고 서버를 스타트했다. 12시가 되자마자 저렇게 뜬다.
=====================================================================================
ㅁ status가 n인 댓글을 스케줄러로 자동으로 delete 해보기
- REPLY 테이블에가서 저 두 댓글을 N으로 바꿨다. (삭제된 댓글이라고 가정)
- 일요일 새벽 1시마다 REPLY 테이블에서 상태가 N인 댓글을 삭제한다.
- 현재는 댓글을 DELETE하는 서비스와 쿼리가 없다. 만든다.
ㅁ board-mapper.xml
ㅁ BoardDao
ㅁ BoardService
ㅁ BoardServiceImpl
ㅁ BoardScheduler
package com.br.spring.scheduler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.br.spring.service.BoardService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequiredArgsConstructor // final 필드에 대한 매개변수 생성자가 만들어지고, 매개변수 생성자 주입에 의해서 생성된 객체가 boardService에 대입된다.
@Component
public class BoardScheduler {
private final BoardService boardService;
@Scheduled(cron="0 10 11 * * *") // 매일 오전 11시 10분마다 로그 출력 (로그쓰게 @Slf4j)
public void esecute1() { // 반환형은 void, 메소드명은 마음대로
log.debug("매일 오전 11시 10분마다 실행됨");
}
@Scheduled(cron="0 0/1 * * * *") // 1분마다 실행
public void execute2() {
log.debug("1분마다 매번 실행됨");
}
@Scheduled(cron="0 0 0/1 * * *") // 1분마다 실행
public void execute3() {
log.debug("1시간 마다 매번 실행됨");
}
// 통계 정보를 위해 매일 밤 12시에 현재 게시글의 총 갯수를 로그로 기록을 남기는 스케줄러가 필요하다고 가정한다.
@Scheduled(cron="0 0 0 * * *") // 매일 밤 12시마다 실행
public void execute4() {
log.debug("현재 게시글의 총 갯수: {}", boardService.selectBoardListCount());
}
// 지금은 개발서버고 나중에 실서버에 배포하면 실서버는 24시간 계속 돌아간다.
// 일요일 새벽 1시마다 현재 status가 N인 댓글을 완벽히 delete하는 스케줄러가 필요하다고 가정한다.
@Scheduled(cron="0 0 1 * * SUN")
public void execute5() {
int result = boardService.deleteReplyCompletely();
log.debug("현재 완전 삭제된 댓글 갯수: {}", result);
}
}
ㅁ 서버 start
- 일요일 새벽 1시가 아닌 지금 되는지 확인하려고 크론 표현식으로 11시 38분 30초에 되도록 했다.
- 실제 db에서도 delete 되었다.
ㅁ 굳이 관리자가 일일이 연차를 지급할 필요 없이, 스케줄러로 매년 1월 1일이 되는 순간 조회해와서 연차를 주게끔 할 수 있다.
- 근태관리도 특정 시간이되었는데 출근/퇴근을 안찍으면 자동으로 출근/퇴근되게끔 할 수 있다.