토비의 스프링 정리 프로젝트 #4.1 예외처리와 사라진 SQL Exception

Jake Seo·2021년 8월 5일
2

토비의 스프링

목록 보기
24/29

예외처리의 중요성

일반적으로 개발자들은 예외처리를 하기 귀찮아한다. 정상적인 결과와 흐름을 보여주는 코드도 만들기 힘든데, 굳이 무언가 실수하거나 잘못된 시도를 해야만 볼 수 있는 예외에 신경쓰기 귀찮아한다. 그래서 예외와 관련된 코드는 자주 엉망이 되거나 무성의해지기 쉽다.

예외처리를 잘못하거나 대충하면, 잘못된 예외처리 코드 때문에 오히려 더 찾기 힘든 버그를 생성할 수 있다.

스프링의 JdbcTemplate을 대표로 하는 데이터 엑세스 기능에 담겨있는 예외처리와 관련된 접근 방법을 살펴보며 예외를 처리하는 베스트 프랙티스도 알아보자.

사라진 SQLException

public void deleteAll() throws SQLException {
  this.jdbcContext...
}
public void deleteAll() {
  this.jdbcTemplate...
}

위 2개의 deleteAll() 메소드 코드는 각각 직접 생성했던 jdbcContext 클래스를 이용한 방법과 스프링에서 제공하는 jdbcTemplate을 이용한 방법으로 구현한 메소드들이다. 차이는 명확하게 throws SQLException이 사라졌다는 것에서 찾을 수 있다.

SQLExceptionJDBC API 메소드들이 던져주는 것이므로 당연히 있어야 한다. 비록 로그를 남긴다고 해도, 다시 JDBC 템플릿 메소드 밖으로 던져서 예외상황이 발생했다는 사실을 알려야 했을 것이다. 어째서 jdbcTemplate도 내부적으로 JDBC API를 쓸텐데 SQLException을 왜 밖으로 던지지 않을까?

초난감 예외처리

안 좋은 예외처리의 대표주자들을 살펴보자.

예외 블랙홀

try {
  ...
} catch (Exception e) {
  // do nothing.
}

자바의 기초를 배울 때 흔히 볼 수 있는 코드이다. 예외를 잡고 아무것도 하지 않는 코드이다. 예외 발생을 무시해버리고 정상적인 상황인 것처럼 다음 라인으로 넘어가겠다는 분명한 의도가 있는 게 아니라면 연습 중에도 절대 만들어서는 안 되는 코드이다. 일반적으로는 IDE의 경고 때문에 그 상황을 모면하려고 위와 같은 코드를 작성하는 경우가 많다.

유명 오픈소스 제품의 코드나 실전 프로젝트에 작성된 코드에도 위와 같은 경우가 있기도 하다. 처음 예외처리용 코드를 만들면서 생긴 잘못된 습관이 계속 남아 있는 게 문제의 원인이다.

위와 같은 코드의 가장 큰 문제는 프로그램 실행 중 오류가 있어서 예외가 발생했는데, 그 예외가 어떤 원인인지, 어디서 발생했는지 전부 무시하고 로그도 남겨주지 않으며 정상적인척 진행되기 때문이다.

메모리, 리소스가 소진되거나 예상치 못한 다른 문제를 일으킬 수 있다.

예외 출력만 하기

} catch (Exception e) {
  System.out.println(e);
}
} catch (Exception e) {
  e.printStackTrace();
}

출력을 해주는데 무슨 문제일까? 싶다. 개발 중에는 콘솔이나 서버 실행창의 로그를 주시하는 상태라 쉽게 볼 수 있지만, 다른 로그나 메세지에 묻혀버리면 금방 놓치기 쉽상이다. 또한 운영서버에 올라가면 로그 모니터링을 하지 않는 이상 이 예외 코드는 심각한 폭탄으로 남아있을 것이다. 단순히 출력하는 것은 예외를 처리한 것이 아니다.

예외를 처리할 때 지켜야 할 핵심 원칙은 한 가지이다.

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

SQL Exception이 발생했다는 것은 데이터 엑세스 로직에 버그가 있거나, 서버가 죽었거나, 네트워크가 끊겼거나 심각한 상황이 벌어졌기 때문이다. 그런데 그냥 예외를 무시하고 다음 코드로 이어가는 것은 더 큰 문제를 만들어낼 수 있다. 로그를 출력하는 것은 아무런 도움이 되지 않는다.

