[Spring DB 1편] 5. 예외

HJ·2023년 1월 28일
0

Spring DB 1편

목록 보기
5/7

김영한 님의 스프링 DB 1편 - 데이터 접근 핵심 원리 강의를 보고 작성한 내용입니다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-1/dashboard


1. 예외 계층

  • Object : 예외도 객체이기 때문에 예외의 최상위 부모도 Object

  • Throwable : 최상위 예외

  • Error : 메모리 부족이나 심각한 시스템 오류와 같이 어플리케이션에서 복구 불가능한 시스템 예외이기 때문에 이 예외를 잡으려고 하면 안된다

    • Error 예외도 함께 잡을 수 있기 때문에 Throwable 예외를 잡으면 안된다

    • 참고> Error 도 언체크 예외이다

  • Exception : 체크 예외

    • Exception 과 그 하위 예외는 모두 컴파일러가 체크하는 체크 예외이다

    • 단> RuntimeException 은 예외

  • RuntimeException : 언체크 예외, 런타임 예외

    • 컴파일러가 체크 하지 않는 언체크 예외이다

    • RuntimeException 과 그 자식 예외는 모두 언체크 예외이며 런타임 예외라고도 부름




2. 예외 기본 규칙

  • 예외를 잡아서 처리하면 그 이후에는 어플리케이션 로직이 정상 흐름으로 동작한다

  • 예외를 처리하지 못하면 호출한 곳으로 예외를 계속 던지게 된다

  • 예외 기본 규칙 2가지

    1. 예외는 잡아서 처리하거나, 처리할 수 없으면 밖으로 던져야한다

    2. 예외를 잡거나 던질 때 지정한 예외 뿐만 아니라 그 예외의 자식들도 함께 처리된다

      • ex> Exception을 catch로 잡으면 그 하위 예외들도 모두 잡을 수 있다

      • ex> Exception을 throws로 던지면 그 하위 예외들도 모두 던질 수 있다

  • 예외를 처리하지 못하고 계속 던지게 되면 어플리케이션의 경우 여러 사용자의 요청을 처리하기 때문에 하나의 예외 때문에 시스템이 종료되면 안된다

  • 이런 경우 WAS가 해당 예외를 받아서 처리하는데, 주로 사용자에게 개발자가 지정한 오류 페이지를 보여준다




3. 체크 예외

// 테스트 코드
// 예외를 잡은 경우 테스트 성공
@Test
void checked_catch() {
    Service service = new Service();
    service.callCatch();
}

// 예외를 던진 경우 assertThatThrownBy()로 확인
@Test
void checked_throw() {
    Service service = new Service();
    assertThatThrownBy(() -> service.callThrow())
        .isInstanceOf(MyCheckedException.class);
}
  • Exception을 상속받은 예외는 체크 예외가 된다

  • 체크 예외의 경우 catch로 잡아서 처리하거나 throws 로 예외를 던져야한다

    • 예외를 잡거나 던졌는지 컴파일러가 체크하고 충족하지 않으면 컴파일 오류
  • catchthrows에서 특정 예외를 처리해도 되지만, Exception 을 처리하면 그 하위까지 모두 처리한다

  • 테스트 할 때 메서드에서 예외를 잡아 처리한 경우, 정상 흐름으로 동작하기 때문에 성공

  • 테스트 할 때 메서드에서 예외를 throws 로 던진 경우, 테스트가 실패하기 때문에 assertThatThrownBy 를 통해 예외가 던져졌는지 확인

  • 장점: 개발자가 실수로 예외를 누락하지 않도록 컴파일러를 통해 문제를 잡아준다

  • 단점: 개발자가 모든 체크 예외를 반드시 잡거나 던지도록 처리해야 하기 때문에, 크게 신경쓰고 싶지 않은 예외까지 모두 챙겨야 하며 의존관계에 따른 단점도 존재




4. 언체크 예외

  • 언체크 예외는 말 그대로 컴파일러가 예외를 체크하지 않는다

  • 체크 예외는 예외를 던지는 throws 를 선언하지 않고, 생략할 수 있는데 이 경우 자동으로 예외를 던진다

  • RuntimeException 을 상속받은 예외는 언체크 예외가 된다

  • 장점: 신경쓰고 싶지 않은 언체크 예외를 무시할 수 있고, 신경쓰고 싶지 않은 예외의 의존관계를 참조하지 않아도 된다

  • 단점: 언체크 예외는 개발자가 실수로 예외를 누락할 수 있다




5. 예외 활용 기본 규칙

  • 기본적으로 언체크( 런타임 ) 예외를 활용

  • 체크 예외는 의도적으로 던지는 예외, 예외를 잡아서 처리해야하는 문제일 때만 활용

    • ex> 계좌 이체 실패 예외, 결제 시 포인트 부족 예외, 로그인 ID, PW 불일치 예외 등



