(제목 전우치 톤으로 읽기)
@Scheduled
is a fully viable option it is part of theorg.springframework.scheduling.annotation
package which means it has nothing to do with spring web etc.
@Scheduled
는org.springframework.scheduling.annotation
패키지의 일부이므로 Spring Web 등과는 전혀 관계가 없다.
참고: How to create a scheduler in spring webflux?
처음엔 Quartz를 사용하려고 시도해봤는데 굳이 안 써도 될 것 같아서 기본 지원 스케줄러 사용했다.
스케쥴되는 메서드는 반환 타입이 반드시 void이고 아무런 인자도 받지 말아야 한다.
@Scheduled
어노테이션이 붙고 컨테이너에 일반적인 스프링 빈으로 등록된 bean 클래스에@Configurable
를 사용하지 말아야 한다.
@Scheduled
과@Async
어노테이션을 둘 다 활성화하려면 설정에서 task 네임스페이스에 'annotation-driven' 요소를 추가해라.
참고: [Spring 레퍼런스] 26장 태스크(Task) 실행과 스케줄링의 26.5 스케줄링과 비동기 실행에 대한 어노테이션 지원
구현하고자 하는 로직은
받은 리스트를 처리하는 부분도 같은 프로젝트 안에 구현되어 있음.
데이터를 받으면 그대로 다시 DB에 업데이트 해주는 로직.
그니까 한 프로젝트 안에
따라서 스케줄러를 실행해서 호스트 리스트를 보내면 받아서 처리하는 부분까지 한 프로젝트 안에서 확인 가능하다는 것
아무튼..
HostEntity.java
@Setter
@Getter
@Builder
public class HostEntity {
private String ip;
private int port;
private String type;
private List<HostEntity> hostList;
}
public interface HostRepository extends R2dbcRepository<HostEntity, String> {
Mono<HostEntity> findByIp(String ip);
@Query("select * from host_entity")
Flux<HostEntity> findAll();
@Modifying
@Query("UPDATE host_entity SET port=:port, type=:type where ip=:ip")
Mono<HostEntity> setHostFor(String port, String type, String ip);
}
@Service
SyncJobScheduleService.java
@Scheduled(cron = "*/30 * * * * *")
public void syncSchedule(){
System.out.println("============스케줄러 start==============");
List<HostEntity> entityList =
hostRepository
.findAll()
.log()
.collectList()
.subscribe(list -> {
for(HostEntity entity : list){
System.out.println("this name: " + entity.getIp());
// 수정 테스트
if("127.0.0.1".equals(entity.getIp())){
entity.setType("수정~~!");
}
// http post application/json 요청
nettyHttpRequest(HostEntity.builder()
.ip(entity.getIp())
.port(Integer.parseInt(entity.getPort()))
.hostList(list).build());
}
}).dispose();
}
}
처음에는 db 조회 후 http client 전송 로직을 수행하도록 구현함
@Scheduled
: 스케줄링 메소드 어노테이션(cron = "*/30 * * * * *)
: 매 30초마다 실행. cron 표현식 (초 분 시 일 월 주 년)(cron = "@hourly")
1시간마다 돌릴 경우 이렇게도 표현 가능nettyHttpRequest()
: Netty http post application/json 요청 서비스. implementation 'org.springframework.boot:spring-boot-starter-webflux'
HostDto.java
@EqualsAndHashCode(callSuper = true)
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class HostDto{
private List<HostEntity> hostList;
}
@Component
routingHandler.java
public Mono<ServerResponse> syncHost(ServerRequest request){
return request.bodyToMono(HostDto.class)
.flatMap(body -> {
log.info("=== Host synchronization 시작 ===");
List<HostEntity> bodyList = body.getHostList();
List<HostEntity> hostList = new ArrayList<>();
for(HostEntity he : bodyList){
hostList.add(HostEntity.builder()
.ip(he.getIp())
.port(he.getPort())
.type(he.getType())
.build());
}
return ServerResponse.ok()
.body(syncService.SyncHost(hostList),String.class);
}).onErrorResume(e->ServerResponse.badRequest().bodyValue(e.getMessage()));
}
@Service
SyncService.java
public Mono<String> SyncHost(List<HostEntity> hostList) throws CustomException {
return hostClient.saveAllHost(hostList)
.flatMap(result -> {
if(result){
return Mono.just(OK);
}else{
throw new CustomException(FAILED_SYNC);
}
});
}
@Service
HostClient.java
@Transactional
public Mono<Boolean> saveAllHost(List<HostEntity> hostList) {
System.out.println("saveAll");
try {
for(HostEntity i: hostList){
hostRepository.findByIp(i.getIp())
.flatMap(found -> hostRepository.setHostFor(i.getPort(), i.getType(), i.getIp()).log())
.switchIfEmpty(hostRepository.save(i)).log().subscribe();
}
}catch (Exception e){
e.printStackTrace();
return Mono.just(false);
}
return Mono.just(true);
}
String
타입이므로 insert/update 대상 구분을 위해 flatMap()
과 switchIfEmpty()
로 분기를 태웠다. (참고: [ERR] r2dbc saveAll() 시 isNew() 판단 못하는 문제)처음 스케줄을 실행할 때는 데이터를 넘기는 것까지 잘 되는 것 같았다. 그런데 데이터를 받아서 처리하는 부분에서는 쿼리 실행을 시도조차 하지 않고, 그 다음 스케줄을 실행할 때도 스케줄러 안의 findAll()
조차 하지 않는다…
... 그러고보니 받는 로직도 같은 프로젝트 안에 있다고 했잖음?
아무튼 그래서 바꿈
@Service
SyncJobScheduleService.java
@Scheduled(cron = "*/30 * * * * *")
public void invalidateOtp(){
System.out.println("============스케줄러 start==============");
// 바꾼 부분
List<HostEntity> hostList = hostRepository.findAll().log().collectList().block();
for(HostEntity entity : hostList){
System.out.println("this host: " + entity.getIp());
// 수정 테스트
if("127.0.0.1".equals(entity.getIp())){
entity.setType("수정^^~~!");
}
// http post application/json 요청
nettyHttpRequest(HostEntity.builder()
.ip(entity.getIp())
.port(Integer.parseInt(entity.getPort()))
.type(entity.getType())
.hostList(hostList).build());
}
}
block()
으로 List에 넣어도 되겠다 싶어서 위에처럼 처리했음. 이게 맞는 설명인지는 모르겠는데 느낌은 이럼.사실... 맞는 방법인지 모르겠음
근데 됨.
오늘도 10%의 확신과 90%의 어 이게 왜 되지 마무리
웹플럭스가 너무 어렵당.. 공부하기 싫당....