그나마 나은 예외처리

} catch (SQLException e) {
  e.printStackTrace();
  System.exit(1);
}

물론 실전에서는 위와 같이 만들면 안되겠지만, 예외를 잡아서 뭔가 조치를 취할 방법이 없다면 잡지 말라는 뜻이다. 무작정 예외를 무시하거나 잡아먹기보다 차라리 throws SQLException을 선언해서 메소드 밖으로 던지고 자신을 호출한 코드에 책임을 전가해버리자.

무의미하고 무책임한 throws

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

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

public void method3() throws Exception ...

모든 메소드 선언에 throws Exception을 기계적으로 붙이는 개발자도 있다..이 경우 예외를 흔적도 없이 먹어치우는 예외 블랙홀보다는 훨씬 낫지만, 이런 무책임한 throws 선언도 심각한 문제가 있다.

throws Exception이 무작위하게 선언되어 있다면, 메소드 선언에서 의미 있는 정보를 알 수 없다. 무엇인가 실행 중에 예외적인 상황이 발생할 수 있다는 것인지, 아니면 그냥 습관적으로 복사해서 붙여 놓은 것인지 알 수 없다. 결국 어떤 Exception이 터지는지도 모르는채 계속 따라서 throws Exception을 남발하게 될 것이다.

결과적으로 적절한 처리를 통해 복구될 수 있는 예외도 제대로 다룰 수 있는 기회를 박탈당한다.

예외 처리에 대한 나쁜 습관은 어떤 경우에도 용납하지 않아야 한다.

예외의 종류와 특징

예외 처리에서 가장 큰 이슈는 체크 예외(checked exception)라고 불리는 명시적인 처리가 필요한 예외를 사용하고 다루는 방법이다.

Error

java.lang.Error 클래스의 서브 클래스들이 있다. 시스템에 비정상적인 상황이 발생했을 경우 사용되며, 자바 VM에서 발생시키는 것이므로 애플리케이션 코드에서 잡으려고 하면 안된다. OutOfMemoryErrorThreadDeath같은 에러는 catch 블록으로 잡아봤자 아무런 대응 방법이 없다.

시스템 레벨에서 이뤄지는 에러기 때문에 애플리케이션에서는 이런 에러에 대한 처리를 신경쓸 필요가 없다.

Exception과 체크 예외

java.lang.Exception 클래스와 그 서브 클래스로 정의되는 예외들은 에러와 달리 개발자들이 만든 애플리케이션 코드의 작업 중에 예외 상황이 발생한 경우에 사용된다.

Exception 클래스는 체크 예외(checked exception)언체크 예외(unchecked exception)로 구분된다.

  • 체크 예외Exception 클래스의 서브 클래스이면서, RuntimeException 클래스를 상속하지 않은 것들을 말한다.
    • 체크 예외는 IDE에서 예외처리를 강요한다.
  • 언체크 예외RuntimeException을 상속한 클래스들을 말한다.
    • 언체크 예외는 IDE에서 예외처리를 강요하지 않는다.

자바는 RuntimeException 클래스와 RuntimeException을 상속한 클래스들을 특별하게 다룬다.

일반적 예외는 RuntimeException을 상속하지 않은 예외를 말한다고 볼 수 있다. JDK 초기 설계자들은 체크 예외를 발생 가능한 모든 예외에 적용하려고 노력했던 것 같으나, 현재의 트렌드는 조금 다르다.

RuntimeException과 언체크/런타임 예외

java.lang.RuntimeException 클래스를 상속한 예외들은 명시적인 예외처리를 강제하지 않기 때문에 언체크 예외라고 불린다. 또는 런타임 예외라고도 한다.

주로 프로그램의 오류가 있을 때 발생하도록 의도된 것들이다. 할당되지 않은 객체 변수를 사용하려 할 때 나타나는 NullPointerException이나 허용되지 않은 인자를 주어 메소드를 호출할 때 발생하는 IllegalArgumentException등이 있다. 이런 예외는 피할 수 있지만, 개발자가 부주의해서 발생할 수 있는 경우에 발생하도록 만든 것이다.

