Spring

240520

짤진이 2024. 5. 21. 08:04
반응형

Valid

  • @NotNull : Null이 아닌 것 => "", " "(blank) 허용
  • @NotEmpty : Null, ""이 아닌 것 => " "허용
  • @NotBlank : Null, "", " "이 아닌 것.

스프링 예외 처리 : 스프링이 일을 해주다가 예외가 나면 @Valid(예외 처리가 필요하다)

MemberDTO

package com.example.shoppingmall.Member;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import jakarta.validation.constraints.*;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class MemberDTO {
    /**
     * Member를 구분하기 위한 id 정보
     */
    @NotBlank(message = "아이디는 필수 입력입니다.")
    @Size(min = 6, max = 15, message = "아이디는 6 ~ 15자 이여야 합니다!")
    private String userId;
    /**
     * Member의 비밀번호 정보
     */
    @NotBlank(message = "비밀번호는 필수 입력입니다.")
    @Size(min = 8, max = 15, message = "비밀번호는 8 ~ 15자 이여야 합니다!")
    private String pw;
    /**
     * Member의 실제 이름 정보
     */
    @NotBlank(message = "이름은 필수 입력입니다.")
    @Size(min = 1, max = 10, message = "이름은 1 ~ 10자 이여야 합니다!")
    private String name;
    /**
     * Member의 이메일 정보
     */
//    @Email
    @Pattern(regexp = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,6}$", message = "이메일 형식에 맞지 않습니다.")
    @NotBlank(message = "이메일은 필수 입력입니다.")
    @Size(min = 6, max = 20, message = "이메일은 6 ~ 20자 이여야 합니다!")
    private String email;
    /**
     * Member의 전화번호
     */
    @Pattern(regexp = "^\\d{3}-\\d{3,4}-\\d{4}$", message = "전화번호 형식에 맞지 않습니다.")
    @NotBlank(message = "연락처는 필수 입력입니다.")
    @Size(min = 12, max = 13, message = "전화번호는 12 ~ 13자 이여야 합니다!")
    private String contact;

    public Member convertToEntity() {
        return new Member(userId, pw, name, email, contact);
    }

    @Override
    public String toString() {
        return "MemberDTO{" +
                "userId='" + userId + '\'' +
                ", pw='" + pw + '\'' +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", contact='" + contact + '\'' +
                '}';
    }
}

 

MemberDTO에서 예외처리해준 값들이 String 값으로 전달되기 때문에

제네릭타입으로 에러메세지를 생성해주면 객체 형태로 전달된다....!

 

package com.example.shoppingmall.utils;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

public class ApiUtils {
    public static <T> ApiResult<T> success(T data) {
        return new ApiResult(true, data, null);
    }

    public static <M> ApiResult<M> error(M message, HttpStatus httpStatus) {
        return new ApiResult(false,
                null,
                new ApiError(message, httpStatus));
    }

    @AllArgsConstructor
    @Getter
    public static class ApiResult<T> {
        boolean success;
        T response;
        ApiError error;
    }

    @Getter
    @AllArgsConstructor
    static class ApiError<M> {
        M message;
        HttpStatus httpStatus;
    }
}

[ExceptionResolver]

  • ExceptionHandlerExceptionResolver : Controller 내에서 메소드를 생성하여 처리해준다.
    ex)@ExceptionHandle(예외가 터질 때 처리해주는 메소드) + @ResponseStatus => 
  • ResponseStatusExceptionResolver : 상태 코드 적용 예외 처리
  • DefaultHandlerExceptionResolver : 스프링 내부적으로 기본 예외 처리, ex)

예외 처리가 되었을 때 HttpStatusCode가 200으로 날아가는 이슈를 해결하기 위해.

 // 유효성 검사하다가 에러가 터지면 호출되는 예외 처리 메소드
    // MethodArgumentNotValidException 예외 처리
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiUtils.ApiResult handleValidationExceptions(MethodArgumentNotValidException errors) {
        Map<String, String> errorMessages = new HashMap<>();

        for (FieldError error : errors.getFieldErrors()){
            String errorField = error.getField(); //예외 필드
            String errorMessage = error.getDefaultMessage(); // 예외 메세지
            errorMessages.put(errorField,errorMessage);
        }
        return error(errorMessages, HttpStatus.BAD_REQUEST);
    }

 

위 Annotation을 추가해주면 HttpStatusCode가 Bad Request로 바뀐다...!

 

 

하지만 Exception이 join API에서만 사용하는 것이 아니라 다른 API에서도 사용하므로 위 Exception을

Global Class로 만들어서 언제든지 사용할 수 있게 해주면 편해질 것 같다...!

 

package com.example.shoppingmall.Member;

import com.example.shoppingmall.utils.ApiUtils;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

import static com.example.shoppingmall.utils.ApiUtils.error;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiUtils.ApiResult handleValidationExceptions(MethodArgumentNotValidException errors) {
        Map<String, String> errorMessages = new HashMap<>();

        for (FieldError error : errors.getFieldErrors()) {
            String errorField = error.getField(); //예외 필드
            String errorMessage = error.getDefaultMessage(); // 예외 메세지
            errorMessages.put(errorField, errorMessage);
        }
        return error(errorMessages, HttpStatus.BAD_REQUEST);
    }
}

 

 

*log를 찍는 위치

  • 매개변수 잘 들어왔는지
  • iterator 등 객체 또는 변수를 하나씩 꺼내볼 때
  • 클라이언트에게 전달하기 전에
  •  
반응형