@Async + ThreadPoolTaskExecutor@EnableAsync
@Configuration
class AsyncConfig {
@Bean
ThreadPoolTaskExecutor jobExecutor() {
ThreadPoolTaskExecutor ex = new ThreadPoolTaskExecutor();
ex.setCorePoolSize(2);
ex.setMaxPoolSize(2);
ex.setQueueCapacity(10);
ex.setThreadNamePrefix("job-");
return ex;
}
}
@Service
class SettlementService {
@Async("jobExecutor")
public void runSettlement(String jobId) {
// 긴 정산 로직, 완료 시 JOB 테이블 갱신
}
}컨트롤러는 runSettlement 을 호출하자마자 리턴하므로 202 응답을 바로 만들 수 있습니다. @Async 메서드의 리턴 타입이 void 면 예외가 호출자에게 전달되지 않으니, 실패는 반드시 메서드 내부에서 잡아 JOB 테이블에 FAILED 로 기록해야 합니다.
SseEmitter 로 진행률 push@GetMapping("/jobs/{id}/events")
public SseEmitter events(@PathVariable String id) {
SseEmitter emitter = new SseEmitter(0L); // 타임아웃 없음
jobExecutor.execute(() -> {
try {
while (진행중) {
emitter.send(SseEmitter.event().data(statusJson(id)));
Thread.sleep(500);
}
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
});
return emitter;
}SseEmitter 는 완료·에러 콜백(onCompletion, onTimeout)을 등록해 클라이언트 연결이 끊겼을 때 리소스를 정리할 수 있습니다. nginx 설정은 2.4 절과 동일하게 버퍼링을 꺼야 합니다.
@Service
class JobService {
public void submitAndRun(String id) {
this.runAsync(id); // 같은 빈 안에서 직접 호출
}
@Async
public void runAsync(String id) {
// 이 메서드는 프록시를 거치지 않아 동기 실행된다
}
}submitAndRun 안에서 this.runAsync(id) 를 호출하면 @Async 가 무시되고 같은 스레드에서 즉시 실행됩니다. 컨트롤러가 submitAndRun 을 호출하는 순간 요청 스레드가 그대로 긴 작업을 떠안습니다. 해결책은 runAsync 를 별도 빈으로 분리해 프록시를 통해 호출하는 것입니다.