4. 예외

이유석·2022년 12월 13일
1

Book - Toby's Spring

목록 보기
19/20
post-thumbnail

4장에서는 JdbcTemplate 을 대표로 하는 스프링의 데이터 접근 기능에 담겨있는 예외처리와 관련된 접근 방법에 대해 알아본다.

JdbcTemplate은 템플릿 콜백 패턴을 사용해서, JDBC를 직접 사용할 때 발생하는 대부분의 반복 작업(아래 나열)을 대신 처리해준다.

  • 커넥션 획득
  • statement 를 준비하고 실행
  • 결과를 반복하도록 루프를 실행
  • 커넥션 종료, statement , resultset 종료
  • 트랜잭션 다루기 위한 커넥션 동기화
  • 예외 발생시 스프링 예외 변환기 실행
  • 개발자는 SQL을 작성하고, 전달할 파리미터를 정의하고, 응답 값을 매핑하기만 하면 된다.

하지만 동적 SQL을 해결하기 어렵다는 단점이 있다.

4.1 사라진 SQLException

직접 만든 JdbcContext 를 JdbcTemplate 으로 전환하는 과정에서 throws SQLException 선언이 사라졌음을 알 수 있다.

4.1.1 초난감 예외처리

개발자들의 코드에서 종종 발견되는 초난간 예외처리의 예제를 알아보도록 하겠다.

예외 블랙홀

try {
	...
} catch(Exception e) {
	e.printStackTrace();
}

위 코드는 일반적으로 하는 예외 처리 코드 같아보이지만, 절대로 이와 같이 코드를 작성하면 안 된다.

예외 처리의 핵심 원칙

  • 모든 예외는 적절하게 복구되든지, 아니면 작업을 중단시키고 운영자 또는 개발자에게 분명하게 통보되어야 한다.

  • 이때, 특별하게 예외를 잡아서 조치할 방법이 없다면 잡지 말고 메서드 밖으로 던져서 책임을 전가해야한다.

무의미하고 무책임한 throws

public void method1 () throws Exception {
	method2();
}

public void method2() throws Exception {
	method3();
}

public void mehtod3() throws Exception {
	...
}

위 코드는 모든 예외를 무조건 던져버리는 선언을 모든 메서드에 넣는 것 이다. 이런 코드 또한 무책임한 throws 선언이란 심각한 문제점이 있다.

위 두가지 예외 처리 방식은 어떤 경우에도 용납하지 않아야 한다.

4.1.2 예외의 종류와 특징

Java 예외의 종류 - 출처

  • Error

    • 메모리 부족(OutOfMemoryError), 스택오버플로우(StackOverFlowError)처럼 자바 가상 기계(JVM)나 하드웨어 등 시스템의 문제로 발생하는 것을 의미한다.
    • 즉, 개발자가 처리할 수 있는 영역이 아니기 때문에 Error가 발생하면 프로그램을 종료시키는 것이 보통이다.
  • Exception

    • Exception은 Error와 다르게 개발자가 프로그램 내에서 처리해 줄 수 있다.
    • 즉, 예외가 발생하더라도 프로그램을 비정상 종료시키지 않는다.
    • Exception 은 컴파일러의 예외 체크 여부에 따라 RuntimeExceptionOtherException 으로 나뉜다.
  • RuntimeException (Unchecked Exception)

    • RuntimeException은 Error와 함께 컴파일러가 예외를 체크하지 않는다.
    • 즉, 컴파일 후 런타임 시 발생할 수 있는 예외이다. 예외처리가 필수적이지 않다.
  • OtherException (Checked Exception)

    • Error와 RuntimeException을 제외한 모든 예외를 의미하며 컴파일러가 예외를 체크해 준다.
    • 즉 컴파일 시 발생될 수 있는 예외이며, 반드시 예외 처리를 해 주어야 한다.

4.1.3 예외 처리 방법

  • 예외 복구

    • 예외 상황을 파악하고 문제를 해결해서 정상 상태로 돌려놓는 것이다.

    • 예외로 인해 기본 작업 흐름이 불가능하다면, 다른 작업 흐름으로 자연스럽게 유도해주는 것이다.

