Spring) @Transaction 은 언제 commit 할까?

CokeBear·2022년 12월 26일
0

spring

목록 보기
14/15

문제

  @Transactional
  public FriendProfileInfoRes updateTypeD(Long friendId, Long userId) {
    Friend friend = findFriends(friendId); // entity 조회
    FriendType beforeFriendType = friend.getType(); // 변경 전 데이터 저장   
    isMine(userId, friend); 
    friend.updateBlockToggle(); // 엔티티 속성 업데이트
    em.flush(); // 플러쉬를 통해 강제 업데이트
    em.clear(); // 컨텍스트 초기화
    Friend syncFriend = findFriends(friendId); // 변경된 엔티티 조회
    FriendProfileInfoRes friendProfileInfoRes = FriendProfileInfoRes.newInstance(syncFriend.getFriend(), syncFriend);
    if(!beforeFriendType.equals(syncFriend.getType())){ // 변경전 데이터와 변경된 엔티티의 데이터가 다르면 
      chatBlockFeignClient.chatBlocked( // 외부 api로 데이터 전송
        userId,
        ChatBlockReq
          .builder()
          .blockUserId(friend.getFriend().getId())
          .build());
    }
    return friendProfileInfoRes;
  }

@Transaction 어노테이션이 걸린 메소드 안에서 데이터 변경 후 외부 api로 데이터 변경되었음을 알렸지만, 외부 api로 데이터를 조회한 결과 변경되지 않고 조회되는 문제가 있었다.

원인찾기

  1. em.flush는 SQL을 발생시키지만 Commit을 시키지 않는 문제점 확인

해결코드

 @Transactional
  public FriendProfileInfoRes updateTypeD(Long friendId, Long userId) {
    Friend friend = findFriends(friendId); // entity 조회
    FriendType beforeFriendType = friend.getType(); // 변경 전 데이터 저장
    isMine(userId, friend);
    //friend.updateBlockToggle(); // 엔티티 속성 업데이트
    if(friend.getType().equals(FriendType.G) || friend.getType().equals(FriendType.S))
      friendRepository.updateTypeD(friendId);
    if(friend.getType().equals(FriendType.D))
      friendRepository.updateTypeG(friendId);
    Friend syncFriend = findFriends(friendId); // 변경된 엔티티 조회
    FriendProfileInfoRes friendProfileInfoRes = FriendProfileInfoRes.newInstance(syncFriend.getFriend(), syncFriend);
    if(!beforeFriendType.equals(syncFriend.getType())){ // 변경전 데이터와 변경된 엔티티의 데이터가 다르면
      chatBlockFeignClient.chatBlocked( // 외부 api로 데이터 전송
        userId,
        ChatBlockReq
          .builder()
          .blockUserId(friend.getFriend().getId())
          .build());
    }
    return friendProfileInfoRes;
  }
  
  ////----------------------Repository-----------
  @Modifying(clearAutomatically = true, flushAutomatically = true)
  @Query("update Friend f set f.type = 'D', f.blockDate= now() where f.id = :id")
  void updateTypeD(Long id);
  @Modifying(clearAutomatically = true, flushAutomatically = true)
  @Query("update Friend f set f.type = 'G', f.blockDate= null where f.id = :id")
  void updateTypeG(Long id);

결과 및 다른 해결 방법

  1. 여전히 업데이트가 되지 않는 문제가 확인되어 Transaction의 문제임을 판단하고 기존의 선언적 트랜젝션 방식이 아닌 명시적으로 선언해 줄 수 있는 방식을 채택
 public FriendProfileInfoRes updateTypeD(Long friendId, Long userId) {
    Friend friend = findFriends(friendId);
    FriendType beforeFriendType = friend.getType();
    isMine(userId, friend);
    TransactionDefinition definition = new DefaultTransactionDefinition();
    TransactionStatus status = platformTransactionManager.getTransaction(definition);
    try {
      if(friend.getType().equals(FriendType.G) || friend.getType().equals(FriendType.S))
        friendRepository.updateTypeD(friendId);
      if(friend.getType().equals(FriendType.D))
        friendRepository.updateTypeG(friendId);
      platformTransactionManager.commit(status);
    } catch (Exception e) {
      platformTransactionManager.rollback(status);
    }
    Friend syncFriend = findFriends(friendId);
    FriendProfileInfoRes friendProfileInfoRes = FriendProfileInfoRes.newInstance(syncFriend.getFriend(), syncFriend);
    if(!beforeFriendType.equals(syncFriend.getType())){
      chatBlockFeignClient.chatBlocked(
        userId,
        ChatBlockReq
          .builder()
          .blockUserId(friend.getFriend().getId())
          .build());
    }
    return friendProfileInfoRes;
  }

성공

회고: Transaction안에서 flush를 하던 JPQL을 날리던간에 COMMIT이 되지 않아 데이터가 바로 업데이트 되지 않는 부분을 명확히 알 수 있었다.

Refactoring

외부 API 때문에 기존 서비스 로직이 크게 변경되었다. 서비스간 의존관계가 커진 것이다. 이런경우에는 Event Driven 방식이나 리플렉션으로 해결해볼 수 있지 않을까? 하는 고민에서 리팩토링을 진행해본다.

EventDriven 방식

  public FriendProfileInfoRes updateTypeD(Long friendId, Long userId) {
    Friend friend = findFriends(friendId);
    isMine(userId, friend);
    friend.updateBlockToggle();
    applicationEventPublisher.publishEvent(
      new FriendBlockEvent(
        userId,
        ChatBlockReq.createInstance(friend.getFriend().getId()))
    );
    return FriendProfileInfoRes.newInstance(friend.getFriend(), friend);
  }

event Driven 방식으로 해결하니 기존 서비스 로직과의 결합이 느슨해졌다.
또한, @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 어노테이션을 걸어 줌으로써 데이터 동기화 이슈에 대해서도 해결할 수 있게 되었다.

profile
back end developer

0개의 댓글