inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

6주 완성! 백엔드 이력서 차별화 전략 4가지 - 똑같은 이력서 속에서 돋보이는 법

eventPublisher.publishEvent 시 @Transactional을 걸어야 하지 않나요?

해결된 질문

135

도등어

작성한 질문수 9

0

딩코님! 강의 너무 잘 듣고 있습니다~! 강의를 따라가며 디비와 트랜잭션 그리고 락에 관해 심화 내용도 듣고 포폴도 하나하나 잘 채워가고 있습니다! 다름이 아니라 딩코님이 내주신 과제 중에 의문이 있어 질문을 드리게 되었습니다.

 

1. 현재 학습 진도

 

2. 어려움을 겪는 부분

현재 딩코님이 올려주신 코드에는

@Component
@RequiredArgsConstructor
@Slf4j
public class ImprovedEventJoinWithExternalApiUpdateFacade {
    private static final String TEST_PHONE_NUMBER = "01012341234";

    private final EventExternalUpdateService eventJoinService;
    private final ExternalEventApi externalEventApi;
    private final ApplicationEventPublisher eventPublisher;

    public void joinEvent(Long eventId, Long memberId) {
        // 1. 기존 서비스로 이벤트 참가 처리
        EventWithLockParticipant participant = eventJoinService.joinEventWithTransaction(eventId, memberId);

        // 2. 외부 API 호출
        ExternalEventResponse response = externalEventApi.registerParticipant(
                eventId, memberId, participant.getEvent().getName()
        );

        if (!response.isSuccess()) {
            throw new RuntimeException("외부 API 호출 실패: " + response.getErrorMessage());
        }

        // 3. 외부 API 응답으로 참가자 정보 업데이트
        eventJoinService.updateExternalId(participant, response.getExternalId());

        // 4. 이벤트 발행 (트랜잭션 커밋 후 실행됨)
        eventPublisher.publishEvent(new EventJoinCompletedEvent(
                eventId,
                participant.getEvent().getName(),
                TEST_PHONE_NUMBER
        ));
    }
}

이렇게 코드 내에 @Transactional이 걸려있지 않습니다. 그런데 이벤트 리스너에는

@Component
@RequiredArgsConstructor
@Slf4j
public class EventJoinEventListener {
    private final KakaoTalkMessageApi kakaoTalkMessageApi;

    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleEventJoinCompleted(EventJoinCompletedEvent event) {
        try {
            kakaoTalkMessageApi.sendEventJoinMessage(
                    event.getPhoneNumber(),
                    event.getEventName()
            );
        } catch (Exception e) {
            log.error("알림 발송 실패. eventId={}, eventName={}",
                    event.getEventId(),
                    event.getEventName(),
                    e);
        }
    }
} 

이렇게 transaction이 커밋된 후에 실행되도록 AFTER_COMMIT이 걸려있는데, 이렇게 되면 이벤트 리스너가 제대로 실행이 안되지 않나요? 제대로 이벤트 리스너가 실행이 되려면 joinEvent부터 @Transactional이 걸려있어야 하지 않나요?

3. 시도해보신 내용

따로 간단하게 코드를 작성하여 테스트해보았습니다.

 

@RequiredArgsConstructor
@Component
public class StudyFacade {

    private final StudyService studyService;
    private final ApplicationEventPublisher eventPublisher;

    // @Transactional : 주석 처리하면 밑에 eventEvent가 실행되지 않음
    public void execute() {
        studyService.saveStudy();
        eventPublisher.publishEvent("event published");
    }

    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void executeEvent(String event) {
        System.out.println("event = " + event);
    }
}

@RequiredArgsConstructor
@Service
public class StudyService {

    private final StudyRepository studyRepository;

    @Transactional
    public void saveStudy() {
        studyRepository.save(new Study("test"));
    }
}

 실제로 execute 메서드의 @Transactional을 주석처리하면 밑의 이벤트 리스너인 executeEvent가 실행이 되지 않더라구요.

 

이러면 ImprovedEventJoinWithExternalApiUpdateFacade의 joinEvent부터 트랜잭션을 걸어야 하니까 딩코님이 말씀하신 트랜잭션을 최대한 작게 나누는 트랜잭션 최적화가 어렵지 않나요?

 

항상 강의 잘 듣고 있습니다! 감사합니다!

java spring mysql redis 이력서

답변 2

0

딩코딩코

안녕하세요 도등어님 좋은 질문 해주셔서 감사합니다!!!!

Spring의 트랜잭션 이벤트 동작 원리를 잘 파악해주신 것 같습니다!!실제로 테스트까지 해보신 것도 넘넘 훌륭하십니다!! 우선 도등어님께서 말씀해주신 내용이 맞습니다!

말씀해주신대로@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)현재 실행 중인 트랜잭션이 있어야만 동작합니다.