예시

  • 파일이 읽어지지 않는 IOException
    • 사용자에게 상황을 알려주고, 다른 파일을 이용하도록 안내한다.
  • DB 서버에 접속이 실패하여, SQLException이 발생한 경우
    • 일정시간 대기 후, 재시도해본다. 이때 정해진 횟수 만큼의 재시도가 실패할 경우 예외복구를 포기한다.
  • 예외처리 회피

    • 예외 처리를 자신이 담당하지 않고, 자신을 호출한 쪽으로 던져버리는 것이다.

    • throws 문으로 선언하여 예외가 발생하면 알아서 던져지게 하거나, catch 문으로 일단 예외를 잡은 후에 로그를 남기고 다시 예외를 던지는 방법이 있다.

    • 예외를 회피하는 것 또한 의도가 분명해야 한다.
      즉, 자신을 사용하는 (책임이 있는) 쪽에서 예외를 다루도록 하는게 최선의 방법이다.

  • 예외 전환

    • 예외 회피와 비슷하게 예외를 던지지만, 발생한 예외를 그대로 넘기는 게 아니라, 적절한 예외로 전환하여 던진다.
    • 이때 전환하는 예외에 원래 발생한 예외를 담아서 중첩 예외로 만드는 것이 좋다.

예외 전환의 목표

  1. 내부에서 발생한 예외의 의미를 분명하게 해줄 수 있는 예외로 바꿔주기 위해서 이다.
  2. 예외를 처리하기 쉽고 단순하게 만들이 위해 포장하는 것 이다.
  • 주로 예외처리를 강제하는 체크 예외를 언체크 예외인 런타임 예외로 바꾸는 경우에 사용한다.
  • 반대로 애플리케이션 로직에서 예외조건이 발생 시, 체크 예외로 전환하는게 적절하다. 이는 비즈니스적인 의미가 있는 예외는 적절한 복구 작업이 필요하기 때문이다.

어차피 복구하지 못할 예외라면

  • 애플리케이션 코드에서는 런타임 예외로 포장하여 던지고,
  • 예외 처리 서비스등을 이용해 자세한 로그를 남기고,
  • 관리자에게는 메일 등으로 통보해주고,
  • 사용자에게는 친절한 안내 메시지를 보여주는 식으로 처리하는게 바람직 하다.

4.1.4 예외처리 전략

지금까지 살펴본 예외의 종류와 처리 방법 등을 기준으로 일괄된 예외처리 전략을 정리해보자.

런타임 예외의 보편화

  • 대응이 불가능한 체크 예외라면, 빨리 런타임 예외로 전환해서 던지는게 낫다.
  • 최근에 등장하는 표준 스펙 또는 오픈소스 프레임워크에서 API가 발생시키는 예외를 체크 예외 대신 언체크 예외로 정의하는 것이 일반화 되어가고 있다.
  • 런타임 예외를 사용하는 경우에는 API 문서나 레퍼런스 문서 등을 통해, 메서드를 사용할 때 발생할 수 있는 예외의 종류와 원인, 활용 방법을 자세히 설명해두자.

애플리케이션 예외

  • 시스템 또는 외부의 예외상황이 원이이 아니라, 애플리케이션 자체의 로직에 의해 의도적으로 발생 시키고, 반드시 catch 해서 조치를 취하도록 요구하는 예외이다.

  • 예시) 은행계좌에서 요청한 금액을 출금하는 서비스 시, 잔고 부족 예외

    • 방법 1 : 각 상황에 맞는 리턴값을 돌려주는 것 이다. (상황에 맞는 코드값)
    • 방법 2 : 잔고 부족과 같은 예외 상황에서는 비즈니스적인 의미를 띤 예외를 던지도록 만든다.

4.1.5 SQLException 은 어떻게 됐나?

  • 대부분의 SQLException 은 복구가 가능하지 않다.
  • 즉, 가능한 빨리 RuntimeException (Unchecked Exception) 으로 전환해주어야 한다.
  • JdbcTemplate 템플릿과 콜백 안에서 발생하는 모든 SQLException을 런타임 예외인 DataAccessException 으로 포장해서 던져준다.
  • 개발자는 상황에 맞게 해당 예외를 잡아서 처리해주면 된다.

4.2 예외 전환

