애앵앷
- Spring Event와 @Async를 결합하여 비동기 이벤트를 처리하는 원리
- @Async의 동작방식, 스레드 풀(Thread Pool)
- @EventListener와 @Async 조합을 통해 서비스 성능을 높이는 구조 구현
- @TransactionalEventListener의 역할과 시점(BEFORE_COMMIT, AFTER_COMMIT 등) 구분하기
- 트랜잭션과 비동기 이벤트의 관계를 실무적으로 이해하고 주의사항 익히기
@Async와 비동기 이벤트 처리의 필요성
Spring Event 시스템은 기본적으로 동기(Synchronous) 방식으로 동작함
즉, 이벤트를 발행하면 리스너가 모두 처리될 때까지 다음 코드가 실행되지 않는다
그러나 메일전송, 로그기록, 외부 API 호출 등 시간이 오래 걸리는 작업의 경우 비동기(Asynchronous)로 처리하는게 효율적이다
그걸 도와주는게 @Async 애너테이션이다
1. 동기 vs 비동기 이벤트 처리 비교
| 구분 | 동기 | 비동기 |
| 실행방식 | 이벤트 발행 후, 리스너가 끝날때까지 대기 | 리스너를 별도 스레드에서 실행함 |
| 장점 | 순서 보장, 예측 가능 | 빠른응답, UI/서비스 블로킹 방지 |
| 단점 | 느림, 메인로직 지연 | 순서제어가 어려움, 예외처리 주의 필요 |
2. @Async 활성화 설정
@Async를 사용하려면 가장 먼저 @EnableAsync를 통해 스프링에서 비동기 처리를 활성화해야함!!
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
@Configuration
@EnableAsync
public class AsyncConfig {
}
이걸 작성해주면, @Async가 붙은 메서드는 별도의 스레드 풀(Thread Pool)에서 실행된다
@Async와 @EventListener 조합하기
@Async와 @EventListener 두 조합은 이벤트 리스너를 비동기적으로 실행되게 만들어준다.
1.비동기 이벤트 리스너 구현
리스너 예시
@Component
public class NotificationEventListener {
@Async
@EventListener
public void handleUserRegistered(UserRegisteredEvent event) {
System.out.println(Thread.currentThread().getName() + " : 이메일 발송 시작");
sendWelcomeMail(event.getEmail());
System.out.println(Thread.currentThread().getName() + " : 이메일 발송 완료");
}
private void sendWelcomeMail(String email) {
try {
Thread.sleep(3000); // 이메일 전송 예시 시뮬레이션
System.out.println("이메일을 전송했습니다. : " + email);
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
@Async가 붙은 리스너의 경우, 별도 스레드에서 실행되기 때문에 메인 로직이 차단되지 않는다.
2. 이벤트 발행자 코드 예시
@Service
public class UserService {
private final ApplicationEventPublisher publisher;
public UserService(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void register(String email) {
System.out.println("회원가입 처리 완료 : " + email);
userRepository.save(new User(email)); // 이 시점에서 커밋이 발생한다고 한다.
publisher.publishEvent(new UserRegisteredEvent(email)); // 이메일 발송 이벤트 발행
System.out.println("회원가입 API 응답 완료");
}
}
출력 예시
회원가입 처리 완료: user@example.com
회원가입 API 응답 완료 // 비동기적 처리
SimpleAsyncTaskExecutor-1 - 이메일 발송 시작 // 리스너 시작
환영 이메일을 전송했습니다: user@example.com
SimpleAsyncTaskExecutor-1 - 이메일 발송 완료 // 리스너 끝
회원가입 로직의 경우 즉시 종료되며, 이메일 발송은 별도의 스레드에서 처리된다.
3. 스레드 풀 설정
기본적으로 Spring은 SimpleAsyncTaskExecutor를 사용하지만, 직접 스레드 풀 크기나 큐 크기를 지정해주는게 좋다고 함(실무)
@Configuration
public class AsyncThreadPoolConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4); // 최소 스레드의 수
executor.setMaxPoolSize(8); // 최대 스레드의 수 설정
executor.setQueueCapacity(50); // 큐 용량
executor.setThreadNamePrefix("AsyncExecutor-"); // ?
executor.initialize();
return executor;
}
}
@Async("taskExecutor") 같은 형태를 통해 명시적으로 특정 스레드 풀을 지정할 수 있다고 한다.
@TransactionalEventListener - 트랜잭션 시점별 이벤트 처리
@TransactionalEventListener는 트랜잭션의 상태에 따라 이벤트를 처리할 수 있게 해준다.
이 애너테이션을 통해서 트랜잭션이 완료된 시점에 맞춰 이벤트를 실행할 수 있다고 함
1. 동작 개념
| 시점 | 의미 | 예시 |
| BEFORE_COMMIT | 트랜잭션 커밋 직전에 실행됨 | 검증 로직 실행 |
| AFTER_COMMIT | 트랜잭션 커밋 완료 후에 실행됨 | 이메일 전송, 로그 저장 |
| AFTER_ROLLBACK | 롤백시 실행됨 | 보상 트랜잭션 수행 |
| AFTER_COMPLETION | 트랜잭션 완료시(성공, 실패 무관) | 리소스 정리 |
이 개념을 @TransactionalEventListener과 함께 쓸 수 있다
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
2. 예시
@Component
public class TransactionalEventHandler {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) // 커밋되고 실행
public void afterCommit(UserRegisteredEvent event) {
System.out.println("트랜잭션 커밋 이후 처리 : " + event.getEmail());
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK) // 롤백시 실행
public void afterRollback(UserRegisteredEvent event) {
System.out.println("트랜잭션 롤백 감지 : " + evnet.getEmail());
}
}
동작 흐름
비동기 이벤트와 트랜잭션 이벤트의 처리 순서를 비교한 것이라고 함......

팁
| 상황 | 팁 |
| 이메일, 알림 등 지연 작업 | @Async 사용으로 서비스 응답 속도 향상 |
| 데이터 일관성이 중요한 작업 | @TransactionalEventListener(phase = AFTER_COMMIT) 사용 |
| 대량 트래픽 서비스 | ThreadPoolExecutor 커스터마이징을 통해 안정적 운영 |
| 테스트시 비동기 실행이 어렵다.. | @Async를 MockExecutor로 대체하거나 SynchronousExecutor 설정하기 |
'Spring Boot > 비동기 처리' 카테고리의 다른 글
| Spring Event : 분산 환경으로의 확장 가능성 (0) | 2025.10.29 |
|---|---|
| Spring Event : 이벤트 기반 비동기 처리 활용사례 (0) | 2025.10.29 |
| Spring Event : @EventListener 활용 (0) | 2025.10.25 |
| Spring Event : 기본 이벤트 처리 방식 (0) | 2025.10.24 |
| Spring Event : Spring Event? (0) | 2025.10.24 |