스프링 DB-예외 이해
자바 예외 이해
스프링에서의 예외 처리에 대해 알기 위해서는 먼저 자바의 예외 계층에 대해 이해해야 한다.
최상위 예외로는 Throwable이 있고, 그 아래에 Exception과 Error가 있다. 이 중 Error는 메모리 부족, 심각한 시스템 오류와 같이 어플리케이션에서 복구가 불가능한 예외들을 의미한다. 따라서 우리는 어플리케이션 내에서 Error를 잡으려 해서는 안된다.
어플리케이션 내에서는 Exception을 잡게 되는데, Exception은 다시 RuntimeException과 그 외 예외들로 나뉜다.
RuntimeException이 중요한 것은 언체크 예외이기 때문이다. RuntimeException을 제외한 예외들은 모두 컴파일러에서 체크하는 체크 예외이다.
체크 예외, 언체크 예외의 차이에 대해 알아보기 전에 먼저 예외의 기본 규칙에 대해 알아보자.
예외는 폭탄 돌리기와 같다. 잡아서 처리하거나, 아니면 밖으로 던져야 한다. 이는 체크 예외, 언체크 예외 관계없이 적용되는 기본 규칙이다.
이렇게 밖으로 계속해서 예외를 던지게 되면 자바의 main() 쓰레드에서는 로그를 출력하고 종료되고, 웹 어플리케이션의 경우 WAS가 받아서 이전 포스팅에서 배운대로 처리하게 된다.
체크 예외, 언체크 예외
이 둘의 가장 큰 차이점은 체크 예외의 경우 컴파일러에서 잡아준다는 것이다. 만약 체크 예외가 일어날 수 있는 코드에서 예외를 잡거나 던지지 않으면, 컴파일러 선에서 컴파일 에러가 발생한다. 체크 예외의 장점과 단점 모두 이 제약으로부터 나온다.
언체크 예외는 RuntimeException과 그 자손들을 뜻하는데, 언체크 예외들은 별 다른 처리가 없으면 자동으로 밖으로 예외를 던진다. 처리를 하지 않아도 컴파일 에러가 발생하지 않는다.
체크 예외는 프로그래머가 실수로 누락하는 예외가 없도록 하는 장점이 있지만, 대신 모든 체크 예외를 프로그래머가 일일이 처리해줘야 하므로 코드가 번거롭고 번잡해진다. 또한 기술에 따라 발생하는 예외도 다른데, 해당 예외에 코드가 의존하게 되는 문제도 발생하는데 이는 후에 자세히 설명하겠다.
언체크 예외는 의무적으로 잡을 필요가 없어져 코드가 간결해지고, 위에 나온 의존관계 문제도 없어진다. 하지만 처리해야 하는 예외도 실수로 누락하게 되는 단점이 있다.
기본적으로는 언체크 예외를 사용하면 되고, 비즈니스 로직 상 반드시 처리해줘야 하는 예외에만 체크 예외를 사용해주면 된다.
체크 예외의 문제점
체크 예외의 문제점을 설명하기 위한 그림이다. 코드가 번잡해지는 것 외에도 여러 문제가 발생하는데, 먼저 Service와 같은 경우 순수한 자바 코드로 비즈니스 로직만을 담고 있는 것이 최선이다. 하지만 Repository, NetworkClient 등에서 체크 예외인 SQLException을 던지면 Service는 처리할 방법도 없고 여기서 처리해선 안됨에도 불구하고 받아서 던지는 코드를 추가로 작성해야하고 그에 따라 해당 예외에 의존하게 된다.
이렇게 예외에 의존하는 것이 왜 문제일까? 그 이유는 기술마다 발생하는 예외도 다르다는 점에 있다. 만약 JDBC가 아닌 JPA를 사용하게 되서 SQLException이 JPAException으로 변경되면 예외에 의존하는 Service 코드까지 뜯어고쳐야 한다. 인터페이스를 사용하여 변경의 여파를 최소화하는 객체지향 설계에서 이는 문제가 된다.
또한 인터페이스의 순수성을 해치는 문제도 있는데, 만약 구현체가 체크 예외를 밖으로 던지면, 인터페이스도 예외를 던지는 코드를 작성해야 한다. 이는 위의 예외 의존 문제를 발생시킬 뿐더러, 해당 인터페이스에 의존하는 다른 코드들에도 문제를 전파한다.
체크 예외의 처리
체크 예외를 처리하는 쉽고 편한 방법은, 체크 예외를 언체크 예외로 변환하여 던지는 것이다.
기존 체크 예외인 SQLException을 언체크 예외인 RuntimeSQLException으로 변경하여 던지면, 서비스, 컨트롤러에서 해당 예외들을 던지는 코드를 작성할 필요 없이 자동으로 밖으로 던져지고 예외 공통 처리 파트에서 잡아서 처리하면 된다.
static class Repository {
public void call() {
try {
runSQL();
} catch (SQLException e) {
throw new RuntimeSQLException(e);
}
}
private void runSQL() throws SQLException {
throw new SQLException("ex");
}
}
체크 예외를 언체크 예외로 변환하는 간단한 코드이다. 이렇게 언체크 예외로 변환하여 던져주면 체크 예외의 의존 문제를 포함한 여러 문제들이 해결되기 때문에 구현체의 기술이 변경되어도 예외 공통 처리부분만 변경하면 되기 때문에 변경의 영향력이 최소화된다.
정리
자바의 예외 계층, 체크 예외와 언체크 예외의 장단점과 차이점을 배웠다. 또한 체크예외의 여러 문제점을 언체크 예외로 변환하여 해결하는 방법도 배울 수 있었다.
기존 자바에서는 체크 예외를 통해 프로그래머가 인지하고 넘어가는 것이 좋다고 생각했다. 하지만 점점 어플리케이션에서 복구 불가능한 예외가 많아지면서 코드에서 체크 예외를 던지는 코드를 덕지덕지 붙이게 되었고 이후 스프링과 여러 라이브러리에서는 런타임 예외들을 기본으로 제공하게 되었다.
이러한 런타임 예외는 놓치고 넘어갈 수 있기 때문에 문서화 하거나 throw를 작성하여 인지할 수 있게 해주는 것이 좋다. 또한 예외를 변환할 때에는 기존 예외를 꼭 포함하여 변환해주어야 한다. 그래야 스택 트레이스에서 기존 예외까지 확인할 수 있다.
다음 포스팅에서는 스프링에서 어떤 식으로 예외를 처리하는지에 대해 알아보도록 하겠다.