• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    미해결

Controller에서 BindingResult값을 유지한채로 @ExceptionHandler를 활용한 사용자 정의 예외를 처리하는법이 궁금합니다.

23.10.01 02:39 작성 조회수 379

0

=========================================
[질문 템플릿]
1. 강의 내용과 관련된 질문인가요? (예/아니오)
2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)
3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)

[질문 내용]
Spring data jpa 를 활용하여 지금까지 배운 내용들을 총 합한 프로젝트를 만들어 보고 있는데 한가지 궁금증이 생겨 질문 드립니다.

 

@ExceptionHandler을 사용하여 사용자정의 예외를 만들어 아이디 중복 예외를 처리하고싶은데 예외를 처리하는 과정에서 아이디가 중복일시 Controller의 BindingResult를 활용하여 아이디가 중복이면 View에 아이디가 중복이라는 정보를 표현하고싶어서 프로젝트를 코딩중에 Controller 부분에서 service단에서 throw한 사용자 정의 예외를 처리하려 하는데 try catch로 예외를 처리하는 순간 @ExceptionHandler를 사용하지 못하고 그렇다고 다시 예외를 던지자니 @ExceptionHandler에서 View에 관련된 Binding result의 값이나 ModelAttribute의 값을 보존해지 못하여 처리가 불가합니다. 이럴때 제일 좋은 방법이 무엇인지 알고싶습니다.

 

Controller 코드입니다.

@Controller
@RequiredArgsConstructor
@RequestMapping("/users")
public class UserController {

    private final LoginService loginService;

    @GetMapping("/add")
    public String addForm(@ModelAttribute("userDto") UserDto userDto) {
        return "user/addUserForm";
    }

    @PostMapping("/add")
    public String save(@Valid @ModelAttribute UserDto userDto, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "user/addUserForm";
        }

//        if (loginService.signUpIdExists(userDto.getLoginId()) == false){
//            bindingResult.reject("loginIdExists", "동일한 아이디가 존재합니다.");
//            return "user/addUserForm";
//        }
        try {
            loginService.signUp(userDto);
            return "redirect:/";
        }
        catch (UserIdExistsException e) {
            bindingResult.reject("loginIdExists", "동일한 아이디가 존재합니다.");
            return "user/addUserForm";
        }

    }

}

 

Service 코드입니다.

@Slf4j
@Service
@RequiredArgsConstructor
public class LoginService {

    private final UserRepository userRepository;

    public User login(String loginId, String password) {
        return userRepository.findByLoginId(loginId).filter(m -> m.getPassword().equals(password))
                .orElse(null);
    }

    public void signUp(UserDto userDto) {
//        if(signUpIdExists(userDto.getLoginId()) == false){
//            throw new UserIdExistsException("이미 존재하는 아이디입니다.");
//        }
        try {
            Address address = new Address(userDto.getAddressDto().getZipcode(),
                    userDto.getAddressDto().getStreetAdr(), userDto.getAddressDto().getDetailAdr());
            User regisUser = new User(userDto.getLoginId(), userDto.getLoginName(), userDto.getPassword(), address);
            userRepository.save(regisUser);
        }
        catch (DataIntegrityViolationException e) {
            throw new UserIdExistsException("이미 존재하는 아이디입니다.");
        }
    }

    private boolean signUpIdExists(String loginId) {
        return userRepository.findByLoginId(loginId).isEmpty();
    }

}

 

@ControllerAdvice 코드입니다.

@Slf4j
@ControllerAdvice
public class ExceptionAdvice {

    @ExceptionHandler(UserIdExistsException.class)
    public ModelAndView userIdExHandler(UserIdExistsException e) {
        log.error("[userIdExistsException] ex", e);
        return new ModelAndView();
    }

}

 

UserIdExistsException 코드입니다.

public class UserIdExistsException extends RuntimeException{

    public UserIdExistsException() {
    }

    public UserIdExistsException(String message) {
        super(message);
    }

}

 

위 코드는 동작은 확인했지만 사실상 try catch 로 예외를 잡아버려서 @ExceptionHandler가 동작하지 않는 상태입니다. Entity의 아이디값에 unique 옵션을 걸어두어 아이디 중복이 일어날시 DataIntegrityViolationException에러가 일어납니다.

추가로 궁금한것

  1. 예외가 사실상 repository에서 터지는데 인터페이스에는 try catch가 권장되지 않는것으로 알고 통상 Service에서 에러를 처리하는것으로 알아 이렇게 코딩하였는데 이게 올바른건지 모르겠습니다. 예외를 발생시키는 지점을 repository로 옮기는것이 맞나요?

 

  1. Spring data jpa 는 알아서 Spring에 종속된 에러를 출력하는것으로 알고있는데 DataIntegrityViolationException에 속한 예외 두가지를 다른방법으로 처리하고싶으면 ErrorCode를 분석해 If문 으로 사용자 정의 예외를 만들어서 처리해야하나요?

     

     

  2. bindingresult를 사용하기 위해 어쩔수없이 예외를 throw하였더니 controller까지 예외가 전파되서 코드가 지저분해졌습니다. 이렇게 View에 특정한 값을 보내주어야할때 Controller에 예외를 throw 하지 않고 해결할수있는 좋은 방법이 있나요?

     

     

    질문이 길고 지저분해서 죄송합니다. 나름 열심히 알아보고 코딩해보아도 잘 모르겠어서 질문남깁니다. 감사합니다.

 

 

