RestControllerAdvice 관련 트러블 슈팅 : 에러 응답관련 트러블 슈팅.

조대훈·2024년 11월 15일
post-thumbnail

문제 상황

Modal 컴포넌트 리팩토링 이후, 확인 Modal을 사용하는 삭제 기능에서 오류 발생

발생한 오류 종류

  1. 게시물 삭제
    • 500 에러 반환
    • 실제로는 게시물이 삭제됨
    • Modal이 닫히지 않음

2. 게시물 삭제

현재 전역예외처리 핸들러를 사용하고 있었고 반환된 에러 내용은 대강
status:INTERNAL_SEVER_ERROR
message:POST NOT FOUND
로 반환되고 있었다. modal 이 닫히지 않고
redirect 처리는 이루어지지 않고 있었다.

백엔드 예외처리 로직

해당 예외처리 핸들러에 예외처리 클래스가 몇 개 더 명시 되어 있었지만 Post Service 에서 orElseThorw 하고 있는 RuntimeExcetpion에 대한 예외처리 클래스는 명시 하지 않아 모든 예외처리를 일괄 담당하는 아래 Exception.classINTERNAL_SEVER_ERROR를 반환했다.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception e) {
        ErrorResponse error = ErrorResponse.builder()
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .message(e.getMessage())
                .redirect("/")
                .build();
        return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }

2-1 백엔드 삭제 로직

아래 백엔드 삭제 컨트롤러와 서비스 로직을 살펴 보면 remove가 동작하게 되면 postService.remove(id) 부분에서 post Entity를 삭제 처리 후에 삭제된 이미지 명을 반환하기 위해 oldFileNames 객체에서 postService 에서 get 요청을 하고있다.
따라서 실제 게시물 삭제 까지는 이루어지지만 500Error를 반환 하는 것이다.


#컨트롤러 
    @DeleteMapping("/{id}")
    public ResponseEntity<?> remove(
            @PathVariable Long id,
            @AuthenticationPrincipal UserDTO principal) {

            postService.checkAuthorization(id, principal.getEmail());
            postService.remove(id);

            List<String> oldFileNames = postService.get(id).getImageList();
            return ResponseEntity.ok(Map.of("result", "success", "oldFileNames", oldFileNames));
    }
    
    #서비스 삭제 로직
       @Override
    @Caching(evict = {@CacheEvict(value = "post", key = "#id"), @CacheEvict(value = "postComments", key = "#id")})
    @Transactional
    public void remove(Long id) {

        Post post = postRepository.findById(id).orElseThrow(()-> new RuntimeException("Post not found"));
        deleteFiles(post.getImageList());
        postRepository.deleteById(id);
    }
    
    
    #서비스 겟 로직
        @Override
    public PostDTO get(Long id) {

        Post post = postRepository.findById(id).orElseThrow(() -> new RuntimeException("Post not found"));
        PostDTO dto = entityToDTO(post);
        dto.setImageList(post.getImageList().stream()
                .map(image -> addPrefix(image.getFileName()))
                .collect(Collectors.toList()));
        dto.setExistingImageUrls(new ArrayList<>(dto.getImageList()));
        return dto;
    }

보시다시피 get 에서도 해당 post 를 찾을 수 없으면 예외를 반환하도록 설정되어 있다.

2-2 게시물 삭제 프론트 로직

프론트 코드

   const delMutation = useMutation({
        mutationFn: (postId) => deleteOne(postId),
        onSuccess: () => {
            closeModal();

            // 삭제된 게시물의 캐시는 제거하되 refetch는 하지 않음
            queryClient.removeQueries({
                queryKey: ['post', postId],
                refetchType: 'none'
            });

            // 리스트는 refetch하여 최신 상태 유지
            queryClient.invalidateQueries({
                queryKey: ['post/List']
            });
            moveToList();
        },

        onError: (error) => {
            console.error("삭제 중 에러 발생:", error);
            alert("게시물 삭제 중 오류가 발생했습니다.");
            closeModal();
        }
    });

removeQueriesrefetch를 유발하지 않지만 명시적으로 적어 주었다. inavalidateQueries 를 통해 post/Listrefetch 해 해당 게시물이 지워진 List 를 새로 받은 후 moveToList 가 작동하도록 설정 했다.

프론트 axios.interceptor 부분

정리를 하면서 프론트 부분에서 INTERNAL_SERVER_ERROR 발생시엔 Redirect 처리를 하지 않은 부분도 확인 했다.(추후 수정 예정)

const handleErrorResponse = ({message, redirect, errorStatus}) => {
    console.error(`Error: ${errorStatus}, Message: ${message}`);

    switch (errorStatus) {
        case ERROR_TYPES.AUTHENTICATION_REQUIRED:
        case ERROR_TYPES.AUTHENTICATION_FAILED:
        case ERROR_TYPES.INSUFFICIENT_AUTHENTICATION:
            handleRedirect(redirect, REDIRECT_PATHS.LOGIN);
            break;
        case ERROR_TYPES.ACCESS_DENIED:
            handleRedirect(redirect, REDIRECT_PATHS.ERROR);
            break;
        case ERROR_TYPES.NEED_PROFILE_UPDATE:
            handleRedirect(redirect, REDIRECT_PATHS.MODIFY);
            break;
    }
};

2-3

최종 수정 코드

    @DeleteMapping("/{id}")
    public ResponseEntity<?> remove(
            @PathVariable Long id,
            @AuthenticationPrincipal UserDTO principal) {

        postService.checkAuthorization(id, principal.getEmail());

        List<String> oldFileNames = postService.get(id).getImageList();

        postService.remove(id);

        return ResponseEntity.ok(Map.of("result", "success", "oldFileNames", oldFileNames));
    }

단순히 필요한 정보를 모두 조회 후 삭제하는 것 만으로 오류 해결. 간단한 문제를 리액트 쿼리 관련 문제인 줄 알고 헤맸다.

profile
백엔드 개발자를 꿈꾸고 있습니다.

0개의 댓글