[Playground] Spring 검증 방식 변경 (Service → Bean Validation)

Daeya·2026년 7월 18일

Playground

목록 보기
8/9

title 빈값이면 400 — 이번엔 Service가 아니라 @Valid

POST /api/memos { "title": "", ... }
PUT /api/memos/{id} { "title": " ", ... }

이전에는 Service에서 validateTitle()로 막고 ApiException을 던졌다.
이번엔 요청 DTO + @Valid 로 검증하고, 실패는 MethodArgumentNotValidException → 400.


1. 왜 방식을 바꿨는지

이전 (Service 검증)이번 (Bean Validation)
검증 위치MemoService.validateTitle()MemoRequestDto 필드 어노테이션
실패 예외ApiException (직접 throw)MethodArgumentNotValidException (Spring이 throw)
요청 타입MemoVO (조회 조건 필드 포함)MemoRequestDto (create/update 전용)

MemoVO는 목록 조회용 keyword / offset 등이 섞여 있다.
쓰기 API는 필요한 필드만 받는 DTO로 분리하는 게 맞다.


2. 의존성 추가 (pom.xml)

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.1.0.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.4.3.Final</version>
</dependency>

Spring 4.3 기준이라 Hibernate Validator 5.x 사용.
(@NotBlank는 org.hibernate.validator.constraints.NotBlank)


3. MemoRequest DTO

public class MemoRequestDto {

    @NotBlank(message = "title은 필수입니다")
    private String title;

    private String content;
    private String status;
    // getter / setter
}
  • create / update 요청 body 전용
  • title만 @NotBlank — 빈 문자열·공백만 있어도 실패
  • 응답 메시지는 이전과 동일: "title은 필수입니다"

4. Controller — @Valid

@PostMapping
public void insert(@Valid @RequestBody MemoRequestDto request) {
    memoService.insertMemo(request.getTitle(), request.getContent(), request.getStatus());
}

@PutMapping("/{id}")
public void update(@PathVariable int id, @Valid @RequestBody MemoRequestDto request) {
    memoService.updateMemo(id, request.getTitle(), request.getContent(), request.getStatus());
}
  • @Valid가 있어야 DTO 어노테이션 검증이 돈다
  • 실패 시 Service까지 안 가고 Spring이 예외를 던진다

5. GlobalExceptionHandler — 400

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidation(MethodArgumentNotValidException e) {
    String message = "입력값이 올바르지 않습니다";
    FieldError fieldError = e.getBindingResult().getFieldError();
    if (fieldError != null && fieldError.getDefaultMessage() != null) {
        message = fieldError.getDefaultMessage();
    }
    Map<String, String> error = new HashMap<>();
    error.put("message", message);
    return ResponseEntity.badRequest().body(error);
}

응답 예:

{ "message": "title은 필수입니다" }

기존 ApiException(404 등) 핸들러는 그대로 둔다.


6. Service — validateTitle() 삭제

검증은 Controller 진입 전에 끝나므로 Service에서는 제거.
insert / update는 저장만 담당.


7. Postman으로 확인

Create (400)

POST http://localhost:8080/playground/api/memos

{
  "title": "",
  "content": "내용",
  "status": "READY"
}

Update (400)

PUT http://localhost:8080/playground/api/memos/1

{
  "title": "   ",
  "content": "내용",
  "status": "READY"
}

정상 (200)

{
  "title": "제목",
  "content": "내용",
  "status": "READY"
}

정리

기능

  • POST / PUT 시 title 빈값 → 400 Bad Request
  • 응답 body: { "message": "title은 필수입니다" } (이전과 동일)

흐름 (요청 → 응답)

Controller: @Valid @RequestBody MemoRequest
→ Bean Validation 실패 시 MethodArgumentNotValidException
→ GlobalExceptionHandler: 400 + message JSON

만든 순서

  1. pom.xml validation 의존성
  2. MemoRequestDto + @NotBlank
  3. Controller @Valid
  4. Handler에 MethodArgumentNotValidException
  5. Service validateTitle() 제거
  6. Postman: 빈 title → 400 확인

이전 검증(#12)과 다른 점

  • #12: Service 수동 검증 + ApiException
  • 이번: DTO 어노테이션 + @Valid + Binding 예외

참고

profile
Daeya

0개의 댓글