6. 체크 예외의 문제점

6-1. 문제 흐름

  • Repository : DB에 접근해서 데이터를 저장 및 관리 / SQLException 체크 예외를 던진다

  • NetworkClient : 외부 네트워크에 접속해서 어떤 기능을 처리 / ConnectionException 체크 예외를 던진다

  • Service : Repository 와 NetworkClient 를 호출
      SQLExceptionConnectionException 를 처리해야함

  • but> Service에서 연결이 실패하거나 DB에 발생하는 문제들을 처리할 방법이 없음
      throws 를 통해 예외를 밖으로 던진다

  • Controller도 처리할 방법이 없기 때문에 throws 를 통해 예외를 던진다

  • 웹 애플리케이션이라면 서블릿의 오류 페이지나 Filter, 또는 스프링 MVC가 제공하는 ControllerAdvice 에서 이런 예외를 공통으로 처리

    • 사용자에게 어떤 문제인지를 설명하지 않고 단순한 일반적인 메세지를 보여준다

    • API라면 HTTP 상태코드 500( 서버 내부 오류 )을 사용해 응답을 내린다

    • 위와 같은 해결 불가능한 공통 예외는 별도의 오류 로그를 남기고, 오류를 해결해서 배포하기 전까지 사용자는 같은 문제를 겪기 때문에 개발자가 빨리 인지할 수 있도록 메일이나 알림 등을 통해 전달받아야한다


6-2. 체크 예외의 문제점

6-2-1. 복구 불가능한 예외

  • 대부분의 예외는 복구 불가능

  • ex> SQLException은 DB에 문제가 있어서 발생하는 예외인데 SQL 문법에 오류가 있다던지, DB 서버가 다운되거나 하는 문제일 수도 있다

  • 이런 경우, 대부분 복구가 불가능하고 Service나 Controller는 이런 문제를 해결할 수 없다

  • ➡️이런 문제들을 일관성 있게 공통으로 처리해야한다

  • ➡️오류 로그를 남기고 개발자가 해당 오류를 빠르게 인지하는 것이 필요하다

  • ➡️서블릿 필터, 스프링 인터셉터, 스프링 ControllerAdvice 등을 활용


6-2-2. 의존 관계에 대한 문제

  • Service나 Controller는 체크 예외이기 때문에 throws 를 통해 예외를 던져야한다

  • ➜ Service와 Conroller가 예외에 의존하게 된다

    • ex> SQLException 예외의 경우, Controller에서 java.sql.SQLException을 의존
  • ➜ 데이터 접근 기술을 변경하게 되면 의존하는 예외를 전부 수정해야한다

  • ➜ 클라이언트 코드 변경 없이 구현체를 변경할 수 있지만 체크 예외가 문제가 된다




7. 런타임 예외로 문제 해결

  • 체크 예외를 런타임 예외로 변경한다

  • Service와 Controller가 throws로 던지지 않아도 자동으로 예외를 던진다

  • ➜ Service나 Controller가 복구 불가능한 예외를 신경쓰지 않아도 된다

  • throws를 통해 예외를 던질 필요가 없어졌기 때문에 의존관계가 사라진다

  • ➜ 데이터 접근 기술이 변경되어도 Service 나 Controller 에서 코드를 변경하지 않게 된다




8. 예외 포함과 스택 트레이스

8-1. 스택 트레이스

log.info("message={}", "message", ex);
log.info("ex", ex)
  • 로그를 출력할 때 마지막 파라미터에 예외를 넣어주면 로그에 스택 트레이스를 출력

  • System.out 에 스택 트레이스를 출력하는 경우, e.printStackTrace() 사용


8-2. 예외 전환

public void call() {
    try {
        runSQL();
    } catch (SQLException e) {
        throw new RuntimeSQLException(e);
    }
}
  • 예외를 전환하는 경우, 반드시 기존 예외를 포함해야한다

  • 위의 코드에서는 SQLException을 잡아서 RntimeException로 전환시키는데 기존 예외를 파라미터로 전달

  • 만약 포함하지 않으면 스택 트레이스를 확인할 때 심각한 문제가 발생


8-3. 예외 생성 시 기존 예외 포함

static class RuntimeSQLException extends RuntimeException {
    public RuntimeSQLException(Throwable cause) {
        super(cause);
    }
}
  • 새로 예외를 생성하는 경우, 위처럼 Throwable cause를 파라미터로 가진 생성자를 사용해서 정의한다

  • 위처럼 작성하게 되면 파라미터로 기존 예외를 받을 수 있고, 만들어진 예외가 내부에 기존 예외를 갖는다

  • 예외를 포함하지 않는다면 기존에 발생한 스택 트레이스를 확인할 수 없다

    • ex> DB와 연동한 경우, DB에서 발생한 예외를 확인할 수 없게 된다

0개의 댓글