처음 JDK 설계자들은 체크 예외의 예외처리를 강제하는 설계를 했지만, 이전에 보았듯, 예외 블랙홀이나 무책임한 throws와 같은 코드가 남발되었다.

최근 새로 등장하는 자바 표준 스펙의 API들은 가능한 체크 예외를 만들지 않는 경향이 있기도 하다.

예외처리 방법

예외를 처리하는 일반적인 방법을 먼저 살펴보고 효과적인 예외처리 전략을 생각해보자.

예외 복구

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

이를테면, 사용자가 요청한 파일을 읽으려고 시도했는데, 해당 파일이 없거나 다른 문제가 있어서 읽어지지 않는 경우 다른 파일을 이용해보라고 안내 메세지를 줄 수 있다.

단, IOException 에러 메세지와 같은 것이 사용자에게 그냥 던져진다면 예외 복구라고 볼 수 없다.

네트워크 접속이 원활하지 않은 경우에는 접속 재시도를 시도해서 예외 복구를 할 수도 있다. 물론 정해진 횟수만큼 접속 재시도에 실패한다면, 그냥 예외를 던져야할지도 모른다.

체크 예외들은 이렇게 어떻게든 예외가 복구될 가능성이 있는 예외이다.

int maxRetry = MAX_RETRY;

while(maxRetry --> 0) {
  try {
    ... // 예외가 발생할 수 있는 시도
    return; // 작업 성공
  }
  catch(SomeException e) {
    // 로그 출력, 정해진 시간만큼 대기
  }
  finally {
    // 리소스 반납, 정리 작업
  }
}
throw new RetryFailedException(); // 최대 재시도 횟수를 넘기면 직접 예외 발생

예외처리 회피

예외 처리를 자신이 담당하지 않고, 자신을 호출한 쪽으로 예외를 던져버리는 방식이다. throws 문으로 선언해서 예외가 던져지게 하거나, catch문으로 일단 예외를 잡은 후에 로그를 남기고 다시 예외를 throw로 던지는 것이다.

catch 블록을 사용하는 것은 예외를 회피하는 것이 아니다. 강제적으로 정상 흐름으로 만드는 행위에 더 가깝다.

public void add() throws SQLEXception {
  // JDBC API
}
public void add() throws SQLException {
  try {
    // JDBC API
  }
  catch(SQLException e) {
    // 로그 출력
    throw e;
  }
}

jdbcContextjdbcTemplate이 사용하는 콜백 오브젝트는 발생하는 SQLException을 자신이 처리하지 않고, 템플릿으로 던져버렸다. SQLException을 처리하는 일은 콜백 오브젝트의 책임이나 역할이 아니라고 보기 때문이다.

하지만, 콜백과 템플릿의 관계와 같은 명확한 역할 분담이 없는 상황에서 무작정 예외를 던지면 무책임한 책임회피일 수 있다. 만약 DAOSQLException을 던지면, 이 예외는 처리할 곳이 없어서 서비스 레이어로 갔다가 컨트롤러로 가고 결국 그냥 서버로 갈 것이다.

이 과정에서 DAO를 사용하는 메소드들은 그냥 아무 생각없이 throws Exception을 추가할지도 모르는 일이다.

예외를 회피하는 것 또한 예외를 복구하는 것처럼 의도가 분명해야 한다. 예외 처리 책임을 분명히 지게 하거나, 자신을 사용하는 쪽에서 예외를 다루는게 최선의 방법이라는 분명한 확신이 있어야 한다.

예외 전환

예외 전환(exception translation)도 마찬가지로 예외를 메소드 밖으로 던지는 것이다. 하지만 예외 회피와 달리 발생한 예외를 그대로 넘기는 것이 아니라, 적절한 예외로 전환해서 던진다는 특징이 있다.

첫번째 목적: 명확한 의미 전달을 위한 전환

  • 예외를 그대로 던지는 것이 예외 상황에 대한 적절한 의미부여가 되지 않는 경우, 의미를 분명하게 해줄 수 있는 예외로 바꿔주기 위해서다.

