본문 바로가기
내가 배운 것들/문제 해결

[SpringBoot] JPA를 이용한 Delete가 실행되지 않는 현상

by Zabee52 2021. 12. 27.

화가 날 뻔한 상황

코드에는 에러가 없다. 근데 Delete가 실행이 안 된다. 왜 안 되지? 난 화가 날 뻔했다. 화가 나기 전에 해결이 되어서 다행이다.

 

코드는 다음과 같다.

public DictLikeResponseDto likeDict(UserDetailsImpl userDetails, Long dictId) {
        // 로그인 체크
        ValidChecker.loginCheck(userDetails);
        User user = jwtAuthenticateProcessor.getUser(userDetails);
        Dict dict = getSafeDict(dictId);
        
        /*
            1. 좋아요 중일 시 : 좋아요 취소
            2. 좋아요 중이 아닐 시 : 좋아요
         */
        boolean isLike = false;
        if (isDictLike(dict, userDetails)) {
            DictLike dictLike = getSafeDictLike(user, dict);
            dictLikeRepository.deleteById(dictLike.getDictLikeId());
        } else {
            DictLike dictLike = DictLike.builder()
                    .dict(dict)
                    .user(user)
                    .build();
            dictLikeRepository.save(dictLike);
            isLike = true;
        }
        return DictLikeResponseDto.builder()
                .result(isLike)
                .build();
    }
private boolean isDictLike(Dict dict, UserDetailsImpl userDetails) {
        // 1. 로그인하지 않았으면 무조건 false.
        // 2. dictLikeList 가 비어있으면 무조건 false.
        // 3. 사용자의 dictLike 목록에 해당 dict 가 포함되어있지 않으면 false.
        // 4. 포함되어있을시 true.
        if (userDetails == null) {
            return false;
        }
        for (DictLike dictLike : dict.getDictLikeList()) {
            if (dictLike.getUser().getUsername().equals(userDetails.getUsername())) {
                return true;
            }
        }

        return false;
    }

기능에 대한 설명을 하자면, 현재 좋아요 중이 아닐 시 좋아요 처리를 하고, 좋아요 중이면 취소 처리하는, SNS에서 자주 볼 수 있는 그 기능이다. 근데 이 기능을 시행하는 중, 좋아요 취소를 담당하는 

dictLikeRepository.deleteById(dictLike.getDictLikeId());

 

코드가 실행이 되지 않는 상황이었다. 에러메세지 하나 없이 말이다. 그야말로 화가 나는 상황이라고 볼 수 있다.

요즘 화가 좀 자주 날 뻔하네?

여기서 생각 가지치기를 해봤는데, 일단 에러메세지가 뜨지 않았다는 것은 일단 문제 없이 쿼리를 실행은 했다는 거고, 그렇다면 그냥 이 행동이 DB에 반영이 되지 않은 것 뿐 아닐까? 라는 생각이 들었다. 그러면 왜 반영이 안 되지? 뭔가 문제가 있나? 혹시 관계?

 

이하부터는 내 추측에 따른 뇌피셜이니 이런 생각을 할 수도 있구나 하고 넘어가주길 바란다.

public class Dict extends Timestamped {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long dictId;
    
    // ...
    
    @OneToMany(mappedBy = "dict", cascade = CascadeType.ALL)
    private final List<DictLike> dictLikeList = new ArrayList<>();
}

Dict 엔티티는 현재 OneToMany로 이 사전의 좋아요 리스트를 가지고 있다. 이 리스트는 호출되는 시점에 Lazy fetch를 시행하게 되고, fetch를 시행한 시점에서 이 리스트는 DB로부터 독립 된다. 실제 DB의 변경 상태를 리스트가 반영하지 못 하게 되는 것이다. 지금 이 상태에서 원래 DB의 값이 변경되면, 데이터의 무결성이 깨진다. Dict.dictLikeList는 신뢰할 수 없는 데이터가 되는 것이다. 내가 생각하기엔, JPA가 이러한 상황에서 데이터 무결성이 깨지지 않도록 어떤 장치를 해둔 것이 아닐까? 그렇기 때문에 DML 작업을 무시하는 것이 아닐까? 하는 생각이 들었다.

 

그래서 한 번 Dict.dictLikeList를 참조하지 않는 코드를 짜보기로 했다.

 

private boolean isDictLike(Dict dict, UserDetailsImpl userDetails) {
        if (userDetails == null) {
            return false;
        }
        
        /*
        for (DictLike dictLike : dict.getDictLikeList()) {
            if (dictLike.getUser().getUsername().equals(userDetails.getUsername())) {
                return true;
            }
        }
        */
        
        Optional<DictLike> dictLike = dictLikeRepository.findByUserAndDict(jwtAuthenticateProcessor.getUser(userDetails), dict);

        return dictLike.isPresent();
    }

 

결과는 잘 된다. 추측에 의해 풀어나간 문제였는데, 잘 풀렸다. 앞으로 이 점을 유의해서 문제를 풀어야겠다. 더 깊게 들어가보자면 영속성 컨텍스트... 그런 관계에 의한 복잡한 이해관계가 얽히게 되겠지만, 당장은 프로젝트을 시행하는 기간이기 때문에 이 부분에 대한 공부는 추후 추가로 진행하기로 한다.

 

 

 

22. 1. 2 추가)

생각해보니, Transactional 선언을 해주고 List의 값도 지워주도록 설정하는 코드를 구성한다면 문제가 없을 수도 있지 않을까? 라는 생각이 든다. 그냥 문득 해 본 생각이다.

댓글