티스토리 뷰

 

※ 배경 설명

프로젝트 : Spring 기반 방탈출 예약 시스템
자바 버전 : 17
스프링부트 버전 : 3.2.4
스프링 버전 : 6.x
데이터 저장 방식 : HashMap 기반 인메모리

 

예외처리

 

초기 예약 삭제 기능을 개발할 때, 클라이언트로부터 예약 id값을 이용해 인메모리 저장소에서 조회 후 반환하도록 로직을 구성했다.

자료구조는  HashMap 이고, stream의 filter를 이용해 조건을 조회 후 List로 반환한다.

 

해당 로직에 대한 코드리뷰는 아래와 같은데, 조회할 id 값이 없는 경우 예외처리가 필요하다는 것을 놓쳤다. (기본적으로 유효성 검증은 필수인데 이런 부분에서 실수를 하다니... 분발해야지) 

id에 해당되는 원소가 존재하지 않는 경우는 어떻게 될까요? 사용자가 이를 인지할 수 있도록 해주면 더욱 좋겠네요!

 

AS-IS

@RestController
public class ReservationController {

     private static final HashMap<Long, Reservation> reservations = new HashMap<>();
     private final Reservation reservation1 = new Reservation("제이슨", "2023-08-05", "15:40");
     private final Reservation reservation2 = new Reservation("심슨", "2023-08-05", "15:40");
	 
     @DeleteMapping("/reservations/{id}")
	 public ResponseEntity deleteReservation(@PathVariable Long id) {
         reservations = reservations.stream()
                 .filter(reservation -> !reservation.getId().equals(id))
                 .toList();
    	return ResponseEntity.ok().build();
	 }
}

 

 

TO-BE 1차 리팩토링

코멘트 내용에 맞춰 인메모리 저장소에 해당 id 값이 있는지 조회한다.

HashMap의 containsKey(Long id) 메서드를 이용해 저장소에 해당 id(key) 값이 있는지 확인한다.

조회 값이 없다면 없다면 notFound(404)를 반환하고, 조회 값이 있다면 HashMap에 해당 id에 해당하는 value를 삭제한다.

@RestController
public class ReservationController {

    private static final HashMap<Long, Reservation> reservations = new HashMap<>();
    private final Reservation reservation1 = new Reservation("제이슨", "2023-08-05", "15:40");
    private final Reservation reservation2 = new Reservation("심슨", "2023-08-05", "15:40");
    
    @DeleteMapping("/reservations/{id}")
    public ResponseEntity deleteReservation(@PathVariable Long id) {
        if (!reservations.containsKey(id)) {
            return ResponseEntity.notFound().build();
        } else {  
        	reservations.remove(id);
            return ResponseEntity.ok().build();
        }
    }
}

 

 

TO-BE 2차 리팩토링

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
        return new ResponseEntity<>("Invalid argument provided : " + ex.getMessage(), HttpStatus.BAD_REQUEST);

    }
}

 

@RestController
public class ReservationController {

    private static final HashMap<Long, Reservation> reservations = new HashMap<>();
    private final Reservation reservation1 = new Reservation("제이슨", "2023-08-05", "15:40");
    private final Reservation reservation2 = new Reservation("심슨", "2023-08-05", "15:40");
    
    @DeleteMapping("/reservations/{id}")
    public ResponseEntity deleteReservation(@PathVariable Long id) {
        if (!reservations.containsKey(id)) {
            throw new IllegalArgumentException("해당 예약이 존재하지 않습니다.");
        } 
        reservations.remove(id);
        return ResponseEntity.ok().build();
        }
    }
}

 

위 1차 리팩토링에서 나아가 @RestControllerAdvice를 이용해보기로 했다. GlobalExceptionHandler 클래스를 만들어 어노테이션을 추가하고, IllegalArgumentException이 발생했을 때, 클라이언트에 반환할 에러문구를 작성하고, ResponseEntity에 담아 반환해 준다.

이를 통해 Controller뿐만 아니라 Controller - Service - Repository 내에 발생한 IllegalArgumentException 예외를 핸들링할 수 있게 되었다

 

@RestControllerAdvice와 @ControllerAdvice 정리

우선 GlobalExceptionHandler class를 생성하고 @RestControllerAdvice 어노테이션을 붙여준다.
이후 IllegalArgumentException 예외가 발생했을 경우 클라이언트에 반환할 로직을 구현한다. 
이를 통해 @Controller 어노테이션이 있는 모든 클래스의 예외 발생 시 핸들링을 할 수 있다.

특정 package나 class에만 핸들링을 할 경우 아래의 selector를 추가하여 범위를 제한할 수 있다.
- @RestControllerAdvice(baskPackageClasses = 해당 클래스명.class)
- @RestControllerAdvice(basePackages = "패키지명")

@RestControllerAdvice와 @ControllerAdvice의 차이는 아래와 같다
- @RestConrollerAdvice = @ControllerAdvice + @ResponseBody

 

 

아래는 @ControllerAdvice 주석의 일부이다.

 

By default, the methods in an @ControllerAdvice apply globally to* all controllers. Use selectors such as  #annotations,*  #basePackageClasses, and #basePackages or its alias*  #value to define a more narrow subset of targeted controllers.* If multiple selectors are declared, boolean OR logic is applied, meaning* selected controllers should match at least one selector. Note that selector checks* are performed at runtime, so adding many selectors may negatively impact* performance and add complexity.

 

 

이상 전달 끝~!

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함