이를테면, 사용자 아이디를 등록하는 과정에서 중복된 사용자 아이디가 존재한다면 DB에서 에러가 발생하고, JDBC APISQLException을 발생시킨다. 이 상황에서 DAOSQLException을 그대로 던진다면, SQLException은 포괄적인 의미를 갖기 때문에 서비스 계층에서는 왜 예외가 발생했는지 쉽게 알 수 없다. 그냥 DB에서 어떠한 이유로 예외가 발생했구나 정도로 추측할 수 밖에 없다.

이 상황에서 예외 전환을 이용하여 SQLExceptionDuplicateUserIdException과 같은 예외로 변경하여 던져준다면, 예외가 일어난 이유가 한 층 명확해지고 사용자는 다른 아이디를 사용하는 것으로 적절한 복구 작업을 수행할 수 있다.

public void add(User user) throws DuplicateUserIdException, SQLException {
  try {
    // JDBC를 이용해 user 정보를 DB에 추가하는 코드 또는
    // 그런 기능을 가진 다른 SQLException을 던지는 메소드를 호출하는 코드
  }
  catch(SQLException e) {
    // ErrorCode가 MySQL의 "Duplicate Entry(1062)"이면 예외 전환
    if (e.getERrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY)
      throw DuplicateUserException();
    else
      throw e; // 그 외의 경우는 SQLException 그대로
  }
}

예외 전달을 할 때, 예외를 중첩 예외(nested exception)로 만들면 좋다. 중첩 예외는 getCause() 메소드를 이용하여 처음 발생한 예외가 무엇인지 확인할 수 있게 해준다.

catch(SQLException e) {
  ...
  throw DuplicateUserIdException(e);
}
catch(SQLException e) {
  ...
  throw DuplicateUserIdException().initCause(e);
}

initCause()를 이용해 근본 원인이 되는 예외를 넣어주어도 된다.

두번째 목적: 예외를 처리하고 쉽고 단순하게 만들기 위해 포장(wrap)

예외처리를 강제하는 체크 예외를 언체크예외인 런타임 예외로 바꾸는 경우에 사용한다. 비즈니스적으로 의미도 없고, 복구 가능하지도 않은 예외에 대해서는 런타임 예외로 포장해서 던지는 편이 낫다.

try {
  ...
} catch (NamingException ne) {
  throw new EJBException(ne);
} catch (SQLException se) {
  throw new EJBException(se);
} catch (RemoteException re) {
  throw new EJBException(re);
}

위 코드에서 EJBExceptionRuntimeException 클래스를 상속한 런타임 예외다. 잡아도 복구할 방법이 없다.

반대로 애플리케이션 로직상에서 예외조건이 발견되거나, 예외 상황이 발생할 수도 있다. 이런 것은 API가 던지는 예외가 아니라 애플리케이션 코드에서 의도적으로 던지는 예외다. 이 때는 체크 예외를 사용하는 것이 적절하다. 비즈니스적인 의미가 있는 예외는 이에 대한 적절한 대응이나 복구작업이 필요하기 때문이다.

일반적으로 체크 예외를 계속 throws를 사용해 넘기는 건 무의미하다. 메소드 선언만 지저분해지고 아무런 장점도 없다. 어차피 복구 불간으한 예외라면 가능한 빨리 런타임 예외로 포장해 던지게 해서 다른 메소드를 작성할 때 불필요한 throws 선언이 필요하지 않도록 만들자.

대부분 서버 환경에서는 애플리케이션 코드에서 처리하지 않고 전달된 예외들을 일괄적으로 다룰 수 있는 기능을 제공한다.

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

예외처리 전략

런타임 예외의 보편화

체크 예외를 강제함으로 인해 어떤 개발자들은 나쁜 예외처리 습관이 생겼을 것이다. 로직을 작성하기도 바쁜데 해당 예외를 복구할 수 있는지까지 알아보고 예외처리 로직을 작성하라니 너무 귀찮은 작업이다.

서버 환경은 일반적인 애플리케이션 환경과 다르다. 이를테면 일반 애플리케이션에서는 파일을 처리한다고 했을 때, 해당 파일이 처리 불가능하면, 처리 불가능하다는 메세지와 함께 다른 파일을 고르라는 안내 메세지를 띄우고 사용자가 다른 파일을 고를 때까지 기다려줄 수 있는 반면, 서버 환경에서는 한 번에 다수의 사용자가 접근하여 해당 서비스를 이용하기 때문에 안내정도는 가능해도 작업을 중지하고 해당 사용자와 바로 커뮤니케이션하면서 예외 상황을 복구할 수 없다.