답변 2

·

답변을 작성해보세요.

3

안녕하세요. 성훈님

예외를 처리하는 방법에는 크게 2가지가 있다고 볼 수 있습니다.

1. 예외를 잡아서 정상화 하는 방법

2. 예외를 해결할 수 없는 문제로 인정하고 공통 처리하는 방법(사용자에게 죄송합니다. 같은 화면을 보여주는 방법)

성훈님의 경우에는 오류가 발생해도 다시 정상화해서 addUserForm으로 이동해야 하는 상황인데요.

이 경우에는 컨트롤러에서 예외를 잡아서 정상적인 상황으로 만든 다음에 다시 입력 폼으로 이동해야 하는 상황입니다.

따라서 성훈님이 작성하신 컨트롤러 코드처럼 작동하는 것이 맞습니다. 컨트롤러에서 예외를 정상화 하고 다시 흐름 제어를 해야하는 것이지요.

제가 추천하는 방법은 1번과 같은 경우에는 예외를 잡아서 정상화해서 처리하고, 2번과 같이 복구가 불가능한 예외의 경우에는 @ExceptionHandler를 사용하는 방법입니다.

지금과 같이 서버 사이드 렌더링 방식이 아니라 백엔드에서 API만 제공하는 방식을 사용한다면, 보통 JSON 데이터만 화면에 내리면 되기 때문에 이런 경우에는 공통 예외처리만으로 더 많은 문제를 해결할 수도 있습니다. 하지만 지금과 같이 서버 사이드 렌더링 방식을 사용하면 뷰의 흐름 제어와 해당 뷰를 렌더링 할 때 추가 데이터까지 뷰에 전달해야 하기 때문에 지금과 같이 컨트롤러에서 흐름 제어를 하는 것이 더 나은 방식입니다.

도움이 되셨길 바래요 :)

0

인프런 AI 인턴님의 프로필

인프런 AI 인턴

2023.10.03

안녕하세요, 인프런 AI 인턴이에요.

@ControllerAdvice와 @ExceptionHandler를 사용하여 사용자 정의 예외를 처리하면, 예외가 발생했을 때 @ExceptionHandler가 동작하고 해당 예외를 처리할 수 있습니다. 그리고 BindingResult를 유지하는 방법은 예외를 발생시키는 메소드에서 BindingResult를 파라미터로 받아서 사용하면 됩니다.

예를 들어, 아이디 중복 예외를 처리하기 위한 예외 클래스를 만들었다고 가정해보겠습니다. 아래와 같은 코드를 사용해볼 수 있습니다.

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(DuplicateIdException.class)
    public ModelAndView handleDuplicateIdException(DuplicateIdException ex, BindingResult bindingResult) {
        
        // 중복 아이디 예외 처리 로직 작성

        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("errorMessage", "아이디가 중복되었습니다.");
        modelAndView.setViewName("error");

        return modelAndView;
    }

}

이 예제에서는 DuplicateIdException 예외가 발생했을 때 handleDuplicateIdException 메소드가 동작하고, BindingResult도 함께 전달됩니다. 이렇게 받은 BindingResult를 활용하여 View에 아이디 중복에 대한 정보를 표현할 수 있습니다.

또한, 예외 발생 시 BindingResult의 내용을 보존하려면, 예외를 던질 때 해당 BindingResult 객체를 함께 전달해주어야 합니다. Controller에서 service 단에서 throw한 사용자 정의 예외를 처리할 때, try-catch 블록으로 예외를 처리하면 @ExceptionHandler를 사용할 수 없지만, catch 블록에서 BindingResult 객체를 다시 예외와 함께 던져주면 @ExceptionHandler에서 처리할 수 있습니다.

이렇게 예외를 처리하면서 BindingResult를 유지할 수 있는 방법을 알려드렸는데, 추후에 문제가 있다면 구체적인 코드나 오류 메시지를 알려주시면 더 자세한 도움을 드릴 수 있을 것 같아요.

더 필요한 도움이 있다면 언제든지 말씀해주세요. 좋은 하루 되세요!

이성훈님의 프로필

이성훈

질문자

2023.10.04

답변 감사합니다. 저렇게 bindingresult값을 가져와서 새로운 error view를 만드는것 말고 기존 페이지에 적힌 값들을 유지하면서 아이디가 중복되었습니다 라는 경고 문구를 추가하고싶은데 그런 경우엔 @ExceptionHandler를 사용해서는 불가능할까요? @ExceptionHandler를 사용하면 일단 dispatcherservlet으로 빠져나가다 보니 기존 페이지의 값을 유지하는게 어렵고 그렇다고 @ExceptionHandler에서 기존 페이지의 model값들을 다 받아서 다시 페이지를 그리자니 뭔가 @ExceptionHandler이 너무 많은 역활을 하는것같아 아닌것같습니다. error를 최대한 try catch를 사용하지 않고 @ExceptionHandler에서 공통으로 처리하고싶은데 저러한 상황에선 불가능한가요?