• Home
  • About
    • Ara Jo photo

      Ara Jo

      Aspiring Backend Developer :)

    • Learn More
    • Email
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

Book/토비의스프링vol1/ 4장. 예외

15 Jun 2021

Reading time ~4 minutes

  • JdbcTemplate을 대표로 하는 스프링의 데이터 엑세스 기능에 담겨 있는 예외 처리와 관련된 접근 방법과 이를 통해 예외를 처리하는 예시를 살펴보자.
  • JdbcContext를 JdbcTemplate으로 바꾸면 SQLException이 사라짐

초난감 예외처리

  • 예외는 처리되어야 한다!
  1. 예외를 catch로 잡고 아무것도 하지 않는 것은 원치 않는 예외가 발생하는 것보다 훨씬 더 안좋음
    => 어디선가 오류가 발생해도 무시하고 계속 진행해버리기 때문에 시스템 오류의 원인을 찾기 힘들어짐
    • 예외 처리 핵심 원칙 : ‘모든 예외는 적절하게 복구되든지 작업을 중단시키고 개발자에게 분명하게 통보되어야 한다’
    • 예외를 잡아 뭔가 조치를 취할 방법이 없다면 잡지 말아야 한다
    • 메서드에 throws SQLException을 선언해서 자신을 호출한 코드에 예외처리 책임 전가하기
  2. 무의미하고 무책임한 throws Exception : 첫 번째 문제보다는 낫지만, 매우 안 좋은 예외처리 방법

예외의 종류와 특징

  • checked exception이라 불리는 명시적인 처리가 필요한 예외를 사용하고 다루는 방법
  • 자바에서 throw를 통해 발생시킬 수 있는 예외 3가지
    1. Error : java.lang.Error 의 서브 클래스, 시스템에 뭔가 비정상적인 상황이 발생했을 경우 사용
      • 어플리케이션에서는 이런 에러에 대한 처리는 신경쓰지 않아도 됨
    2. Exception과 checked Exception : java.lang.Exception클래스와 그 서브 클래스, 개발자들이 만든 애플리케이션 코드의 작업 중 예외상황이 발생한 경우 사용
      • Checked Exception : RuntimeException을 상속하지 않은 것들
      • Unchecked Exception : RuntimeException을 상속한 클래스
      • 체크 예외가 발생할 수 있는 메서드를 사용할 경우, 반드시 catch문으로 잡든지, 아니면 다시 throws를 정의해서 메서드 밖으로 던져야 함
    3. RuntimeException과 언체크/런타임 예외 : 주로 프로그램의 오류가 있을 때 발생
      • 에러와 마찬가지로 catch문으로 잡거나 throws 선언하지 않아도 됨
      • 피할 수 있지만 개발자가 부주의해서 발생할 수 있는 경우에 발생 (ex. NullPointerException, IllegalArgumentException)

예외처리 방법

  1. 예외 복구 : 예외상황을 파악하고 문제를 해결해서 정상 상태로 돌려놓는 것
    • 예외로 인해 기본 작업 흐름이 불가능하면 다른 작업 흐름으로 자연스럽게 유도
    • 단, 에러 메시지가 사용자에게 그냥 던져지는 것은 예외복구라고 볼 수 없음
  2. 예외처리 회피 : 예외처리를 자신을 호출한 쪽으로 던져버리는 것
    • throws문으로 선언해서 알아서 던져지게 하거나 catch문으로 일단 예외를 잡은 후 로그를 남기고 다시 던지는 것
    • JdbcContext나 JdbcTemplate이 사용하는 콜백 오브젝트는 SQLException에 대한 예외를 회피하고 템플릿 레벨에서 처리하도록 던짐
    • 예외를 회피하는 것은 의도가 분명해야 함
  3. 예외 전환 : 예외 회피와 비슷하게 예외를 복구해서 정상 상태로 만들 수 없기 때문에 예외를 메서드 밖으로 던지는 것, but 그대로 넘기는 게 아니라 적절한 예외로 전환해서 던짐
    1. 내부에서 발생한 예외를 그대로 던지는 것이 그 예외상황에 대한 적절한 의미를 부여해주지 못하는 경우, 더 분명한 예외로 바꿔주기 위해
      ex) DAO에서 SQLException을 잡아 DuplicateUserException과 같은 의미가 분명한 예외로 바꿔서 던지는 경우
      • 중첩 예외 (nested exception) : 전환하는 예외에 원래 발생한 예외를 담는 것(생성자 or initCause() 이용), getCause()를 이용해 처음 발생한 예외 확인 가능
    2. 예외처리를 강제하는 체크 예외를 언체크 예외인 런타임 예외로 바꾸는 경우, 예외를 처리하기 쉽고 단순하게 만들기 위해 포장하는 것
      ex) 비즈니스적 의미가 있는 예외가 아닌 EJB 컴포넌트에서 발생하는 체크예외를 런타임 예외인 EJBException으로 포장해서 던지는 경우
      • 비즈니스적 의미가 있는 예외는 적절한 대응이나 복구 작업이 필요하기 때문에 체크 예외를 사용하는 것이 좋음

