-
Notifications
You must be signed in to change notification settings - Fork 170
[4기 - 황창현] Week 3-1 Thymeleaf를 이용하여 바우처 관리페이지 구현, ControllerAdvice를 통한 예외 처리 #842
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| @Controller | ||
| public class CustomErrorController implements ErrorController { | ||
|
|
||
| // ControllerAdvice로 처리되지 않는 서블릿 수준의 예외와 404 에러 처리 | ||
| @GetMapping("/error") | ||
| public String errorThrow(HttpServletRequest httpServletRequest, Model model) { | ||
| Object errorStatusCode = httpServletRequest.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); | ||
| Object errorMessage = httpServletRequest.getAttribute(RequestDispatcher.ERROR_MESSAGE); | ||
|
|
||
| model.addAttribute("errorCode", errorStatusCode); | ||
|
|
||
| if ((Integer) errorStatusCode == 404) { | ||
| model.addAttribute("errorMsg", "요청하신 페이지를 찾을 수 없습니다."); | ||
| return "errorPage"; | ||
| } | ||
|
|
||
| model.addAttribute("errorMsg", errorMessage); | ||
|
|
||
| return "errorPage"; | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ControllerAdvice에서 NoHandlerFoundException으로 404 페이지 에러를 처리할 수 있지만 application.yaml에 아래 설정을 추가해야지만 가능해서 그렇게 진행하지 않고 위 코드로 대체하였습니다.
spring.mvc.throw-exception-if-no-handler-found=true
-> HTTP 요청에 일치하는 핸들러(컨트롤러 메소드)를 찾지 못할 경우, NoHandlerFoundException을 발생시킴
spring.web.resources.add-mappings=false
-> 웹 리소스에 대한 맵핑을 설정을 자동으로 하지 않겠다는 설정
물론 위와 같이 설정하고 ControllerAdvice를 통해 처리하면 한 곳에서 다 처리할 수 있어서 좋다고 생각했지만 add mapping 설정시 css와 같은 정적 리소스를 직접 다 처리해줘야하는 문제가 발생되어 에러 맵핑을 이용하여 처리했습니다!
| import org.springframework.web.bind.annotation.ResponseStatus; | ||
|
|
||
| @Slf4j | ||
| @ControllerAdvice |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ControllerAdvice를 처음 사용해보았는데 올바르게 사용하고 있는 것인지 궁금합니다.
- 프로그램 내에서 발생할 것 같은 예외들을 모두 구분하여 로깅하고, 메세지를 에러 페이지로 발송하는 로직 구현, 예외 별 HTTP 상태 코드 적용
- Controller 하위 단에서 예외를 발생시킬 때 메세지에 전달하고자하는 내용을 구체적으로 적어서 상위로 계속 Throw
- ControllerAdvice에서 해당 예외를 잡고 Model에 저장하여 에러 페이지로 넘겨 출력
- ControllerAdvice에서 명시되지 않은 개발자가 예상치 못한 예외가 발생했을 때 프로그램을 꺼지지 않도록 제일 하위에 최상위 Exception으로 예외 처리 -> 물론 최상위 Exception을 사용하는 것은 좋지 않지만 예상치 못한 예외가 발생되었을 때 어떻게 처리해야할지 몰라 이렇게 적용했습니다!
실제로 실무에서는 모든 예외를 잡으려고 노력하겠지만 분명 개발자가 잡지 못하는 예외도 있을 것이라고 생각이 듭니다. 이럴 때 어떤방식으로 예외 처리를 하는지 궁금합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ControllerAdvice는 지금 잘 구현하신것 같습니다!
실제로 실무에서는 모든 예외를 잡으려고 노력하겠지만 분명 개발자가 잡지 못하는 예외도 있을 것이라고 생각이 듭니다. 이럴 때 어떤방식으로 예외 처리를 하는지 궁금합니다!
사실상 개발자가 모든 예외를 예측하는건 불가능합니다.
최대한 경우의 수를 줄여주는게 중요하죠. 예측 불가능한 예외는 최상위 Exception으로 처리합니다.
우선 예측 가능한 예외는 Custom Exception 클래스를 만들고, 해당 예외를 통해 ErrorHandler에서 캐치될 수 있도록 해야합니다.
예를 들어 사용자가 입력한 id가 없는 경우에는 어떻게 해야할까요?
이건 비즈니스 요구사항에서 파생된 예외라고 할 수 있습니다.
현재는 기본 정의된 NoSuchElementException을 사용하고 있지만, 각 비즈니스 룰에 맞춰 다양한 커스텀 예외를 만들어줘야합니다.
public class BusinessException extends RuntimeException {
public BusinessException(String message) {
super(message);
}
}이런 방식으로 상속을 사용해 커스텀 예외를 만들어 주고, service 계층에서는 해당 예외를 throw 하도록 해주는거죠.
현재는 필드값도 없는 그냥 깡통 클래스이지만, 각 예외 상황에 맞춰 필요한 필드를 추가해줄수도 있겠죠?
다음과 같이 도메인별로 Exception 계층구조를 만들수도 있습니다.
- RuntimeException
- BusinessException
- VoucherException
- CustomerException
- ....
- BusinessException
여기서 각 상황에 맞춘 예외를 더 세분화할수도 있지만, 그건 좀 번거로우니 enum을 사용하기도 합니다.
영주님 리뷰 참고
제가 말씀드리고 싶은것은 비즈니스 로직에서 발생하는 예측가능한 예외는 반드시 개발자가 제어해야한다는 것입니다.
텍스트로 잘 전달이 될지 모르겠네요.
이건 나중에 팀 전체 라이브 리뷰로 한번 더 말씀드리도록 하겠습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
자세하게 말씀해주셔서 너무 잘 이해됐습니다! 👍👍
제가 직접 발생시킨 예외들은 자바에서 제공하는 기본 예외에 메시지가 잘 담겨있어서 위에서 enum으로 처리한 것 처럼 잘 처리될 것이라고 판단했던 것 같습니다!
현재 여기저기 뻗어있는 exception 예외를 enum 클래스로 변경처리할 경우 Files Changed가 너무 많아져 복잡해질 것 같아 다음번 과제 때 말씀해주신 내용으로 적용해서 처리해보도록 하겠습니다!
항상!! 신경써서 봐주셔서 감사드립니다!! 🙇🙇🙇
다시 한 번 예외 처리에 대해서 깊은 고민을 할 수 있게 된 것 같아서 좋네요 ㅎㅎ 👍 👍
hanjo8813
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
창현님 수고하셨습니다~!
| } | ||
|
|
||
| @PostMapping("/save") | ||
| public String save(@Validated CustomerCreateRequest customerCreateRequest) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
보통 @Valid를 사용합니다~
두 차이점은 요기에 잘 설명되어 있으니 한번 읽어보세요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
서로 처리하는 부분이 달랐었네요! @Validated는 AOP로 동작하는거 완전 처음 알았습니다!! 감사합니다!! 👍
적용 커밋 : 9902efc
|
|
||
| private final CustomerService customerService; | ||
|
|
||
| @GetMapping("/") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
클래스 상단에서 @RequestMapping 설정을 해줬다면 빈 uri는 생략해도 됩니다!
url과 uri의 차이점은 뭘까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
url과 uri에 대해 차이점을 안다고 생각했었는데 찾아보니 조금 다르게 알고 있었던 것 같아요!
uri는 자원 자체를 식별하는 것으로 프로토콜이나 다른 값들을 제외한 고유한 주소만 가르킵니다!
url은 네트워크상에 자원이 어디에있는지 알려주기 위한 규약으로 프로토콜, uri 등 자원을 찾아갈 수 있도록 하는 모든 것을 포함한 내용입니다!
적용 커밋: 4039ebc
| boolean isExistCustomerId = customerService.existById(customerId); | ||
|
|
||
| if (!isExistCustomerId) { | ||
| throw new NoSuchElementException("사용자가 삭제하려는 아이디 " + customerId + "는 없는 ID입니다."); | ||
| } | ||
|
|
||
| customerService.deleteById(customerId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
id 존재 여부를 판단하는 로직이 컨트롤러에 있어야할까요?
콘솔에서는 어쩔 수 없이 view에서 호출해야했지만, 웹 환경에서는 어떨까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
우선 Service에서 더블 체킹을 하고 있어서 Controller보다는 View쪽에서 체크기능이라는 버튼을 추가하여 확인할 수 있을 것 같습니다! 삭제할 때는 회원가입할 때 같지 않고 굳이 View쪽에서 처리하기보다는 Serivce단에서 한 번만 체크해도 될 것 같아서 기존 Controller에 있는 로직은 삭제 처리했습니다!
적용 커밋 : 5243622
| public class HomeController { | ||
| @GetMapping("/") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
항상 한칸 띄우기~!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
앗 반영했습니다!
적용 커밋 : 7b29cec
| CustomerValidator.validateCustomer( | ||
| customerCreateRequest.getCustomerName(), | ||
| customerCreateRequest.getCustomerEmail(), | ||
| customerCreateRequest.getCustomerType() | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CustomerCreateRequest에서 이미 검증을 했는데 한번 더 검증할 필요가 있을까요?
(콘솔에서 @Valid를 사용하지 않아서 생긴 클래스였죠)
그리고 @Valid 검증은 어떻게 진행될까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
처음에 찾아보았을 때 생각으로는 @Valid 어노테이션으로 dto에서 검증할 수 있는 방법이 다양하지 않다고 느껴져서 dto에서 기본적인 것만하고 컨트롤러에서 조금 더 처리하는 것으로 진행했었습니다. 하지만 다시 자세하게 찾아보니 CustomerCreateRequest에서 모두 다 처리할 수 있다고 판단되어 컨트롤러에서 삭제 후 dto에 더 반영했습니다!
VoucherCreateRequest의 경우 VoucherType에 따라 검증이 달라져서 Controller에서도 검증하는 것으로 처리했습니다!
@Valid는 잘 몰라서 언급해주신 것을 보며 공부해보았습니다!
표준 스펙으로 빈 검증기를 이용해 객체의 제약 조건을 검증하도록 지시하는 어노테이션입니다!(왠지 long 타입은 안되더라구요,,) 프론트 컨트롤러인 디스패처 서블릿을 통해 컨트롤러로 전달 될 때 그 사이에서 ArgumentResolver가 @Valid를 처리합니다!
적용 커밋 : 0c81e7e
| @Controller | ||
| @RequiredArgsConstructor | ||
| @RequestMapping("/view/voucher") | ||
| public class VoucherViewController { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요 클래스도 고객 컨트롤러와 리뷰가 동일합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| final static String ERROR_MSG = "errorMsg"; | ||
| final static String ERROR_CODE = "errorCode"; | ||
| final static String ERROR_PAGE = "errorPage"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private static final
선언하는 순서 잘 지켜주세용~
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
예외 잡는거에 신경을 너무 쓰다보니 기본적인 것을 놓쳤네요 😢 수정 완료했습니다!
적용 커밋: 9d5717a
| import org.springframework.web.bind.annotation.ResponseStatus; | ||
|
|
||
| @Slf4j | ||
| @ControllerAdvice |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ControllerAdvice는 지금 잘 구현하신것 같습니다!
실제로 실무에서는 모든 예외를 잡으려고 노력하겠지만 분명 개발자가 잡지 못하는 예외도 있을 것이라고 생각이 듭니다. 이럴 때 어떤방식으로 예외 처리를 하는지 궁금합니다!
사실상 개발자가 모든 예외를 예측하는건 불가능합니다.
최대한 경우의 수를 줄여주는게 중요하죠. 예측 불가능한 예외는 최상위 Exception으로 처리합니다.
우선 예측 가능한 예외는 Custom Exception 클래스를 만들고, 해당 예외를 통해 ErrorHandler에서 캐치될 수 있도록 해야합니다.
예를 들어 사용자가 입력한 id가 없는 경우에는 어떻게 해야할까요?
이건 비즈니스 요구사항에서 파생된 예외라고 할 수 있습니다.
현재는 기본 정의된 NoSuchElementException을 사용하고 있지만, 각 비즈니스 룰에 맞춰 다양한 커스텀 예외를 만들어줘야합니다.
public class BusinessException extends RuntimeException {
public BusinessException(String message) {
super(message);
}
}이런 방식으로 상속을 사용해 커스텀 예외를 만들어 주고, service 계층에서는 해당 예외를 throw 하도록 해주는거죠.
현재는 필드값도 없는 그냥 깡통 클래스이지만, 각 예외 상황에 맞춰 필요한 필드를 추가해줄수도 있겠죠?
다음과 같이 도메인별로 Exception 계층구조를 만들수도 있습니다.
- RuntimeException
- BusinessException
- VoucherException
- CustomerException
- ....
- BusinessException
여기서 각 상황에 맞춘 예외를 더 세분화할수도 있지만, 그건 좀 번거로우니 enum을 사용하기도 합니다.
영주님 리뷰 참고
제가 말씀드리고 싶은것은 비즈니스 로직에서 발생하는 예측가능한 예외는 반드시 개발자가 제어해야한다는 것입니다.
텍스트로 잘 전달이 될지 모르겠네요.
이건 나중에 팀 전체 라이브 리뷰로 한번 더 말씀드리도록 하겠습니다!
| </div> | ||
| <input type="submit" class="btn btn-outline-success" value="고객 생성"/> | ||
| </form> | ||
| <button style="width:100%; margin-top:10px;" type="button" onclick="javascript:location.href='/view/customer/'" class="btn btn-secondary">돌아가기</button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rest api 규칙이긴 한데...
url 마지막에는 슬래시를 포함시키지 말아주세요!
(규칙 관련해서는 rest api PR에서 빡세게 보도록하겠습니다)
| <button style="width:100%; margin-top:10px;" type="button" onclick="javascript:location.href='/view/customer/'" class="btn btn-secondary">돌아가기</button> | |
| <button style="width:100%; margin-top:10px;" type="button" onclick="javascript:location.href='/view/customer'" class="btn btn-secondary">돌아가기</button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
말씀하신대로 / 슬래시 제외해서 처리했습니다! 감사합니다!!!
적용 커밋 : 4039ebc
| } | ||
|
|
||
| @PostMapping("/save") | ||
| public String save(@Validated CustomerCreateRequest customerCreateRequest) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
서로 처리하는 부분이 달랐었네요! @Validated는 AOP로 동작하는거 완전 처음 알았습니다!! 감사합니다!! 👍
적용 커밋 : 9902efc
|
|
||
| private final CustomerService customerService; | ||
|
|
||
| @GetMapping("/") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
url과 uri에 대해 차이점을 안다고 생각했었는데 찾아보니 조금 다르게 알고 있었던 것 같아요!
uri는 자원 자체를 식별하는 것으로 프로토콜이나 다른 값들을 제외한 고유한 주소만 가르킵니다!
url은 네트워크상에 자원이 어디에있는지 알려주기 위한 규약으로 프로토콜, uri 등 자원을 찾아갈 수 있도록 하는 모든 것을 포함한 내용입니다!
적용 커밋: 4039ebc
| CustomerValidator.validateCustomer( | ||
| customerCreateRequest.getCustomerName(), | ||
| customerCreateRequest.getCustomerEmail(), | ||
| customerCreateRequest.getCustomerType() | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
처음에 찾아보았을 때 생각으로는 @Valid 어노테이션으로 dto에서 검증할 수 있는 방법이 다양하지 않다고 느껴져서 dto에서 기본적인 것만하고 컨트롤러에서 조금 더 처리하는 것으로 진행했었습니다. 하지만 다시 자세하게 찾아보니 CustomerCreateRequest에서 모두 다 처리할 수 있다고 판단되어 컨트롤러에서 삭제 후 dto에 더 반영했습니다!
VoucherCreateRequest의 경우 VoucherType에 따라 검증이 달라져서 Controller에서도 검증하는 것으로 처리했습니다!
@Valid는 잘 몰라서 언급해주신 것을 보며 공부해보았습니다!
표준 스펙으로 빈 검증기를 이용해 객체의 제약 조건을 검증하도록 지시하는 어노테이션입니다!(왠지 long 타입은 안되더라구요,,) 프론트 컨트롤러인 디스패처 서블릿을 통해 컨트롤러로 전달 될 때 그 사이에서 ArgumentResolver가 @Valid를 처리합니다!
적용 커밋 : 0c81e7e
| public class HomeController { | ||
| @GetMapping("/") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
앗 반영했습니다!
적용 커밋 : 7b29cec
| VoucherValidator.validateVoucher( | ||
| voucherCreateRequest.getVoucherType(), | ||
| String.valueOf(voucherCreateRequest.getDiscountAmount()) | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
타입에 따라서 검증 내용이 달라지기 때문에 1차적으로 @Valid를하고 진행하지 못한 부분을 2차로 한 번 더 검증했습니다!
| @Controller | ||
| @RequiredArgsConstructor | ||
| @RequestMapping("/view/voucher") | ||
| public class VoucherViewController { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| boolean isExistCustomerId = customerService.existById(customerId); | ||
|
|
||
| if (!isExistCustomerId) { | ||
| throw new NoSuchElementException("사용자가 삭제하려는 아이디 " + customerId + "는 없는 ID입니다."); | ||
| } | ||
|
|
||
| customerService.deleteById(customerId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
우선 Service에서 더블 체킹을 하고 있어서 Controller보다는 View쪽에서 체크기능이라는 버튼을 추가하여 확인할 수 있을 것 같습니다! 삭제할 때는 회원가입할 때 같지 않고 굳이 View쪽에서 처리하기보다는 Serivce단에서 한 번만 체크해도 될 것 같아서 기존 Controller에 있는 로직은 삭제 처리했습니다!
적용 커밋 : 5243622
| </div> | ||
| <input type="submit" class="btn btn-outline-success" value="고객 생성"/> | ||
| </form> | ||
| <button style="width:100%; margin-top:10px;" type="button" onclick="javascript:location.href='/view/customer/'" class="btn btn-secondary">돌아가기</button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
말씀하신대로 / 슬래시 제외해서 처리했습니다! 감사합니다!!!
적용 커밋 : 4039ebc
| final static String ERROR_MSG = "errorMsg"; | ||
| final static String ERROR_CODE = "errorCode"; | ||
| final static String ERROR_PAGE = "errorPage"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
예외 잡는거에 신경을 너무 쓰다보니 기본적인 것을 놓쳤네요 😢 수정 완료했습니다!
적용 커밋: 9d5717a
| import org.springframework.web.bind.annotation.ResponseStatus; | ||
|
|
||
| @Slf4j | ||
| @ControllerAdvice |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
자세하게 말씀해주셔서 너무 잘 이해됐습니다! 👍👍
제가 직접 발생시킨 예외들은 자바에서 제공하는 기본 예외에 메시지가 잘 담겨있어서 위에서 enum으로 처리한 것 처럼 잘 처리될 것이라고 판단했던 것 같습니다!
현재 여기저기 뻗어있는 exception 예외를 enum 클래스로 변경처리할 경우 Files Changed가 너무 많아져 복잡해질 것 같아 다음번 과제 때 말씀해주신 내용으로 적용해서 처리해보도록 하겠습니다!
항상!! 신경써서 봐주셔서 감사드립니다!! 🙇🙇🙇
다시 한 번 예외 처리에 대해서 깊은 고민을 할 수 있게 된 것 같아서 좋네요 ㅎㅎ 👍 👍
hanjo8813
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💫
| @NotBlank(message = "이름은 필수 입력 값입니다.") | ||
| @Pattern(regexp = "^[a-zA-Z]*$", message = "이름은 영어로만 입력되어야 합니다.") | ||
| private String customerName; | ||
|
|
||
| @Email(message = "이메일 양식에 맞춰서 입력해주세요.") | ||
| @NotBlank(message = "이메일은 필수 입력 값입니다.") | ||
| private String customerEmail; | ||
|
|
||
| @NotNull(message = "타입은 필수 입력 값입니다.") | ||
| private CustomerType customerType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Valid로 검증 실패하면 어떤 예외 클래스가 터질까요?
해당 예외도 error handler에서 잡아보세요~~ (생각보다 많은 내용이 들어있는 클래스입니다)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MethodArgumentNotValidException 예외가 발생하는 것으로 알고있습니다!!
말씀하신대로 ExceptionHandler를 통해 예외처리 해주었습니다!
적용 커밋 : 39602aa
| @Getter | ||
| @Setter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ModelAttribute를 역직렬화할때 필요한 요소에는 무엇이 있을까요?
setter를 쓰는게 열받지는 않으셨나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사실 직렬화 처리를 할 때 어떤식으로 변환되는지에 대해서 잘 모르고 있었는데 찾아보고 알았습니다!
ModelAttribute는 setter나 생성자 둘 중 하나만 있어도 되네요!
항상 직렬화에 대해 고민해보고 상황에 따라 적절하게 setter를 이용해야될 것 같아요!
감사합니다!!
적용 커밋 : 2027f3c
mentor-tyler
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수고하셨습니다.
| Object errorStatusCode = httpServletRequest.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); | ||
| Object errorMessage = httpServletRequest.getAttribute(RequestDispatcher.ERROR_MESSAGE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
처음부터 형변환을 해도 좋을것 같아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Object가 아닌 사용하는 형태로 변환해서 받는 것으로 변경처리했습니다!
적용 커밋 : 6e71741
| @GetMapping("/find") | ||
| public String getFindAllPage(Model model) { | ||
| CustomerListResponse customerListResponse = customerService.findAll(); | ||
| model.addAttribute("customerList", customerListResponse.getCustomerList()); | ||
|
|
||
| return "customer/findAll"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uri는 find인데 메서드네임이나 다른것들은 all이네요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| return "customer/find"; | ||
| } | ||
|
|
||
| @GetMapping("/delete/{id}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DeleteMapping 은 안되려나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
삭제하는 로직은 DeleteMapping으로 변경하여 처리하였습니다!
적용 커밋 : f9edd04
| public String save(@Valid VoucherCreateRequest voucherCreateRequest) { | ||
| VoucherValidator.validateVoucher( | ||
| voucherCreateRequest.getVoucherType(), | ||
| String.valueOf(voucherCreateRequest.getDiscountAmount()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
long 그대로 넘기면 안되나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validator를 도메인마다 분리하기전에 정규식 표현으로 처리하다보니 바꿀 생각을 못했던 것 같습니다! 다시 고민해보니 정규식 표현을 거치지 않고 단순 숫자형으로도 Validate가 가능하여 말씀하신대로 long 그대로 넘기는 것으로 변경했습니다!
적용 커밋 : b6a4098
| @@ -0,0 +1,2 @@ | |||
| package com.programmers.springweekly.exception;public enum ErrorResponseStatus { | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아무것도 없네요 + 개행이 빠진듯?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enum을 만들었다가 사용안하기로 결정해서 삭제했어야했는데 삭제를 못했던 것 같습니다! 삭제처리했습니다!
적용 커밋 : 9d9d53b
| model.addAttribute(ERROR_MSG, e.getMessage()); | ||
| model.addAttribute(ERROR_CODE, 404); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
별도 메소드로 추출하면 메세지랑 코드를 받아서 처리 가능할것 같아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| @ExceptionHandler(MethodArgumentNotValidException.class) | ||
| @ResponseStatus(HttpStatus.BAD_REQUEST) | ||
| public String handleMethodArgumentNotValidException(MethodArgumentNotValidException e, Model model) { | ||
| log.warn("GlobalExceptionHandler - MethodArgumentNotValidException 발생, 데이터 검증 실패 {}", e.getMessage(), e); | ||
| addAttributeInModel(e.getBindingResult().getAllErrors().get(0).getDefaultMessage(), 400, model); | ||
|
|
||
| return ERROR_PAGE; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
만약 dto의 여러 필드값에서 검증에 실패했을때 e.getBindingResult().getAllErrors() 의 순서는 랜덤하게 들어가게 됩니다.
dto에서 bean validation을 수행하는 순서가 랜덤하기 때문인데요.
현재는 첫번째 메시지만 보여주고 있어서 아마 요청마다 다른 원인 메시지를 출력해줄겁니다.
메시지를 모두 보여주거나 검증에 대한 순서를 지정해주는게 좋습니다.
이 때 검증 순서를 어떻게 지정할까요?
한번 공부해보시고 다음 미션에서 적용해보시길 바랍니다~!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
다음 미션에서 한 번 적용해보도록 하겠습니다!
찾아보니 그룹을 만들고 그룹 시퀀스를 이용하여 순서를 정할 수 있네요!
전체 메세지도 좋은 방법이지만 명시적으로 딱 처음 발생한 에러 메시지에 대해서 출력하는게 더 좋은 것 같습니다!
감사합니다!!
📌 과제 설명
3주차 미션
3-1
3-2
👩💻 요구 사항과 구현 내용
3-1 Thymeleaf를 이용하여 바우처 관리페이지 구현
요약
Voucher(web)
Customer(web)
Validation
ControllerAdvice
웹 동작 화면(정상)
웹 동작 화면(에러)
✅ PR 포인트 & 궁금한 점
웹 화면을 직접 돌려보시기 불편하실 것 같아 태블릿으로 접속하여 화면 녹화 후 gif로 변경해서 위에 올려두었습니다!
이번 PR도 잘 부탁드립니다 🙏🙏
아래 궁금한 점은 Files changed에도 남겨두었습니다!
궁금한 점
ControllerAdvice를 처음 사용해보았는데 올바르게 사용하고 있는 것인지 궁금합니다.
실제로 실무에서는 모든 예외를 잡으려고 노력하겠지만 분명 개발자가 잡지 못하는 예외도 있을 것이라고 생각이 듭니다. 이럴 때 어떤방식으로 예외 처리를 하는지 궁금합니다!