[Spring] 스프링 DB 1편 5-3. 체크 예외 기본 이해

강우엉·2023년 8월 27일

Spring

목록 보기
273/359
  • Exception 과 그 하위 예외는 모두 컴파일러가 체크하는 체크 예외이다. 단 RuntimeException 은 예외로 한다.
  • 체크 예외는 잡아서 처리하거나, 또는 밖으로 던지도록 선언해야한다. 그렇지 않으면 컴파일 오류가 발생한다.

체크 예외 전체 코드

package hello.jdbc.exception.basic;

import lombok.extern.slf4j.Slf4j;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

@Slf4j
public class CheckedTest {

    @Test
    void checked_catch() {
        MyCheckedException.Service service = new MyCheckedException.Service();
        service.callCatch();
    }

    @Test
    void checked_throw() {
        MyCheckedException.Service service = new MyCheckedException.Service();
        Assertions.assertThatThrownBy(() -> service.callThrow())
                .isInstanceOf(MyCheckedException.class);
    }
    /**
     * Exception 을 상송받은 예외는 체크 예외가된다.
     */
    static class MyCheckedException extends Exception {
        public MyCheckedException(String message) {
            super(message);
        }

        /**
         * Checked 예외는
         * 예외를 잡아서 처리하거나 던지거나 선택해야한다.
         */
        static class Service {
            Repository repository = new Repository();

            /**
             * 예외를 잡아서 처리하는 코드
             */

            public void callCatch() {
                try {
                    repository.call();
                } catch (MyCheckedException e) {
                    //예외처리 로직
                    log.info("예외처리, message={}", e.getMessage(), e);
                }
            }

            /**
             * 체크 예외를 밖으로 던지는 코드
             * 체크 예외는 예외를 잡지 않고 밖으로 던지려면 throws 예외를 메서드에 필수로 선언해야한다.
             */
            public void callThrow() throws MyCheckedException {
                repository.call();
            }
        }

        static class Repository {
            public void call() throws MyCheckedException {
                throw new MyCheckedException("ex");
            }
        }
    }
 }

Exception을 상속받은 예외는 체크 예외가 된다.

 static class MyCheckedException extends Exception {
        public MyCheckedException(String message) {
            super(message);
        }
  • MyCheckedException 는 Exception 을 상속받았다. Exception 을 상속받으면 체크 예외가 된다.
  • 참고로 RuntimeException 을 상속받으면 언체크 예외가 된다. 이런 규칙은 자바 언어에서 문법으로 정한것이다.
  • 예외가 제공하는 여러가지 기본 기능이 있는데, 그 중에 오류 메시지를 보관하는 기능도 있다. 예제에서 보는것 처럼 생성자를 통해서 해당 기능을 그대로 사용하면 편리하다.

먼저 예외를 잡아서 처리하는 코드를 실행해보자

  	@Test
    void checked_catch() {
        MyCheckedException.Service service = new MyCheckedException.Service();
        service.callCatch();
    }

service.callCatch() 에서 예외를 처리했기 때문에 테스트 메서드까지 예외가 올라오지 않는다.

실행 순서를 분석해보자.
1. test service.callCatch() repository.call() [예외 발생, 던짐]
2. test service.callCatch() [예외 처리] repository.call()
3. test [정상 흐름] service.callCatch() repository.call()

Repository.call() 에서 MyUncheckedException 예외가 발생하고, 그 예외를 Service.callCatch() 에서 잡는 것을 확인할 수 있다.

log.info("예외 처리, message={}", e.getMessage(), e);

실행 결과

[Test worker] INFO hello.jdbc.exception.basic.CheckedTest - 예외 처리, message=ex
hello.jdbc.exception.basic.CheckedTest$MyCheckedException: ex
at
hello.jdbc.exception.basic.CheckedTest$Repository.call(CheckedTest.java:64)
at
hello.jdbc.exception.basic.CheckedTest$Service.callCatch(CheckedTest.java:45)
at hello.jdbc.exception.basic.CheckedTest.checked_catch(CheckedTest.java:14)
  • 실행 결과 로그를 보면 첫줄은 우리가 남긴 로그가 그대로 남는 것을 확인할 수 있다.
  • 그런데 두 번째 줄 부터 예외에 대한 스택 트레이스가 추가로 출력된다.
  • 이 부분은 로그를 남길 때 로그의 마지막 인수에 예외 객체를 전달해주면 로그가 해당 예외의 스택 트레이스를 추가로 출력해주는 것이다.
    • log.info("예외 처리, message={}", e.getMessage(), e); 여기서 마지막에 있는 e 부분이다.

체크 예외를 잡아서 처리하는 코드

 				try {
                    repository.call();
                } catch (MyCheckedException e) {
                    //예외처리 로직
                    log.info("예외처리, message={}", e.getMessage(), e);
                }
  • 체크 예외를 잡아서 처리하려면 catch(..) 를 사용해서 예외를 잡으면 된다.
  • 여기서는 MyCheckedException 예외를 잡아서 처리한다.
  • catch 에 MyCheckedException 의 상위 타입인 Exception 을 적어주어도 MyCheckedException 을 잡을 수 있다.
  • catch 에 예외를 지정하면 해당 예외와 그 하위 타입 예외를 모두 잡아준다.
  • 물론 정확하게 MyCheckedException 만 잡고 싶다면 catch 에 MyCheckedException 을 적어주어야 한다.

이번에는 예외를 처리하지 않고, 밖으로 던지는 코드를 살펴보자.

 	@Test
    void checked_throw() {
        MyCheckedException.Service service = new MyCheckedException.Service();
        Assertions.assertThatThrownBy(() -> service.callThrow())
                .isInstanceOf(MyCheckedException.class);
    }
  • service.callThrow() 에서 예외를 처리하지 않고, 밖으로 던졌기 때문에 예외가 테스트 메서드까지 올라온다.
  • 테스트에서는 기대한 것 처럼 MyCheckedException 예외가 던져지면 성공으로 처리한다.

실행 순서를 분석해보자.
1. test service.callThrow() repository.call() [예외 발생, 던짐]
2. test service.callThrow() [예외 던짐] repository.call()
3. test [예외 도착] service.callThrow() repository.call()

체크 예외를 밖으로 던지는 코드

public void callThrow() throws MyCheckedException {
 		repository.call();
}
  • 체크 예외를 처리할 수 없을 때는 method() throws 예외 을 사용해서 밖으로 던질 예외를 필수로 지정해주어야 한다. 여기서는 MyCheckedException 을 밖으로 던지도록 지정해주었다.
  • throws 를 지정하지 않으면 컴파일 오류가 발생한다.
  • throws 에 MyCheckedException 의 상위 타입인 Exception 을 적어주어도 MyCheckedException 을 던질 수 있다.

체크 예외의 장단점
체크 예외는 예외를 잡아서 처리할 수 없을 때, 예외를 밖으로 던지는 throws 예외 를 필수로 선언해야 한다. 그렇지 않으면 컴파일 오류가 발생한다. 이것 때문에 장점과 단점이 동시에 존재한다.

  • 장점: 개발자가 실수로 예외를 누락하지 않도록 컴파일러를 통해 문제를 잡아주는 훌륭한 안전 장치이다.
  • 단점: 하지만 실제로는 개발자가 모든 체크 예외를 반드시 잡거나 던지도록 처리해야 하기 때문에, 너무 번거로운 일이 된다. 크게 신경쓰고 싶지 않은 예외까지 모두 챙겨야 한다.
profile
우엉이의 코딩 성장일기💻

0개의 댓글