차라리 애플리케이션 차원에서 예외 상황을 미리 파악하고 예외가 발생하지 않도록 차단하는 게 좋다. 프로그램의 오류나 외부 환경으로 인해 예외가 발생하는 경우, 해당 요청의 작업을 취소하고 서버 관리자나 개발자에게 통보해주는 편이 좋다.

자바의 환경이 서버로 이전되면서 체크 예외의 활용도와 가치는 점점 떨어지고 있는 추세이다. 서버에서 대응이 불가능한 체크 예외라면 빠르게 런타임 예외로 전환해서 던지는 게 낫다. 최근 API들은 체크 예외보다는 언체크 예외를 많이 쓴다. 언체크 예외도 필요하면 얼마든지 catch 블록으로 잡아서 복구하거나 처리 가능하지만, 대부분은 복구 불가능한 상황이다.

add() 메소드의 예외처리

현 상황

현재의 상황은 아래와 같다.

    @Test
    public void addDuplicateUsers() {
        User user = new User("user11", "김똘일", "1234");

        userDao.add(user);
        userDao.add(user);
    }

위와 같이 테스트 코드를 작동시키면

org.springframework.dao.DuplicateKeyException: PreparedStatementCallback; SQL [insert into users(id, name, password) values (?, ?, ?)]; 오류: 중복된 키 값이 "users_pkey" 고유 제약 조건을 위반함
  Detail: (id)=(user11) 키가 이미 있습니다.; nested exception is org.postgresql.util.PSQLException: 오류: 중복된 키 값이 "users_pkey" 고유 제약 조건을 위반함
  Detail: (id)=(user11) 키가 이미 있습니다.

그래도 스프링의 JdbcTemplate 덕택에 어느정도 알아들을 수 있는 수준의 예외 메세지가 전달된다.

예외처리 전략을 이용한 add()

public class DuplicateUserIdException extends RuntimeException{
    public DuplicateUserIdException(Throwable cause) {
        super(cause);
    }
}

public void add() throws DuplicateUserIdException {
  try {
    // JDBC를 이용해 user 정보를 DB에 추가하는 코드 또는
    // 그런 기능이 있는 다른 SQLException을 던지는 메소드를 호출하는 코드
  }
  catch (SQLException e) {
    if (e.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY)
      throw new DuplicateUserIdException(e); // 예외 전환
    else
      throw new RuntimeException(e); // 예외 포장
  }
}

기존에 체크 예외SQLExceptioncatch로 잡아서 중복된 아이디를 입력했을 때 나오는 에러 번호인 MysqlErrorNumbers.ER_DUP_ENTRY에 대해서는 RuntimeException을 상속한 DuplicateUserIdException으로 예외 전환예외 포장해주고, 다른 에러 번호에 대해서는 단순히 RuntimeException으로 언체크 예외예외 포장만 해준다.

DuplicateUserIdException이 발생하면, 사용자가 요청한 아이디 대신 사용할 수 있는 추천 아이디를 만들어 중복 메세지와 함께 제공해주면 좋을 것이다.

런타임 예외를 일반화하면 단점도 있다. 컴파일러가 예외처리를 강제하지 않으므로 신경쓰지 않으면 예외 상황을 충분히 고려하지 않을 수 있다. 런타임 예외를 사용하는 경우엔 API 문서, 레퍼런스 문서 등을 통해 메소드를 사용할 때 발생할 수 있는 예외의 종류와 원인, 활용 방법을 자세히 설명해두자.

애플리케이션 예외

런타임 예외 중심 전략은 굳이 이름을 붙이자면 낙관적 예외처리 기법이다. 복구할 수 있는 예외는 없다고 가정하고, 예외가 생겨도 어차피 런타임 예외 이므로 시스템에서 알아서 처리해줄 것이고, 꼭 필요한 경우는 런타임 예외라도 잡아서 복구하거나 대응할 수 있으니 문제될 것이 없다는 낙관적 태도를 기반으로 한다.

혹시 놓치는 예외가 있을까 처리를 강제하는 체크 예외의 비관적인 접근 방법과 대비된다.

