문제발생
스프링부트와 JPA로 게시판을 구현하고 있었는데 Repositroty에서 예외가 발생했다. 보통 Repository에서 예외가 발생하면 Controller까지 Exception이 전달되어 내가 원하는 상태 메시지를 ResponseEntity로 반환해야한다.
@PostMapping("/edit/{boardId}")
public ResponseEntity<String> boardEditUpdate(@PathVariable("boardId") Long boardId,
@RequestBody BoarRequestDTO bDTO,
HttpServletRequest request) {
Long memberId = (Long) request.getAttribute("memberId");
String title = bDTO.getTitle();
String content = bDTO.getContent();
BoardDetailDTO board = boardService.findBoard(boardId);
if (!board.getMember_id().equals(memberId)){
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("게시물을 수정할 권한이 없습니다.");
}
try {
boardService.updateBoard(boardId, title, content);
log.info("게시판 수정 성공");
return ResponseEntity.ok("게시물이 수정되었습니다.");
} catch (NotFindBoardException e){
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("게시물을 찾을 수 없습니다.");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("서버 내부 오류로 수정에 실패했습니다.");
}
}
게시물을 찾을 수 없을 때, 기타 데이터베이스 에러로 에러가 발생했을 때 내가 정해둔 에러코드와 메시지를 클라이언트에 반환하고 싶다.
하지만 Repository에서 발생한 에러가 Controller까지 전달되어 catch에 NotFindBoardException, Exception에 에러가 잡혀서 클라이언트에게 전달되어야 하는데, Controller에서 예외가 잡히지 않고 스프링 자체에서 다음과 같은 Response가 반환되었다.
"timestamp": "2024-05-29T14:24:39.517+00:00",
"status": 500,
"error": "Internal Server Error",
"trace": "creativeprj.creative.Exception.NotFindBoardException: 해당 게시물을 찾을 수 없습니다.\n\tat creativeprj.creative.Repositrory.BoardRepository.findOneBoard(BoardRepository.java:48)
클라이언트에게 API 스펙을 보장하는게 중요한데 위와 같이 응답이 온 것이다.
일관된 API 스펙을 반환 해주어야 하는데 불규칙한 API 스펙이 전달되는 것이다.
원인
@Transactional
public void updateBoard(Long boardId, String boardName, String content) {
try {
Board board = em.find(Board.class, boardId);
board.setBoard_content(content);
board.setBoard_name(boardName);
} catch (NotFindBoardException e) {
throw new NotFindBoardException("게시물을 찾을 수 없습니다.");
}
}
위 코드는 BoardRepositroy에서 게시판을 수정하는 로직이다. 만약에 위 로직에서 예외가 발생한다면 NotFindBoardException이 발생해서 Controller로 넘어가 사용자에게 "게시물을 찾을 수 없습니다" 메시지가 반환 되어야 하는데 스프링에서 자체 에러를 발생 시켜버린다.
이유는 수정, 삭제 로직은 JPA에서 트랜잭션이 발생하는데, 트랜잭션이 실패하게 되면 JPA는 자동으로 롤백하게 된다. 롤백이 발생하면 Controller까지 예외가 전달되지 않고 스프링 자체에서 에러를 처리해버린다.
해결
롤백을 못하게 할 수는 없는 노릇이고, 어떻게 클라이언트에게 정해진 API 응답스펙을 전할 수 있을까?
이는 전역 에러 핸들러로 예외를 잡아주면 된다.
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NotFindBoardException.class)
public ResponseEntity<String> handleNotFindBoardException(NotFindBoardException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}
}
@ControllerAdvice를 사용하면 스프링 전역에서 발생하는 에러를 처리할 수 있다.
NotFindBoardException을 반환한다면 위에서 처럼 스프링이 자체적으로 예외를 반환하지 않고, 일관성 있게 예외 메시지를 처리할 수 있다. 이러면 JPA에서 롤백이 되어도 스프링 자체에서 에러를 반환하는 대신 GlobalExceptionHandler에 정의한대로 예외를 발생시킨다.
전역예외를 정의하기 전
전역예외를 정의한 후
'Spring > JPA' 카테고리의 다른 글
[JPA] DataIntegrityViolationException 에러 원인과 해결 (2) | 2024.06.06 |
---|