프로그래밍을 할 때 Null 데이터를 잘 처리하는 것은 매우 중요하다.
프레임워크 혹은 라이브러리를 활용해 Null 을 허용하지 않음으로써 null에 접근하는 오류를 사전에 방지할 수 있다. 다음 라이브러리에서 어노테이션 활용이 가능하다.
Null 처리를 돕는 몇가지 라이브러리에 대해 정리해보려고 한다.
Javax Validation (JSR-303/JSR-380) | @NotNull | null을 허용하지 않음 |
Lombok (lombok.NonNull) | @NonNull | null이 들어오면 NullPointerException 발생 |
Spring (org.springframework.lang) | @NonNull | null을 허용하지 않음 (Spring 전용) |
1. Javax Validation (javax.validation.constraints)
- 대표적인 어노테이션: @NotNull, @NotNull, @Size, @Pattern, @Min, @Max
- JSR-303 (Bean Validation 1.0), JSR-380 (Bean Validation 2.0)에서 표준화 되어서 사용된다.
- 런타임 시점에 검증이 수행되며, 검증 실패 시 예외 발생 (MethodArgumentNotValidException)이 일어난다.
- 유효성 검사를 위해 Spring의 @Valid 또는 @Validated와 함께 사용한다.
- Spring과 Hibernate Validator를 함께 사용하여 Controller에서 요청 값을 검증할 수 있다. : MVC 요청 바디 검증시 활용 (DTO 필드 검증)
- 메서드 파라미터를 검증하는데는 한계가 있다.
2. Lombok (lombok.NonNull)
- 대표적인 어노테이션: @NonNull
- Lombok이 자동으로 null 체크를 추가하여, 런타임에서 NullPointerException을 발생시킨다.
- 따라서 컴파일 타임이 아니라 런타임에서 검증이 이루어진다.
- Spring Validation과 달리 HTTP 요청 검증보다는 객체의 생성자나 메서드 호출 시 검증하는 용도로 사용된다.
- null 관련된 부분만 검증할 수 있다.
🔹 예제
import lombok.NonNull;
public class User {
private String name;
public User(@NonNull String name) { // name 이 null이면 NullPointerException 발생
this.name = name;
}
}
@NonNull을 사용하면, 내부적으로 다음과 같이 변환된다.
public User(String name) {
if (name == null) {
throw new NullPointerException("name is marked non-null but is null");
}
this.name = name;
}
3. Spring (org.springframework.lang)
- 대표적인 어노테이션: @NonNull, @Nullable
- Spring 5부터 도입된 어노테이션으로, Spring 내부에서 null 체크를 보조하는 용도로 사용된다.
- JSR-303 기반의 @Valid, @Validated 외에도 커스텀한 검증 로직을 추가할 수 있다.
- Spring 에서 제공하는 만큼, Spring MVC 와 연동이 강하며 DTO 필드를 검증할때 유용하다. 서비스 계층을 검증할 때도 사용할 수 있다.
- 커스텀한 검증 로직을 사용하는 경우, Validator 를 implement 해서 구현해야한다.
예제
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
@Component
public class UserValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return UserDto.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
UserDto userDto = (UserDto) target;
if (userDto.getPassword() != null && !userDto.getPassword().equals(userDto.getConfirmPassword())) {
errors.rejectValue("confirmPassword", "Passwords do not match", "비밀번호가 일치하지 않습니다.");
}
}
}
@RestController
@RequestMapping("/users")
public class UserController {
private final UserValidator userValidator;
public UserController(UserValidator userValidator) {
this.userValidator = userValidator;
}
@PostMapping
public ResponseEntity<String> createUser(@RequestBody UserDto userDto, Errors errors) {
userValidator.validate(userDto, errors);
if (errors.hasErrors()) {
return ResponseEntity.badRequest().body(errors.getAllErrors().toString());
}
return ResponseEntity.ok("User created successfully");
}
}
@NonNull, @Nullable 예제
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
public class SpringExample {
public void process(@NonNull String input) {
// input이 null이면 IDE에서 경고
}
public void display(@Nullable String message) {
// message는 null 가능
}
}
'개발 > TIL' 카테고리의 다른 글
@EntityGraph 어노테이션 (0) | 2025.02.24 |
---|---|
로그인 시 refresh token 을 사용하는 이유와 frontend - backend에서 주의할 점 (3) | 2025.02.23 |
단 한 줄로 spring boot 애플리케이션 성능 올리기 - gzip (0) | 2025.02.21 |
Java의 Optional<T>로 안전하게 null 데이터 처리하기 (0) | 2025.02.20 |
default 를 이용해서 interface 에 메서드 정의하기 (0) | 2025.02.19 |