인프런 커뮤니티 질문&답변
eventPublisher.publishEvent 시 @Transactional을 걸어야 하지 않나요?
해결된 질문
작성
·
115
0
딩코님! 강의 너무 잘 듣고 있습니다~! 강의를 따라가며 디비와 트랜잭션 그리고 락에 관해 심화 내용도 듣고 포폴도 하나하나 잘 채워가고 있습니다! 다름이 아니라 딩코님이 내주신 과제 중에 의문이 있어 질문을 드리게 되었습니다.
1. 현재 학습 진도
4주차 수강 후 락과 트랜잭션 최적화를 수행하고 있습니다.
현재 여러가지 작업(일기 작성, 포인트 적립, AI 댓글 작성 예약, 알림 이벤트 발생)에 대해 facade 계층을 만들어 중요한 작업(일기 작성, 포인트 적립)에 대해서는 트랜잭션을 묶어 우선적으로 처리되게 하고 AI 댓글은 TaskScheduler로 특정 시간 후에 비동기적으로 이루어지게 하며 알림은 spring event로 after commit 후 처리하려 합니다.
2. 어려움을 겪는 부분
현재 이벤트 발행 (트랜잭션 커밋 후 실행됨, after-commit)에 대해 의문이 있습니다.
현재 딩코님이 올려주신 코드에는
@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부터 트랜잭션을 걸어야 하니까 딩코님이 말씀하신 트랜잭션을 최대한 작게 나누는 트랜잭션 최적화가 어렵지 않나요?
항상 강의 잘 듣고 있습니다! 감사합니다!
답변 2
0
안녕하세요 도등어님 좋은 질문 해주셔서 감사합니다!!!!
Spring의 트랜잭션 이벤트 동작 원리를 잘 파악해주신 것 같습니다!!실제로 테스트까지 해보신 것도 넘넘 훌륭하십니다!! 우선 도등어님께서 말씀해주신 내용이 맞습니다!
말씀해주신대로@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)은 현재 실행 중인 트랜잭션이 있어야만 동작합니다.
현재 구조에서 가장 좋은 방법은 @TransactionalEventListener 을 @EventListener으로 변경하는게 좋을 것 같습니다. 트랜잭션 경계를 작게 유지할 수 있고, 모든 핵심 작업(DB 저장, 외부 API 호출) 완료 후 알림 발송하는거라 문제가 없을 것 같습니다. 이를 통해 트랜잭션과 무관하게 정상 작동할 것으로 보입니다!
위와 같이 시일 내에 교재 및 강의 내용을 변경하도록 하겠습니다!!
피드백 주심에 대한 감사의 의미로 커피 기프티콘을 드리겠습니다 아래 카카오톡 오픈 링크로 연락 부탁드립니다!!
https://open.kakao.com/me/ding_coding_co
감사합니다
0
안녕하세요, 인프런 AI 인턴입니다.
도등어님, 강의를 잘 듣고 계신다니 기쁩니다! 질문해 주신 트랜잭션과 이벤트 리스너의 실행 관련 내용을 자세히 설명해 드리겠습니다.
@TransactionalEventListener 어노테이션을 사용하면 해당 이벤트 리스너는 기본적으로 트랜잭션이 성공적으로 커밋된 이후에 실행되도록 되어 있습니다. 따라서 ApplicationEventPublisher를 통해 이벤트를 발행하는 메서드가 트랜잭션 안에 있지 않으면 AFTER_COMMIT 단계에서 이벤트 리스너가 실행되지 않습니다.
이러한 이유 때문에 joinEvent 메서드에 @Transactional을 붙이지 않아 발생한 문제가 있는 것입니다. joinEvent 메서드에 @Transactional을 걸지 않으면, 이벤트 발생이 트랜잭션 컨텍스트 밖에서 이루어지게 되고, 따라서 AFTER_COMMIT 단계가 발생하지 않기 때문에 관련 이벤트 리스너가 실행되지 않습니다.
그러므로, 트랜잭션을 최대한 작게 나누면서도 AFTER_COMMIT 이벤트를 사용하고자 한다면, 트랜잭션을 유지해야 할 범위나 조건에 따라 적절한 위치에 @Transactional을 걸어주는 것이 필요합니다.
전체 트랜잭션을 필요 최소한의 범위로 유지하면서도 작성한 이벤트 리스너를 정상적으로 실행하기 위해서는 트랜잭션의 경계 및 이벤트 흐름을 명확히 설정하는 것이 중요합니다. 가능하다면 이벤트의 발행 또한 필요한 트랜잭션 범위 안에서 이루어지도록 코드를 구성해 주세요.
궁금한 점이 해결되었길 바라며, 질문해 주셔서 감사합니다! 계속해서 강의를 재미있게 들어주시면 좋겠습니다. 😄
information source: Spring Data JDBC - Reference Documentation




