Spring
Spring Event - 최종 프로젝트 관련 내용 포함
어떻게말이름이히힝
2025. 3. 27. 16:18
Spring Event는 애플리케이션 내에 발생하는 이벤트를 기반으로 Component들을 서로 독립적으로 동작할 수 있게 해주는 이벤트 기반 처리 매커니즘
언제 사용?
컴포넌트 간 느슨한 결합이 필요할 때
ex) 회원가입 이메일/문자 발송
비동기작업 처리
ex) 재고 업데이트, 대용량 데이터 처리와 같은 작업
*카프카나 이런거에 도입을 한다하면 퍼블리셔랑 리스너만 카프카에 맞게 바꿔주면 됨
ApplicationEvent : 이벤트 객체
public record UserSaveEvent(Long id, String userName) {}
ApplicationEventPublisher : 이벤트를 발행하기 위해 사용하는 인터페이스 publishEvent() 메소드를 호출하여 ApplicationContext에 이벤트를 전달
@Component
@RequiredArgsConstructor
public class UserSaveEventPublisher {
private final ApplicationEventPublisher applicationEventPublisher;
public void publishEvent(Long id, String userName) {
UserSaveEvent event = new UserSaveEvent(id, userName);
applicationEventPublisher.publishEvent(event);
}
}
@EventListener : 이벤트를 처리하기 위해 사용하는 Spring 어노테이션, 특정 이벤트를 감지하여 해당 이벤트에 대한 작업을 실행
@Slf4j
@Component
public class UserSaveEventListener {
@Async
@EventListener(condition = "#event.id() == 25")
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleEvent(UserSaveEvent event) {
log.info("이메일 전송 : {}", event.id());
}
@Async
@EventListener
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void handleEvent2(UserSaveEvent event) {
log.info("로깅 : {}", event.id());
}
@Async
@EventListener
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
public void handleEvent3(UserSaveEvent event) {
log.info("회원가입 완료!!", event.id());
}
}
@TransactionalEventListener : 트랜잭션 상태에따라 이벤트를 처리하기 위한 어노테이션
phase 설정
- BEFORE_COMMIT : 트랜잭션 커밋 직전 / 데이터 확인, 검증 작업
- AFTER_COMMIT : 트랜잭션 커밋 성공 이후 / 후속 작업(알림, 이메일 등)
- AFTER_ROLLBACK : 트랜잭션 롤백 이후 / 오류 처리, 복구 작업
- AFTER_COMPLETION : 트랜잭션 완료 후(성공/실패 무관) / 리소스 정리, 공통 후속 작업
EventListner 개념으로 해결한 문제
이메일 전송이 실패하면 그 전 작업이 롤백되던 문제가 있어 이벤트리스너를 생성하여 문제를 해결함
@Component
@RequiredArgsConstructor
public class EmailEventListener {
private final EmailService emailService;
@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleEmailEvent(EmailEvent event) {
emailService.sendMemberEmail(event.getMemberId(), event.getEmailRequestDto());
}
}
웹소켓 사용으로 입찰내역을 실시간으로 뿌려줄 때, 입찰은 동시다발적으로 일어날 수 있어 실패가 잦을거라고 예상.
-> 입찰이 '성공'해야 정보를 뿌려줄 수 있도록 설정
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void publishBidding(BiddingResponseDto biddingResponseDto) {
try {
redisTemplate.convertAndSend(topic.getTopic(), biddingResponseDto);
log.info("Redis publish success");
} catch (Exception e) {
log.error("Redis publish error", e);
}
}