예외처리 전략

  1. 런타임 예외의 보편화
    • 대응이 불가능한 체크 예외는 빨리 런타임 예외로 전환해서 던지는 게 낫다
    • 예전엔 복구가능성이 조금이라도 있다면 체크 예외로 만들었지만, 지금은 항상 복구가능한 것이 아니라면 일단 언체크 예외로 만듦
    • 런타임 예외를 일반화해서 사용하는 것은 여러모로 장점이 많지만, 컴파일러가 예외처리를 강제하지 않으므로 사용에 더 주의를 기울여야 함
  2. 애플리케이션 예외
    • 외부의 예외상황이 아니라 애플리케이션 로직에 의해 의도적으로 발생시키고, 반드시 catch해서 조치를 취해야하는 경우
    1. 정상요청과 예외상황에 대해 다른 종류의 리턴 값을 돌려주기
      • 리턴 값을 일종의 결과 상태를 나타내는 정보로 활용
      • 리턴 값을 명확하게 코드화하고 잘 관리하지 않으면 혼란이 생길 수 있음
      • 리턴 값을 확인하는 조건문이 자주 등장해 코드의 가독성이 떨어짐
    2. 정상요청은 그대로 두고 예외상황에 비즈니스 의미를 띤 예외를 던지기
      • 예외가 발생할 수 있는 코드를 try 블록 안에 두고 예외처리는 catch블록에 모아두기 때문에 코드 가독성이 좋아짐
      • 이 때는 의도적으로 체크 예외로 만들어서 개발자가 잊지 않고 예외상황에 대한 로직을 구현하도록 강제하는 것이 좋음

SQLException의 행방

  • SQLException의 99%는 복구할 방법이 없으므로 빨리 개발자에게 예외가 발생했다는 사실이 알려지도록 전달하는 방법밖에 없음 => 가능한 빨리 언체크/런타임 예외로 전환해줘야 함
  • 스프링의 JdbcTemplate은 이 예외처리 전략을 따르고 있다! => 모든 SQLException을 런타임 예외인 DataAccessException으로 포장해서 던짐
  • DataAccessException은 SQLException에 담긴 다루기 힘든 상세한 예외정보를 의미있고 일관성 있는 예외로 전환해서 추상화해주려는 용도로 쓰이기도 함

JDBC의 한계

  • DB종류에 상관없이 사용할 수 있는 데이터 엑세스 코드를 작성하는 것이 어려운 이유?
  1. 비표준 SQL : 대부분의 DB가 비표준 문법과 기능도 제공하기 때문
    • 이런 비표준 SQL이 DAO 코드에 들어가면 특정 DB에 종속적인 코드가 됨
      => 표준 SQL만 사용하거나, DB별 DAO를 만들거나, SQL을 외부에 독립시키는 방법이 있음 (7장)
  2. 호환성 없는 SQLException의 DB 에러정보 : DB마다 에러의 종류와 원인도 제각각이기 때문

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

  • SQLException에 담긴 SQL 상태 코드는 신뢰할 수 없으므로 어느정도 일관성이 유지되는 DB 에러 코드를 참고하자
  • 스프링은 DataAccessException이라는 SQLException을 대체할 런타임 예외를 정의하고 있을 뿐 아니라 DataAccessException의 서브클래스로 세분화된 예외 클래스들을 정의하고 있음
  • 하지만, DB마다 에러 코드가 제각각이라는 문제가 있음
    => 스프링은 DB별 에러 코드를 분류해 스프링이 정의한 예외 클래스와 매핑해놓은 여러 코드 매핑정보 테이블을 만들어두고 이용
    => JdbcTemplate은 SQLException을 단지 런타임 예외로 포장하는 것이 아니라 DB의 에러 코드를 DataAccessException 계층구조의 클래스 중 하나로 매핑해줌, DB가 달라져도 같은 종류의 에러라면 동일한 예외를 받을 수 있다!
    => 여전히 체크 예외라는 점과 SQL 상태정보를 이용한다는 아쉬움..

기술에 독립적인 UserDao 만들기

  • 인터페이스 사용 + 런타임 예외 전환 + DataAccessException 예외 추상화


booktobi_spring Share Tweet +1