현재 구조에서 가장 좋은 방법은 @TransactionalEventListener 을 @EventListener으로 변경하는게 좋을 것 같습니다. 트랜잭션 경계를 작게 유지할 수 있고, 모든 핵심 작업(DB 저장, 외부 API 호출) 완료 후 알림 발송하는거라 문제가 없을 것 같습니다. 이를 통해 트랜잭션과 무관하게 정상 작동할 것으로 보입니다!

위와 같이 시일 내에 교재 및 강의 내용을 변경하도록 하겠습니다!!

피드백 주심에 대한 감사의 의미로 커피 기프티콘을 드리겠습니다 아래 카카오톡 오픈 링크로 연락 부탁드립니다!!

https://open.kakao.com/me/ding_coding_co

감사합니다

 

0

인프런 AI 인턴

안녕하세요, 인프런 AI 인턴입니다.
도등어님, 강의를 잘 듣고 계신다니 기쁩니다! 질문해 주신 트랜잭션과 이벤트 리스너의 실행 관련 내용을 자세히 설명해 드리겠습니다.

@TransactionalEventListener 어노테이션을 사용하면 해당 이벤트 리스너는 기본적으로 트랜잭션이 성공적으로 커밋된 이후에 실행되도록 되어 있습니다. 따라서 ApplicationEventPublisher를 통해 이벤트를 발행하는 메서드가 트랜잭션 안에 있지 않으면 AFTER_COMMIT 단계에서 이벤트 리스너가 실행되지 않습니다.

이러한 이유 때문에 joinEvent 메서드에 @Transactional을 붙이지 않아 발생한 문제가 있는 것입니다. joinEvent 메서드에 @Transactional을 걸지 않으면, 이벤트 발생이 트랜잭션 컨텍스트 밖에서 이루어지게 되고, 따라서 AFTER_COMMIT 단계가 발생하지 않기 때문에 관련 이벤트 리스너가 실행되지 않습니다.

그러므로, 트랜잭션을 최대한 작게 나누면서도 AFTER_COMMIT 이벤트를 사용하고자 한다면, 트랜잭션을 유지해야 할 범위나 조건에 따라 적절한 위치에 @Transactional을 걸어주는 것이 필요합니다.
전체 트랜잭션을 필요 최소한의 범위로 유지하면서도 작성한 이벤트 리스너를 정상적으로 실행하기 위해서는 트랜잭션의 경계 및 이벤트 흐름을 명확히 설정하는 것이 중요합니다. 가능하다면 이벤트의 발행 또한 필요한 트랜잭션 범위 안에서 이루어지도록 코드를 구성해 주세요.

궁금한 점이 해결되었길 바라며, 질문해 주셔서 감사합니다! 계속해서 강의를 재미있게 들어주시면 좋겠습니다. 😄

information source: Spring Data JDBC - Reference Documentation

조회속도 개선에서 더 개선하는 방법이 궁금합니다.

0

44

2

Build 관련 문제 (테스트 관련 문제)

0

44

2

인덱스 관련 질문 있습니다.

0

75

2

비관적 락 구현 방식 문의 건

0

82

2

외부 api 처리 방안에 대하여 궁금한 점이 있습니다.

0

93

2

네임드 락 사용 시 커넥션 풀을 분리하는 방법에 대한 질문

0

88

2

이벤트) 백엔드 기술면접 실전문제집

0

107

2

로컬에서 테스트 한 결과를 이력서에 써도 괜찮을까요?

0

134

2

데드락 발생 시, 외래 키를 제거하는 방법 관련 질문

0

91

2

Redis 캐싱을 도입하는데 db조회와 성능이 차이가 거의 없습니다.

0

115

2

k6 부하테스트 중인데 개선 전 성능이 너무 안나와서 고민

0

142

2

강의와 성능수치 비교

0

105

2

13강 강의 뒷부분의 과제 안내부분은 어디있나요?

0

52

2

이벤트 참가자 수 증가 후, save 메서드 호출 코드 질문

0

66

2

[수업 자료 질문] Cache Aside의 특징 문의

0

89

2

[수업자료 문의] RedisTemplate으로 SETNX 시 리턴값 문의

0

83

2

블로그에 학습 내용 정리 포스트를 올려도괜찮나요?

0

113

2

멀티스레드 상황인데 currentParticipants 가 AtomicInteger가 아닌 이유?

0

91

3

클라우드 환경 배포시 부하 테스트 방식에 대하여

0

153

2

k6 dashboad 안나오는 상

0

119

2

2-4 도커 빌드 에러가 계속 납니다.

0

302

2

AWS EC2에 도커 컨테이너가 동작하지 않을 때 확인 해야하는 것

0

118

2

성능 측정시

0

114

2

API 별 실행 쿼리 모니터링 구현 질문 있습니다.

0

87

2