트랜잭션 커밋 이후 실행하여 일관성을 보장하는 방법 (멱등성): TransactionSynchronization

ssongyi·2025년 6월 17일
0

Java/Spring TIL

목록 보기
19/22

Spring 은 @Transactional 이 붙어있다고 해서, 메서드 내 모든 코드가 "커밋 이후" 에 실행되는 것은 아니다.

스프링의 트랜잭션은

  1. 메서드 본문 전체를 하나의 트랜잭션 경계 안에서 실행
  2. 메서드가 정상 종료(return)되면 커밋

순으로 동작하기 때문에, 메서드 안에서 broadcast 호출을 바로 하면 커밋 전에 실행된다.
이 상태에서 커밋이 실패하거나 롤백되면, 이미 브로드캐스트된 메시지는 롤백되지 않아 데이터 불일치가 발생한다.

따라서 “DB 커밋이 정말 완료된 후에만 브로드캐스트를 보내라”는 보장을 하려면

  • TransactionSynchronizationManager.registerSynchronization(...)
  • 또는 ApplicationEventPublisher.publishEvent(...) + @TransactionalEventListener(phase = AFTER_COMMIT)

중 하나를 사용해야 한다.

요약

  • @Transactional 은 “이 메서드가 하나의 트랜잭션 범위 안에서 실행” 만 보장
  • 실제 커밋 시점(트랜잭션이 DB에 반영된 시점) 이후에만 동작시키려면,
    - TransactionSynchronization 을 등록하거나
    • @TransactionalEventListener(phase = AFTER_COMMIT) 로 처리해야 함

이렇게 해 두면, 브로드캐스트 이벤트가 트랜잭션 커밋 성공 시에만 실행되어서,
메시지와 DB 상태 간의 일관성이 완벽히 유지된다.

Before

   /** 퇴장(본인 나가기) */
    @Transactional
    public void leaveChatRoom(Long roomId, Long userId) {
        ChatParticipant participant = chatParticipantRepository.findActiveByRoomAndUser(roomId, userId)
                .orElseThrow(() -> new ChatException(ChatErrorCode.USER_NOT_IN_CHAT_ROOM));
        participant.leave();

        // 실시간 멤버 브로드캐스트
        chatMemberEventService.broadcastMembers(roomId);
    }
  • participant.leave() 호출 후 flush 되기 전에 바로 브로드캐스트하므로 위와 동일한 불일치 가능성 있음
  • flush 되지 않은, 같은 트랜잭션 내에서 즉시 조회하면 변경된 멤버를 보지 못할 수 있음
  • 또, 이후 트랜잭션이 롤백되면 이미 전파된 정보가 실제 DB 와 달라짐

After

/**
     * 퇴장(본인 나가기)
     *
     * @param groupId 모임 ID
     * @param roomId  채팅방 ID
     * @param userId  요청 사용자 ID
     */
     public void leaveChatRoom(Long groupId, Long roomId, Long userId) {
         // 1) 모임 멤버 여부 검증
         groupValidator.validateMember(userId, groupId);

         // 2) 채팅방 존재 여부 검증
         ChatRoom room = chatValidator.validateRoom(roomId);

         // 3) 해당 방의 참가자 여부 검증 및 조회
         ChatParticipant participant =
                 chatValidator.validateParticipant(roomId, userId);

         // 4) 참가 비활성 처리
         participant.leave();

         // 5) 트랜잭션 커밋 후 실시간 멤버 브로드캐스트
         TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
             @Override
             public void afterCommit() {
                 chatMemberEventService.broadcastMembers(roomId);
             }
         });
     }

TransactionSynchronization 직접 구현하는 방법 사용

  • Java 8 이후부터 TransactionSynchronization 인터페이스에 기본 메서드(default method)가 구현되어 있어, 어댑터 클래스를 상속할 필요 없이 바로 익명 구현으로 쓸 수 있음

0개의 댓글