@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로 데이터를 조회한 결과 변경되지 않고 조회되는 문제가 있었다.
- 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);
- 여전히 업데이트가 되지 않는 문제가 확인되어 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이 되지 않아 데이터가 바로 업데이트 되지 않는 부분을 명확히 알 수 있었다.
외부 API 때문에 기존 서비스 로직이 크게 변경되었다. 서비스간 의존관계가 커진 것이다. 이런경우에는 Event Driven 방식이나 리플렉션으로 해결해볼 수 있지 않을까? 하는 고민에서 리팩토링을 진행해본다.
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) 어노테이션을 걸어 줌으로써 데이터 동기화 이슈에 대해서도 해결할 수 있게 되었다.