protected Account() {}
@builder
어노테이션이 설정돼 있는 생성자 메서드를 통해 객체를 생성한다. RequestBody
를 받게 된다면? 모든 속성값들을 컨트롤러를 통해 넘겨 받게 되고 의도하지 않은 데이터 변경이 발생할 수 있음setter 대신 DTO 클래스를 기준으로 데이터를 변경하는 것이 더 직관적이고 유지보수하기 쉽다.
@Getter
@Entity
public class Account {
private String address1;
private String address2;
@Column(name = "zip", nullable = false)
private String zip;
...
@Builder
public Account(String email, String firstName, String lastName, String password, String address1, String address2, String zip) {
this.email = email;
this.firstName = firstName;
this.lastName = lastName;
this.password = password;
this.address1 = address1;
this.address2 = address2;
this.zip = zip;
}
public void updateMyAccount(AccountDto.MyAccountReq dto) {
this.address1 = dto.getAddress1();
this.address2 = dto.getAddress2();
this.zip = dto.getZip();
}
}
@Valid
를 통한 유효성 검사@Email
, @NotEmpty
@Valid
어노테이션 추가하면 유효성 검사를 진행하고 실패하면 MethodArgumentNotValidException
가 발생한다.MethodArgumentNotValidException
발생 시 공통적으로 사용자에게 적절한 Response 값을 리턴해주는 게 효율적 -> @ControllerAdmivce @ControllerAdvice
를 이용한 Exception 핸들링@ControllerAdvice
public class ErrorExceptionController {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
protected ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
retrun errorResponse...
}
}
@ControllerAdvice
MethodArgumentNotValidException
핸들링을 따로 하지 않으면 스프링 자체의 에러 Response 값을 리턴해준다. {
"timestamp": 1525182817519,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.web.bind.MethodArgumentNotValidException",
"errors": [
{
"codes": [
"Email.signUpReq.email",
"Email.email",
"Email.java.lang.String",
"Email"
],
"arguments": [
{
"codes": [
"signUpReq.email",
"email"
],
"arguments": null,
"defaultMessage": "email",
"code": "email"
},
[],
{
"arguments": null,
"defaultMessage": ".*",
"codes": [
".*"
]
}
],
"defaultMessage": "이메일 주소가 유효하지 않습니다.",
"objectName": "signUpReq",
"field": "email",
"rejectedValue": "string",
"bindingFailure": false,
"code": "Email"
}
],
"message": "Validation failed for object='signUpReq'. Error count: 3",
"path": "/accounts"
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
protected ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error(e.getMessage());
final BindingResult bindingResult = e.getBindingResult();
final List<FieldError> errors = bindingResult.getFieldErrors();
return buildFieldErrors(
ErrorCode.INPUT_VALUE_INVALID,
errors.parallelStream()
.map(error -> ErrorResponse.FieldError.builder()
.reason(error.getDefaultMessage())
.field(error.getField())
.value((String) error.getRejectedValue())
.build())
.collect(Collectors.toList())
);
}
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ErrorResponse {
private String message;
private String code;
private int status;
private List<FieldError> errors;
...
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public static class FieldError {
private String field;
private String value;
private String reason;
...
}
}
{
"message": "입력값이 올바르지 않습니다.",
"code": "???",
"status": 400,
"errors": [
{
"field": "email",
"value": "string",
"reason": "이메일 주소가 유효하지 않습니다."
},
{
"field": "lastName",
"value": null,
"reason": "반드시 값이 존재하고 길이 혹은 크기가 0보다 커야 합니다."
},
{
"field": "fistName",
"value": null,
"reason": "반드시 값이 존재하고 길이 혹은 크기가 0보다 커야 합니다."
}
]
}
@Getter
public enum ErrorCode {
ACCOUNT_NOT_FOUND("AC_001", "해당 회원을 찾을 수 없습니다.", 404),
EMAIL_DUPLICATION("AC_002", "이메일이 중복되었습니다.", 400),
INPUT_VALUE_INVALID("CM_001", "입력값이 올바르지 않습니다.", 400);
private final String code;
private final String message;
private final int status;
ErrorCode(String code, String message, int status) {
this.code = code;
this.message = message;
this.status = status;
}
}