티스토리 뷰

 

 

1. 정리하게된 배경

초기 도메인 필드의 유효성을 검증하고자, 아래와 같이 @NonNull을 이용해서 구현했다. 초기 프로젝트 환경에서 validation 의존성이 추가되지 않아서, Spring 에서 제공되는 @NonNull이 있길래 사용했다. 클라이언트에서 입력받았을 때 해당 필드값이 비어있으면 예외를 처리가하기 위함이었는데 이에 대해 리뷰어님께 코멘트를 받았다. 해당 어노테이션에 대한 이해가 부족한 것으로 판단해서 코멘트를 주신것 같아 이번기회에 정리해보기로 했다.

NonNull 과 NotNull, NotBlank 는 서로 어떤 차이가 있을까요?
이들의 차이를 알아보고 적용해보는 것도 좋을거 같습니다

AS-IS 코드

import org.springframework.lang.NonNull;
import roomescape.reservationTime.ReservationTime;

import java.time.LocalDate;
import java.util.Objects;

public class Reservation {

    private Long id;
    @NonNull
    private String name;
    @NonNull
    private String date;
    @NonNull
    private ReservationTime reservationTime;

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getDate() {
        return date;
    }

    public ReservationTime getReservationTime() {
        return reservationTime;
    }

    public Reservation(@NonNull String name, @NonNull String date, @NonNull ReservationTime reservationTime, ReservationPolicy reservationPolicy) {
        this(null, name, date, reservationTime, reservationPolicy);
    }

    public Reservation(Long id, @NonNull String name, @NonNull String date, @NonNull ReservationTime reservationTime, ReservationPolicy reservationPolicy) {
        this.id = id;
        if(reservationPolicy.validateName(name)) {
            throw new IllegalArgumentException("예약자 이름에 특수문자가 포함되어 있습니다.");
        }
        this.name = name;

        if(reservationPolicy.validateDate(date)) {
            throw new IllegalArgumentException("예약 날짜 형식이 올바르지 않습니다.");
        }
          this.date = date;
        this.reservationTime = reservationTime;

    }

}

 

2. 어노테이션 별 특징 비교 테이블

  @Nonnull @NonNull @NotNull @NotEmpty @NotBlank
null 확인 O O O O O
"" 확인       O O
"  " 확인         O
컴파일 시 확인 O O X X X
런타임 시 확인 X X(Lombok 가능) O O O
용도 변수, 파라미터 ,필드, 메서드 반환 값 등에 null 이 아님을 보장 변수, 파라미터 ,필드, 메서드 반환 값 등에 null 이 아님을 보장 변수, 파라미터 ,필드, 메서드 반환 값 등에 null 이 아님을 보장 Collection, array, String[] 등에 null이 아니고, 비어있지 않음을 보장 문자열이 null이 아니고, 길이가 0이 아니며, 공백 문자가 아님을 보장
적용대상 모든 객체 타입 모든 객체 타입 모든 객체 타입 문자열, 컬렉션, 배열, 맵 등의 참조객체 문자열
특징 컴파일 타임 null 검사 목적 코드 분석 도구나 IDE에서 지원 Lombok의 경우, null 체크 로직을 자동으로 생성

Spring의 @NonNull은 null 안전성을 보장하기 위해 사용 및 특정한 검증 로직을 포함하지 않습니다.
Bean Valdation API(JSR-380)와 함께 사용. 런타임 시 검증 로직 추가 가능 null 검사,
길이가 0인지 검사
null 검사와 빈 문자열 검사 및 문자열이 공백 문자로만 이루어 졌는지 검사
제공 패키지 javax.annotation Spring, Lombok,
jetBrain
javax.validation.constraints javax.validation.constraints javax.validation.constraints

 

위와 같이 각 어노테이션 별 특징과 용도 등을 정리했다. 테이블을 정리하면서 리뷰어님이 왜 코멘트하셨는지 알 것 같았다.

우선 @NonNull과 @Nonnull의 경우 런타임 시 해당 유효성 검증을 수행하지 않는다. 즉, 내가 적용한 어노테이션은 런타임에서 수행하지 않는다는 얘기가 된다. 런타임 시에 유효성을 검증하기 위해서는 @NotNull, @NotEmpty, @NotBlank 중에 사용해야하고, 특히 문자열의 경우 @NotBlank 어노테이션을 사용해야, null & "" & " " 모두를 체크할 수 있다는 것을 알게 되었다. 

 

위 내용을 기반하여, 코드 리팩토링을 수행했고, Reservation 엔티티에 있던 @NonNull 을 모두 제거했고, 클라이언트에 값을 받아오는 ReservationRequestDto에 @NotBlank 를 적용했다. 또한, Controller에 해당 dto에 @Valid를 적용했고, GlobalExceptionHandler.class 에 예외 핸들링을 위한 코드도 추가했다. 일련의 과정은 아래추가로 정리했다.

 

3. 유효성 검증을 위한 적용 과정

1. gradle을 통해 의존성을 추가한다.

dependencies {
	...
    implementation 'org.springframework.boot:spring-boot-starter-validation' // 추가 의존성
	...
}

 

2. 유효성 검증을 할 대상필드에 어노테이션을 추가한다.

public class ReservationThemeRequestDto {
    @NotEmpty(message = "테마명을 입력 해주세요")
    private String name;

    @NotEmpty(message = "테마에 대한 설명을 입력해주세요")
    private String description;

    @NotEmpty(message = "썸네일 url 을 입력해주세요")
    private String thumbnail;

}

 

 

3. @Valid 어노테이션이 활성화 되는데, @Valid는 controller의 각 함수의 파라미터에 붙여준다.

    @PostMapping("/reservations")
    public ResponseEntity<ReservationResponseDto> createReservation(@Valid @RequestBody ReservationRequestDto reservationRequestDto) {
        final ReservationResponseDto responseDto = reservationService.save(reservationRequestDto);
        return ResponseEntity.ok().body(responseDto);
    }

 

4. 예외 핸들링을 위해서, @ExceptionHandler(MethodArgumentNotValidException.class) 어노테이션이 붙은 핸들링 함수를 정의해줘야한다. 

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error ->
                errors.put(error.getField(), error.getDefaultMessage())
        );
        return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
    }
}

 

 

AS-IS 코드

import org.springframework.lang.NonNull;
import roomescape.reservationTime.ReservationTime;

import java.time.LocalDate;
import java.util.Objects;

public class Reservation {

    private Long id;
    @NonNull
    private String name;
    @NonNull
    private String date;
    @NonNull
    private ReservationTime reservationTime;

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getDate() {
        return date;
    }

    public ReservationTime getReservationTime() {
        return reservationTime;
    }

    public Reservation(@NonNull String name, @NonNull String date, @NonNull ReservationTime reservationTime, ReservationPolicy reservationPolicy) {
        this(null, name, date, reservationTime, reservationPolicy);
    }

    public Reservation(Long id, @NonNull String name, @NonNull String date, @NonNull ReservationTime reservationTime, ReservationPolicy reservationPolicy) {
        this.id = id;
        if(reservationPolicy.validateName(name)) {
            throw new IllegalArgumentException("예약자 이름에 특수문자가 포함되어 있습니다.");
        }
        this.name = name;

        if(reservationPolicy.validateDate(date)) {
            throw new IllegalArgumentException("예약 날짜 형식이 올바르지 않습니다.");
        }
          this.date = date;
        this.reservationTime = reservationTime;

    }

}

 

 

느낀점

어노테이션의 용도와 목적을 알고 사용합시다 :)

공지사항
최근에 올라온 글
최근에 달린 댓글
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
글 보관함