@Valid, @Validated 입력값 검증과 StringUtils

양성준·2025년 3월 18일

스프링

목록 보기
14/49

@Valid, @Validated를 사용하면, 파라미터 받고 if문으로 이게 null인지 검증하는 로직이 필요없다! + 입력값 검증은 Controller단에서 이뤄지면 됨!

    private void validateUserField(CreateUserParam createUserParam) {
        if (Stream.of(createUserParam.username(), createUserParam.email(), createUserParam.password())
                .anyMatch(field -> field == null || field.isBlank())) {
            throw new IllegalArgumentException("username, email, password는 필수 입력값입니다.");
        }
    }

1. @Valid vs @Validated: 입력값 검증

@Valid

  • @Valid는 Java 표준(Javax, Jakarta)의 검증 애노테이션으로, javax.validation.Valid 또는 jakarta.validation.Valid 패키지에 속합니다.
  • 주로 DTO(데이터 전송 객체)에서 필드 검증에 사용됩니다.
  • 컨트롤러에서 요청 바디(@RequestBody)를 받을 때 사용.
  • @Valid는 클래스 레벨의 그룹 검증을 지원하지 않음.

사용 예시 (@Valid 적용)

@RestController
@RequestMapping("/api/user")
public class UserController {

    @PostMapping("/register")
    public ResponseEntity<String> registerUser(@Valid @RequestBody UserDto userDto) {
        return ResponseEntity.ok("User registered successfully");
    }
}

public class UserDto {
    @NotBlank(message = "이름은 필수 입력 값입니다.")
    private String name;

    @Email(message = "올바른 이메일 형식이 아닙니다.")
    private String email;
}

결과:

  • name이 비어 있거나 email 형식이 잘못되면 예외 발생 (MethodArgumentNotValidException).
  • @Valid는 클래스 단위의 그룹(Group) 검증을 지원하지 않음.

@Validated

  • @Validated는 Spring에서 제공하는 애노테이션(org.springframework.validation.annotation.Validated)이며 그룹(Group) 검증을 지원합니다.
  • 클래스 레벨에서 유효성 검사를 할 때 유용.
  • Service 계층에서도 사용 가능.
  • 클래스 내에서도 그룹(Group) 검증을 적용하여 특정 상황에 맞는 검증을 실행 가능.

사용 예시 (@Validated 적용)

@RestController
@RequestMapping("/api/user")
@Validated // 클래스 레벨에 적용 가능
public class UserController {
    
    @PostMapping("/register")
    public ResponseEntity<String> registerUser(@Validated(OnCreate.class) @RequestBody UserDto userDto) {
        return ResponseEntity.ok("User registered successfully");
    }
}

public class UserDto {
    @NotBlank(message = "이름은 필수 입력 값입니다.", groups = OnCreate.class)
    private String name;
    
    @Email(message = "올바른 이메일 형식이 아닙니다.", groups = {OnCreate.class, OnUpdate.class})
    private String email;
}

public interface OnCreate {}
public interface OnUpdate {} // 그룹 검증이 필요하다면 필수, 마커 인터페이스이므로 빈 인터페이스만 만들어주면 된다.

결과:

  • OnCreate 그룹에서만 특정 필드의 검증을 수행 가능.
  • @Validated는 Service 계층에서도 사용 가능.

Service 계층에서 @Validated 적용

@Service
@Validated
public class UserService {
    public void createUser(@Validated(OnCreate.class) UserDto userDto) {
        // 비즈니스 로직
    }
}

Service단에서의 입력값 검증

"Service단에선 어차피 Controller 계층에서 넘어온 파라미터를 쓰기 떄문에, @Valid로 검증해줄 필요가 거의 없다."

  • @Valid 혹은 @Validated 어노테이션을 이용한 검증은 Controller 계층에서 수행하는 것이 일반적
  • 입력값 검증은 컨트롤러에서 처리하는 것이 책임 분리 원칙(SRP)에 맞다.
  • Service는 비즈니스 로직을 수행하는 곳이지, 입력값이 올바른지 검증하는 역할을 맡지 않는 게 좋음.
  • Controller에서 이미 검증된 데이터를 넘겨주면, Service에서는 순수한 로직 처리에만 집중할 수 있음.
  • @Valid는 주로 DTO와 함께 사용됨
  • Controller에서 요청 객체(예: @RequestBody DTO)를 받을 때 @Valid를 사용해 유효성 검사를 진행.
  • Service 계층에서는 이미 검증된 값이 넘어오기 때문에 따로 추가 검증할 필요가 없음.

예외

  • Service 계층에서도 검증해야 하는 경우
    • Controller를 거치지 않고 Service가 직접 호출되는 경우 (예: 내부 API, 스케줄러, 이벤트 리스너 등).
    • 비즈니스 로직 차원의 추가적인 검증이 필요한 경우 (예: DB 조회 후 특정 조건 충족 여부 확인).
    • 여러 서비스 계층에서 공통으로 사용하는 검증 로직이 있는 경우 (@Validated를 Service에 적용).

2. StringUtils.equals()를 활용한 Null 방지

  • StringUtils.equals()는 Apache Commons Lang 라이브러리에서 제공하는 메서드로, null 값을 비교할 때 NullPointerException을 방지하는 데 유용합니다.

String.equals() vs StringUtils.equals() 차이

String a = null;
String b = "Hello";

// NullPointerException 발생
if (a.equals(b)) {
    System.out.println("같음");
}
  • 위 코드는 a가 null이므로 NullPointerException이 발생합니다.
import org.apache.commons.lang3.StringUtils;

String a = null;
String b = "Hello";

// Null 안전한 비교
if (StringUtils.equals(a, b)) {
    System.out.println("같음");
}
  • StringUtils.equals(a, b)는 a 또는 b가 null이어도 안전하게 비교 가능.
  • equalsIgnoreCase는 대소문자 비교 X

=> 비즈니스 로직 상 null이 안들어오더라도 null일 가능성이 있다는 것을 염두에 두고 해야함 (방어적인 개발)
그러므로, 그냥 equals()를 쓰기 보단 null 체크 기능이 있는 StringUtils.equals()를 사용할 것!

profile
백엔드 개발자를 꿈꿉니다.

0개의 댓글