프로그래밍/spring

체크 예외와 언체크(런타임) 예외 차이 / Throwable?

승민아 2024. 5. 17. 14:04

1) 체크, 언체크 예외

먼저, 체크 예외와 언체크 예외 차이를 알아보자.

출처 : 인프런 김영한

 

Exception은 컴파일러가 체크하는 체크 예외이다.

그러나 RuntimeException은 체크하지 않는다.

RuntimeException의 자식 모두 언체크 예외이다.

 

어떤 차이를 가질까?

 

체크 예외

- Repository, Service 코드에 throws 명시

memberRepository 코드

public Member findById(String memberId) throws SQLException {
    try {
        ...
    } catch (SQLException e) {
        throw e;
    }
}

만약 Repository에서 SQLException 체크 예외를 던진다면 throws로 던지는 예외를 명시해야한다.

 

Service 코드

private void bizLogic() throws SQLException {
        memberRepository.findById(fromId);
}

서비스단에서는 리포지토리에서 올라오는 체크 예외를 그대로 throws로 던지거나

 

private void bizLogic(String fromId, String toId, int money) {
    try {
        memberRepository.findById(fromId);
    } catch (SQLException e) {
        ...
    }
}

명시하고싶지 않다면 try catch로 예외를 잡아 처리해야한다.

 

 

 - 인터페이스에도 throws 명시

만약 Repository 인터페이스를 만들고

그 구현체를 구현한다면 구현체에서 체크 예외를 던지는 경우가 있을 것이다.

 

Repository 구현체

public class MemberRepositoryExImpl implements MemberRepositoryEx {

    @Override
    public Member save(Member member) throws SQLException {
        return null;
    }

    @Override
    public Member delete(String memberId) throws SQLException {
        return null;
    }
}

구현체의 메서드 save, delete에서 체크 예외를 던진다면 다음처럼 인터페이스를 구성해야한다.

 

Repository 인터페이스

public interface MemberRepositoryEx {

    // 구현체에서 체크 예외를 던지려면, 인터페이스에서도 throws를 선언되어 있어야 던질 수 있다.
    Member save(Member member) throws SQLException;

    // 구현 클래스에서 던지는 예외는 하위 클래스도 가능하다.
    Member delete(String memberId) throws Exception;

}

인터페이스에도 예외에 의존적이게 throws를 명시해줘야한다.

예외에 의존적인 인터페이스는 문제가 된다.

 

대신 누락된 예외를 컴파일러를 통해 잡을 수 있다는 장점이 있다.

 

언체크 예외

언체크 예외는 체크 예외와 달리 try catch로 잡거나 throws로 던지거나 하지 않아도 된다.

자동으로 예외를 던져주기 때문에 throws를 명시하지 않아도 된다.

 

Repository 코드

public void call() {
    throw new RuntimeConnectException("연결 실패");
}

리포지토리에서 Runtime 예외를 던지면 Service단에서는 try catch로 잡아줘야할까?

 

Service 코드

public void logic() {
    repository.call();
}

잡아주지 않아도 된다!!!

 

런타임 예외를 사용한다면 예외의 의존관계에서 벗어날 수 있다는 것이다.

 

체크 예외를 언체크 예외로 변경

다음과 같이 RuntimeException을 상속 받아 언체크를 만들 수 있다.

static class MyUncheckedException extends RuntimeException {
    public MyUncheckedException(String message) {
        super(message);
    }
}

 

체크 예외가 발생시 위에서 만든 언체크 예외를 던져주면 된다.

public void update(String memberId, int money) {
    try {
        ...
    } catch (SQLException e) {
        throw new MyUncheckedException("ex");
    }
}

 

2) Throwable

예외와 관련된 코드를 작성하다보면 나오는 Throwable이 뭔지 궁금했다.

예외의 인자로 Throwable을 넘긴다. 이게 뭘까?

변수명은 cause로 예외의 이유를 넣는것 같다. 

 

출처 : 인프런 김영한

 

예외 클래스의 Exception 위에 Object가 있을줄 알았지만

Exception 위에 Throwable이 존재했다.

(Error는 메모리 부족과 같은 복구 불가능한 시스템 예외이다.)

 

예외의 인자로 Throwable을 넣는 이유는 뭘까?

이는 예외 스택 트레이스와 관련 있다.

 

언체크 예외를 만들었을때

public class MyDbException extends RuntimeException {

    public MyDbException() {
        super();
    }

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

    public MyDbException(String message, Throwable cause) {
        super(message, cause);
    }

    public MyDbException(Throwable cause) {
        super(cause);
    }
}

Throwable도 오버라이딩한다. 이건 어디다 사용할까?

 

체크 예외를 언체크 예외로 변경과 같은 상황에서

public void update(String memberId, int money) {
    try {
        ...
    } catch (SQLException e) {
        throw new MyDbException(e);
    }
}

위와 같이 이전의 예외를 넣어줄 수 있다.

예외 모두 Throwable 자식이기 때문이다.

 

예외를 처리하지 못하고 받아버렸을때

Caused by: 로 예외가 어디서 발생했는지 트레이스할 수 있게 된다.

 

예외를 전환할때 기존의 예외를 꼭 포함하도록 해야지

예외 추적에 용이하다.