1. HTTP 응답 코드
throw
로 던진 예외를 처리하기 전 HTTP 응답코드를 간단히 알아보겠습니다.HTTP 응답코드는 주로 다음과 같은 의미를 갖고 있습니다.
- 1XX (임시 응답): 요청을 처리하고 있음을 나타내는 임시 응답입니다.
- 2XX (성공): 클라이언트의 요청이 성공적으로 수신되어 처리되었음을 나타냅니다.
- 3XX (리다이렉션): 클라이언트가 요청한 리소스가 다른 위치로 이동되었음을 나타냅니다.
- 4XX (클라이언트 오류): 클라이언트의 잘못된 요청으로 인해 발생하는 오류입니다.
- 5XX (서버 오류): 서버가 요청을 처리하는 동안 오류가 발생했음을 나타냅니다. 이 오류는 반드시 로그로 기록되어야 합니다.
따라서 error 패키지의 ex 내부에 Exception400~404 , 500 에러 처리 클래스를 정의했습니다.


public class Exception400 extends RuntimeException {
public Exception400(String message) {
super(message);
}
}
해당
Exception400
클래스는 RuntimeException
을 상속 받아 커스텀 하고 있습니다.RuntimeException 을 상속 받는 이유는 애플리케이션 실행중 발생하는 오류를 다룰 것이기 때문입니다.
에러메세지는 다음과 같이 JavaScript 로 처리합니다.
입력받은 에러 메세지를 출력하고 이전 페이지로 이동하게 됩니다.
public static String back(String msg) {
String errMsg = """
<script>
alert('$msg');
history.back();
</script>
""".replace("$msg", msg);
return errMsg;
}
GlobalExceptionHandler 원리


실행중
RuntimeException
이 발생하면 여기로 오게됩니다.@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public String ex(Exception e) {
String errMsg = """
<script>
alert('$msg');
history.back();
</script>
""".replace("$msg", e.getMessage());
return errMsg;
}
}
ex 의 매개변수
Exception e
는
throw new
RuntimeException("게시글 id를 찾을 수 없습니다");
를 갖고있다.
이를 실행하면

팝업이 뜨고 확인을 누르면 이전 페이지로 돌아가게 된다.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public String ex(Exception e) {
return Script.back(e.getMessage());
}
}
@RestControllerAdvice 어노테이션을 사용하여 모든 RuntimeException 을 처리합니다.
예외 처리는 여기까지 하고 User 서비스 리팩토링을 해 보겠습니다.
2. User 서비스 리팩토링
기존 회원가입 기능에 아이디 중복 검사를 추가합니다.
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
@Transactional
public void 회원가입(UserRequest.JoinDTO joinDTO) {
User oldUser = userRepository.findByUsername(joinDTO.getUsername());
if (oldUser != null) {
throw new Exception400("이미 존재하는 유저네임입니다.");
}
userRepository.save(joinDTO.toEntity());
}
}
입력한 아이디가 이미 존재하는지 확인합니다.
중복인 경우
findByUsername
의 값이 리턴되겠죠? (아니면 null 이 오도록 작성되어있습니다)if 조건문을 설정하여 이전 에 작성한
Exception400
로 예외를 던집니다.2.1 레포지토리 로직
@RequiredArgsConstructor
@Repository
public class UserRepository {
private final EntityManager em;
public User findByUsername(String username) {
Query query = em.createQuery("select u from User u where u.username = :username", User.class);
query.setParameter("username", username);
User user = (User) query.getSingleResult();
return user;
}
기존의 코드입니다. db 에 있는 이름을 체크할 때는 상관없지만 우리는 db 에 없는 값을 조회하더라도 처리해야 합니다.
이대로 실행시 아래와 같이 NoResultException 이 나오게 되는데 회원 가입때 마다 발생하면 곤란하겠죠..
- 검증
중복 검사를 위해 name 으로 조회하는 것이기 때문에
noresult Exception 이 나오는게 정상입니다.
@Test
public void findByUsername() {
String username = "haha";
User user = userRepository.findByUsername(username);
}

따라서 아래와 같이 수정합니다.
- 레포지토리 수정
public User findByUsername(String username) {
Query query = em.createQuery("select u from User u where u.username = :username", User.class);
query.setParameter("username", username);
try {
User user = (User) query.getSingleResult();
return user;
} catch (Exception e) {
return null;
}
}
수정한 뒤 실행하면 존재하는 값을 주거나, null 을 리턴하게 됩니다.
중복 체크이기 때문에 null 이 정상상태, 있는것은 중복인 상황이죠.
3. 로그인 기능 리팩토링
3.1 서비스 로직
public void 로그인(UserRequest.LoginDTO loginDTO) {
User user = userRepository.findByUsernameAndPassword(loginDTO.getUsername(), loginDTO.getPassword());
}
3.2 레포지토리 수정
public User findByUsernameAndPassword(String username, String password) {
Query query = em.createQuery("select u from User u where u.username = :username and u.password = :password", User.class);
query.setParameter("username", username);
query.setParameter("password", password);
try {
User user = (User) query.getSingleResult();
return user;
} catch (Exception e) {
throw new Exception401("인증되지 않았습니다.");
}
}
이렇게 적절하게 예외를 처리할 수 있도록 합니다.
저희는 개발하며 e.getMessage 를 통해 정확한 에러 메세지를 확인할 수 있지만
사용자에게는 위와 같은 메세지를 띄워주는 편이 좋겠죠
3.3 컨트롤러 수정
@PostMapping("/login")
public String login(UserRequest.LoginDTO loginDTO) {
User sessionUser = userService.로그인(loginDTO);
session.setAttribute("sessionUser", sessionUser);
return "redirect:/board";
}
@PostMapping("/join")
public String join(UserRequest.JoinDTO joinDTO) {
userService.회원가입(joinDTO);
return "user/join-form";
}
이렇게 컨트롤러 - 서비스 - 레포지토리 로 요청을 하게될 때
조건문이 중첩되는 경우가 있습니다.
if(조건==조건1){
if(조건 == 조건2) {
실행해줘();
}
}else{
}
가독성을 위해 조건이 맞지 않는 경우 초기에 예외를 던져 흐름을 제어하는 방법을 사용하여
코드를 작성했습니다.
if (조건 != 조건1) {
throw new Exception404("조건1이 맞지 않습니다.");
}
if (조건 != 조건2) {
throw new Exception404("조건2가 맞지 않습니다.");
}
실행해줘();
이렇게 작성하면 if-else 구조가 없어 코드가 깔끔하게 보이고 , 예외 상황을 보다 확실히 구분할 수 있어 예외 관리에 유용할 듯 싶습니다. 😁
스프링부트 게시판 시리즈 v2 -1. https://inblog.ai/hj/27190 (User 테이블 생성 및 쿼리 수정) -2. https://inblog.ai/hj/27193 (User, Board 테이블 조인 과 JPQL) -3. https://inblog.ai/hj/27224 (회원 가입) -4. https://inblog.ai/hj/27225 DTO 를 통한 리팩토링 -5. https://inblog.ai/hj/27310 로그인, 로그아웃 -6. https://inblog.ai/hj/27316 서비스 레이어 추가 및 DTO 활용 -7. https://inblog.ai/hj/27430 예외처리 핸들러 설정과 User 서비스 리팩토링 -8. https://inblog.ai/hj/27431 Board 기능 리팩토링 -9. https://inblog.ai/hj/27560 게시글 수정, 더티체킹(flush) -10. https://inblog.ai/hj/27561 인터셉터, AOP 사용 / 마무리
Share article