예외 전환의 목적은 아래와 같다.

  • 런타임 예외로 포장해서 굳이 필요하지 않은 catch / throws를 줄여주는 것
  • 로우레벨의 예외를 좀 더 의미있고 추상화된 예외로 바꿔서 던져주는 것

4.2.1 JDBC 의 한계

  • JDBC는 자바를 이용해 DB에 접근하는 방법을 추상화된 API 형태로 정의해놓고,
    각 DB업체가 JDBC 표준을 따라 만들어진 드라이버를 제공하게 해준다.

  • 즉, DB의 종류에 상관없이 일관된 방법으로 프로그램을 개발하게 해주는 도구 이다.

  • 하지만 DB 종류에 상관없이 사용할 수 있는 데이터 접근 코드를 작성하는 일은 쉽지 않다.
    현실적으로 2가지 걸림돌이 있다.

    • 비표준 SQL

      • SQL은 어느정도 표준화된 언어이고 몇가지 표준 규약이 있다.

      • 하지만, 개부분의 DB는 표준을 따르지 않는 비표준 문법과 기능을 제공한다.
        비표준 특정 DB 전용 문법은 매우 폭넓게 사용되고 있다.

      • 이를 해결할 수 있는 방법은 DAO를 DB별로 만들어 사용하거나 SQL을 외부에서 독립시켜서 바꿔 쓸 수 있게 하는 것이다.

    • 호환성 없는 SQLException의 DB 에러 정보

      • DB마다 에러의 종류와 원인도 제각각이다.
        그러므로 JDBC는 데이터 처리 중 발생하는 예외를 그냥 SQLException 하나에 모두 담아버린다.

      • 예외가 발생한 원인은 SQLException 안에 담긴 에러 코드와 SQL 상태정보를 참조해봐야 알 수 있다.
        하지만, 이마저도 DB 에러 코드가 DB 별로 모두 다르다.

      • SQL 상태정보는 DB 종류에 독립적으로 정보를 제공하므로, 이를 통해 어느정도 에러 처리가 가능하지만,
        이 상태정보 또한 제대로 만들어지지 않는 경우가 대부분이다.

      • 즉, 호환성 없는 SQLException의 정보를 활용하여 DB에 독립적인 유연한 코드를 작성하는 건 불가능에 가깝다.

4.2.2 DB 에러 코드 매핑을 통한 전환

결국 DB에 독립적인 DAO를 만들기 위해서는, 위 2가지 걸림돌을 해결해야 한다.
이번장에서는 2번째 문제인 호환성 없는 SQLException의 DB 에러 정보에 대한 해결책을 살펴보도록 하겠다.

해결 방법은 DB별 에러 코드를 참고하여 발생한 예외의 원인이 무엇인지 해석해주는 기능을 만드는 것 이다.

예제) 키 값이 중복될 경우 발생하는 에러 코드

  • MySQL : 1062, Oracle : 1, DB2 : -803

스프링은 DB별 에러 코드를 분류해서 스프링이 정의한 예외 클래스와 매핑해놓은 에러 코드 매핑 정보 테이블을 만들어두고 이를 이용한다.

스프링이 정의한 예외 클래스란?

  • DataAccessException의 서브 클래스이다.

  • 데이터 액세스 작업 중에 발생할 수 있는 예외 상황을 수십가지 예외로 분류하고 이를 추상화해 정의한 다양한 예외 클래스이다.

    예시 : BadSqlGrammarException, DuplicatedKeyException, ...

4.2.3 DAO 인터페이스와 DataAccessException 계층 구조

자바에는 JDBC 외에도 데이터 액세스를 위한 표준 기술이 존재한다.

  • JDO, JPA, TopLink, 하이버네이트, iBatis, ...

DataAccessException은 의미가 같은 예외라면, 데이터 액세스 기술의 종류와 상관없이 일관된 예외가 발생하도록 만들어준다.
즉, 데이터 액세스 기술에 독립적인 추상화된 예외를 제공하는 것이다.

스프링은 이와 같이 DataAccessException 계층구조를 이용해 데이터 액세스 기술에 독립적인 예외를 정의하고 사용한다.

스프링의 데이터 액세스 전략이나, DataAccessException의 예외 사용법은 11장에서 더 자세히 알아보도록 하겠다.

소스코드

profile
https://github.com/yuseogi0218

0개의 댓글