HTTP 상태코드 / Exception 관리

GlobalExceptionHandler를 통한 5가지 응답 코드 관리
SHIN's avatar
Aug 24, 2024
HTTP 상태코드 / Exception 관리
 
RuntimeException의 자식들
→ Exception400 ~ 500 (Handler 분기 시킴)
 

1. 상태코드

💡
10X → wait : 서버가 바쁘거나 트래픽이 몰렸을 때. (프로세스는 계속 진행 상태)
20X → GOOD (201 = create) 성공적으로 요청됐으며, 서버가 새 리소스를 작성.
30X → 요청한 거 외에 다른걸 돌려줌 → 요청 완료를 위해 추가 작업 조치 필요 (Redirection)
40X → 클라이언트 오류 : 요청의 문법이 잘못됐거나 요청 처리 X
50X → 서버 오류 (에러 로그 남김 → why? 계속 모니터링 하는게 아니기에, 안하면 디버깅 힘듦)

1-1. 전체적인 구조

💡
Throwable → getMessage(); Exception RuntimeException Exception400
 

2. Exception 관리

2-1. GlobalExceptionHandler

💡
→ 모든 예외를 중앙에서 처리하여 일관된 응답 제공. 각 예외에 대해 별도로 작성된 핸들러 메소드에서 발생한 예외 처리
package shop.mtcoding.blog.core.error; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import shop.mtcoding.blog.core.error.ex.*; import shop.mtcoding.blog.core.util.Script; @RestControllerAdvice // 모든 throw가 이쪽으로 날라옴, 무조건 데이터로 응답 public class GlobalExceptionHandler { // 유효성 검사 실패 (잘못된 클라이언트의 요청) @ExceptionHandler(Exception400.class) public String ex400(Exception e) { return Script.back(e.getMessage()); } // 인증 실패 (클라이언트가 인증 없이 요청했거나, 인증 도중 실패 @ExceptionHandler(Exception401.class) public String ex401(Exception e) { return Script.href("인증되지 않았습니다", "/login-form"); } // 권한 실패 (인증은 되어있으나, 삭제하려는 게시글이 내가 적은 글이 아님) @ExceptionHandler(Exception403.class) public String ex403(Exception e) { return Script.back(e.getMessage()); } // 서버에서 리소스(자원)를 찾을 수 없을 때 @ExceptionHandler(Exception404.class) public String ex404(Exception e) { return Script.back(e.getMessage()); } // 서버에서 심각한 오류 발생시 (알고 있을 때) @ExceptionHandler(Exception500.class) public String ex500(Exception e) { return Script.back(e.getMessage()); } // 서버에서 심각한 오류 발생시 (모를 때) @ExceptionHandler(Exception.class) public String ex(Exception e) { return Script.back(e.getMessage()); } }

2-2. Test 코드 (게시글 삭제 테스트)

💡
→ 예외가 발생할 경우와 발생하지 않을 경우의 처리 확인
 
  • try-catch 사용 테스트
@Test public void deleteById_test() { int id = 6; try { boardRepository.deleteById(id); boardRepository.findById(id); } catch (Exception e) { Assertions.assertThat(e.getMessage()).isEqualTo("게시글 id를 찾을 수 없습니다"); } } // deleteById 메소드 호출 후, 해당 게시글이 삭제되었는지 확인하기 위해 findById 메소드 호출. // 만약 게시글이 존재하지 않으면 Exception404가 발생해야 하며, 이를 try-catch 블록으로 잡아서 테스트.
💡
결과 → 예외가 정상적으로 발생하고, 기대한 대로 메시지를 반환하는지 확인. 이는 예외 처리가 의도한 대로 동작하고 있음을 보여줌
 
 
  • Service & Repository 레이어에서의 예외 처리 및 트랜잭션 관리
@Transactional public void 게시글삭제(int id, User sessionUser) { Board board = boardRepository.findById(id); if (sessionUser.getId() != board.getUser().getId()) { throw new Exception403("권한이 없습니다"); } boardRepository.deleteById(id); } // '게시글삭제' 메소드는 먼저 게시글의 존재 여부를 확인하고, 권한이 있는지 검사한 후에 삭제 작업. // 각 단계에서 조건이 만족되지 않으면 적절한 예외를 발생시킴
💡
트랜잭션 관리 : @Transactional 어노테이션을 사용하여 DB 작업이 실패할 경우, 롤백해서 데이터 일관성 유지.
 
 
  • 게시글 조회 Repository (BoardRepositoryfindById 메소드)
public Board findById(int id) { Query query = em.createQuery("SELECT b FROM Board b JOIN fetch b.user u WHERE b.id = :id", Board.class); query.setParameter("id", id); try { Board board = (Board) query.getSingleResult(); return board; } catch (Exception e) { e.printStackTrace(); throw new Exception404("게시글 id를 찾을 수 없습니다"); } } // 'findById' 메소드는 JPQL을 사용하여 게시글을 조회, 게시글이 존재하지 않으면 Exception404 발생 시킴.
💡
예외 처리 : catch 블록에서 예외 캐치 후, stackTrace를 출력 후 커스텀 예외를 다시 던짐.
 
 
  • Service 테스트
@Test public void 게시글삭제_test() { int id = 1; User sessionUser = new User(); sessionUser.setId(2); // 권한이 없는 사용자 try { boardService.게시글삭제(id, sessionUser); } catch (Exception e) { Assertions.assertThat(e).isInstanceOf(Exception403.class); } } // '게시글삭제' 메소드가 사용자의 권한을 확인하여 권한이 없을 경우 Exception403이 발생하는지 테스트
 
  • Repository 테스트
@Test public void findById_test() { int id = 1; Assertions.assertThatThrownBy(() -> boardRepository.findById(id)) .isInstanceOf(Exception404.class) .hasMessage("게시글 id를 찾을 수 없습니다"); } // 'findById' 메소드가 존재하지 않는 게시글 조회시, 적절한 예외를 발생시키는지 확인
 

결론

 
  • try-catch의 중요성
💡
try-catch 블록은 예외 상황을 처리 및, 프로그램이 중단되지 않고 정상적으로 동작하도록 도움. 특히 테스트 과정에서는 예상되는 예외 상황 확인 후 코드가 제대로 작동하는지를 검증.
 
  • 예외 처리 & 단위 테스트
💡
각 예외 처리 로직을 단위 테스트와 연관 지어 테스트함으로써 코드의 안정성을 높임. 각 단계에서 발생할 수 있는 예외 상황을 고려하여 테스트 작성 후, 이를 통해 코드의 완성도를 높이는 것이 중요.
Share article

SHIN