반면, 시스템 혹은 외부 상황이 원인이 아닌 애플리케이션 자체의 로직에 의해 의도적으로 발생시키고 반드시 catch 해서 무엇인가 조치를 취하도록 요구하는 예외도 있다. 이런 예외들을 일반적으로 애플리케이션 예외라고 한다.

은행의 출금 작업을 예로 들면, 잔고가 부족할 때는 InsufficientBalanceException과 같이 적절한 비즈니스적 의미를 띤 예외를 던지면 개발자가 잊지 않고 자주 발생 가능한 예외 상황에 대한 로직을 구현하도록 강제할 수 있다. 이마저도 무책임하게 throws Exception을 달아놓는 경우에는 무시당하기 쉽지만, 기본적으로 런타임 예외로 만들어두는 것보다는 상대적으로 안전하다.

try {
  BigDecimal balance = account.withdraw(amount);
  ...
  // 정상적인 처리 결과를 출력하도록 진행
}
catch(InsufficientBalanceException e) { // 체크 예외
  // InsufficientBalanceException에 담긴 인출 가능한 잔고 금액 정보를 가져옴
  BigDecimal availFunds = e.getAvailFunds();
  ...
  // 잔고 부족 안내 메세지를 준비하고 이를 출력하도록 진행
}

그래서 SQLException은 어떻게 됐나?

앞서 체크 예외언체크 예외를 배워보았고, 딱히 코드 레벨에서 복구 방법이 없는 경우 예외 전환을 통해 언체크 예외를 던져버리는 편이 낫다는 것을 배웠다. 런타임 예외의 보편화와 함께 만일 비즈니스적으로 더 명확한 의미를 줄 수 있는 경우에는 의미를 분명하게 전달할 수 있는 예외를 만들고 중첩 예외로 던져버리는 편이 낫다는 결론을 얻었다.

복구 불가능한 예외를 괜히 체크 예외로 만들면 나쁜 예외처리 습관을 가진 개발자에 의해 더 최악의 시나리오가 발생할 수도 있다.

위와 같은 가정을 놓고 SQLException을 생각해봤을 때, 과연 SQLException이 복구 가능한 예외일까? 라는 생각을 해보아야 한다. 99%의 SQLException은 코드 레벨에서는 복구할 방법이 없다. 일반적으로 해당 예외가 발생하는 이유는 SQL 문법이 틀렸거나, 제약조건을 위반했거나, DB 서버가 다운됐거나, 네트워크가 불안정하거나, DB 커넥션 풀이 꽉 찬 경우 등이다. 주로 DB에 종속적인 에러라서 기껏해봐야 DB를 연결하는데 네트워크적인 문제가 있을 때 일정 간격으로 연결을 재시도해보는 것 말고는 없을 것이다.

이 외에도 시스템 예외, 애플리케이션의 코드의 버그나 미처 다루지 않았던 범위를 벗어난 값 때문에 발생한 예외 등은 복구할 방법이 없다.

복구할 수 없는 경우에는 런타임 예외의 보편화를 따라 기계적인 throws 선언이 등장하도록 방치하지 말고, 가능한 빨리 언체크/런타임 예외로 전환해주자.

그래서 SQLException이 사라진 이유는 스프링의 JdbcTemplate런타임 예외의 보편화 전략을 따르고 있기 때문이다. JdbcTemplate 템플릿과 콜백 안에서 발생하는 모든 SQLException을 런타임 예외인 DataAccessException으로 포장해서 던져준다. 따라서 JdbcTemplate을 사용하는 UserDao 메소드에서는 꼭 필요한 경우에만 런타임 예외인 DataAccessException을 잡아서 처리하면 되고 그 외의 경우에는 무시하면 된다.

JdbcTemplateupdate(), queryForInt(), query() 메소드 선언을 잘 살펴보면 다음과 같이 모두 throws DataAccessException이라고 되어 있음을 발견할 수 있다. throws로 선언되어 있긴 하지만 DataAccessException이 런타임 예외이므로 update()를 사용하는 메소드에서 이를 잡거나 다시 던질 이유는 없다.

그 밖에도 스프링 API 메소드에 정의되어 있는 대부분의 예외는 런타임 예외다. 따라서 발생 간읗나 예외가 있다고 하더라도 이를 처리하도록 강제하지 않는다.

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글