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);
        }
    }