1. 사라진 SQLException
    • JdbcTemplate로 변경 후 throws SQLException이 사라졌다.

        // 변경 전
        public void deleteAll() throws SQLException {
        	this.jdbcContext.executeSql("delete from users");	
        }
      
        // 변경 후
        public void deleteAll() {
        	this.jdbcTemplate.update("delete from users");	
        }
      
    1. 초난감 예외처리
      • SQLException을 알아보기 전에 간단한 초난감 예외처리를 살펴보자.

          // 예외를 catch하고 아무것도 하지 않는 코드. 
          // 절대 이렇게 짜서는 안된다.
          try{
          	...
          } catch(SQLException e){
        
          }
        
          // 출력은 해주나, 다른 로그에 묻혀버리기 십상이다.
          try{
          	...
          } catch(SQLException e){
          	System.out.println(e);
          }
        
          // 마찬가지로 묻혀버리기 십상이다. 양심에 찔린다..
          try{
          	...
          } catch(SQLException e){
          	e.printStacTrace();
          }
        
      • 무의미하고 무책임한 throws. 자신이 예외처리 하기 싫으니 계속 throws를 던지는 코드

          public void method1() throws Exception {
          	method2();
          }
        
          public void method2() throws Exception {
          	method3();
          }
        
    2. 예외의 종류와 특징
      • java.lang.Error : 시스템이 비정상적인 상황이 발생했을 경우에 사용되며, 주로 JVM에서 발생시킨다. 따라서 특별한 처리를 할 필요 없다.
      • java.lang.Exception : 코드를 실행 중에 예외상황이 발생했을 경우에 사용되며, 체크 예외와 언체크 예외로 구분된다.
        • 체크 예외 : RuntimeException은 상속받지 않는 예외. 이 예외는 catch 또는 thorws로 잡아야지 컴파일 에러가 나지 않음.
        • 언체크 예외 : RuntimeException을 상속받는 예외. 명시적으로 예외처리를 강제하지 않음.
    3. 예외처리 방법
      • 예외복구 : 예외가 발생해도, 문제를 해결하고 정상상태로 돌려놓는 방식.

          // 재시도를 통해 예외 복구
          int retry = 3;
          while(retry-- > 0){
          	try{
          		...
          	} catch(...){
                    		
          	}
          }
          // 예외 복구 실패시, 최종적으로는 예외를 던진다.
          throw new RetryFailedException();
        
      • 예외처리 회피 : 예외를 자신이 처리하지 않고, throw. 예외를 회피하는 것은 의도가 분명해야 하며, 자신을 호출한 곳에서 예외처리 책임을 분명히 지게 해야 한다.
      • 예외 전환 : **예외를 다른 예외로 전환.
        • 의미를 분명한 예외로 전환

            public void add(User user) throws DuplicateUserIdException, SQLException {
            		try{
            				...
            		} catch(SQLException e){
            			// 에러를 파악해서 좀 더 분명한 에러를 자신이 만들어서 던짐
            			if(e.getErrorCode == MysqlErrorNumber.EP_DUL_ENTRY)
            				throw DuplicateUserIdException(e); // 에러의 근본 원인도 알려주기 위해 이전 예외를 포함시킴.
            			else
            				throw e;
            		}
            }
          
        • 예외를 간단하게 처리하도록 하기 위해 감싸서 전환. 예를 들어 프레임워크에서 관리하는 예외로 포장함.

    4. 예외처리 전략
      • 런타임(언체크) 예외의 보편화 : 체크 예외는 예외처리를 강제하는 것 때문에 무책임한 코드가 남발됐다고 비난하는 개발자도 많으며, 요즘 오픈소스들은 체크 예외를 만들지 않는 경향이 있다.
      • 따라서 앞 내용인 예외 전환에서 만든 DuplicateUserIdException도 사실 복구 가능한 예외이긴 하지만, 대부분의 SQLException은 복구불가능하므로 체크 예외보다는 언체크 예외로 만드는 편이 좋다.

          public class DuplicateUserIdException extends RuntimeException {
          		public DuplicateUserIdException(Throwable cause){
          		super(cause);
          	}
          }
        
      • 언체크 예외로 수정한 add 메소드는 다음과 같다.

          public void add(User user) throws DuplicateUserIdException {
          		try{
          				...
          		} catch(SQLException e){
          			if(e.getErrorCode == MysqlErrorNumber.EP_DUL_ENTRY)
          				throw new DuplicateUserIdException(e);
          			else
          				throw new RuntimeException(e); // SQLException도 런타임으로 포장
          		}
          }
        
      • 애플리케이션 예외 : 시스템 또는 외부의 예외상황이 원인이 아닌 애플리케이션 로직에 의해 발생한 예외를 칭한다. 예외가 발생할 경우 코드에서는 보통 다음과 같이 처리한다.
        • 다른 종류의 값을 리턴 (ex: 0, -1). 이러면 개발자들끼리 미리 의사소통으로 정해야 하지만, 잘못될 수 있으며, if문이 많아짐
        • 체크예외를 만들어 던져주기. 상대적으로 안전함
    5. SQLException은 어떻게 됐나?
      • SQLException은 복구할 방법이 거의 없으니 기계적인 throws 을 방치않게 Spring에서는 언체크/런타임 예외로 전환하였다.
      • Spring은 모든 SQLException을 DataAccessException으로 예외전환 방식을 사용하여 포장해서 던져준다.
      • Spring은 대부분의 예외는 런타임 예외
  2. 예외 전환
    1. JDBC의 한계
      • JDBC는 DB에 접근하는 방법을 추상화된 API형태로 제공하므로, 다양한 DB종류를 사용해도, 표준 인터페이스를 사용하여 일관된 방법으로 개발 가능하다.
      • 하지만 문제가 되는 부분이 두 가지 존재한다.
      • 비표준 SQL : 표준화된 SQL을 사용하긴 하지만 특정한 기능들은 DB 제조사마다 비표준 SQL을 사용한다.
      • 호환성 없는 SQLException의 DB 정보 : DB 제조사마다 던지는 에러의 종류와 원인이 다르다. XOPEN SQL 스펙에 정의된 에러에 대한 상태코드가 표준화는 되어있으나, 모든 DB제품이 이를 따르지는 않는다.
    2. DB 에러 코드 매핑을 통한 전환
      • 먼저 호환성 없는 SQLException의 DB 정보 문제를 Spring에서는 어떻게 해결했을까?
      • 각각 DB마다 에러코드를 해석하여 동일한 원인에 대하여 같게 처리한다. 예를 들어 키 값이 중복돼서 오류가 발생하는 경우 Mysql은 1062, 오라클은 1, DB2는 -803 이라는 에러 코드를 던진다. Spring은 이 에러코드를 매핑하여 DuplicateKeyException 예외를 던진다.
      • 매핑정보는 org.springframework.jdbc.support.sql-error-codes.xml 파일에 작성되어 있다.
      • 이처럼 JdbcTemplate를 이용한다면 JDBC에서 발생하는 DB 관련 예외는 거의 신경 쓰지 않아도 된다.
      • 그렇다면 이런 방식을 어떻게 생각했는지 DataAccessException이라는 예외를 가지고 아래 설명해본다.
    3. DAO 인터페이스와 DataAccessException 계층 구조 (이 챕터는 책을 직접 읽고 흐름을 이해하는게 중요)
      • DataAccessException : 스프링의 데이터 액세스 기술에 독립적인 추상화된 예외 클래스이다. JDBC 뿐만 아니라, JDO, JAP, iBatis 등에서 발생하는 예외도 이를 이용해서 처리한다.
      • 뜬끔없지만, DAO 인터페이스와 구현을 분리하는 이유는 무엇일까? 당연 분리하는 이유는 구현을 여러 방법으로 사용하기 위해서이며, DAO를 사용하는 클라이언트는 구현체가 어떻게 되었는지 상관하지 않아도 되기 때문이다.
      • 따라서 DAO를 인터페이스로 만들면 다음과 같다.

          public interface UserDao{
          		public void add(User user);
          		...
          }
        
      • 하지만, 실제 구현체가 예외를 만약에 던진다면 throws를 추가해야 하는데, 여러 DAO 기술들(JDBC, JPA, Hibernate 등등)이 던지는 예외가 다르다. 예외가 다르기 때문에 사실 다 다르게 표현돼버린다.

          public void add(User user) throws SQLException; // JDBC
          public void add(User user) throws PersistentException; // JPA
          public void add(User user) throws HibernateException; // Hibernate
        
          // 모든 예외를 받기 위해 이렇게 해도 되기는 하지만, 무책임하다.
          public void add(User user) throws Exception;
        
      • 그래서 예외를 DataAccessException으로 추상화했다.
      • 하지만! 문제점이 있다. 만약 사용자가 DAO 예외를 직접적으로 핸들링하고 싶다면, 추상화를 하게 되면 알 수가 없다.
      • (이 부분은 위 의문점에 해결책인 것 같은데, 잘 이해가 되지는 않는다.) 위 의문점을 해결하기 위해, 스프링은 발생하는 예외들을 추상화해서 DataAccessException 계층구조 안에 정리해놓았다.
      • DataAccessException 서브클래스중에는 각각 DAO 기술들에게 공통으로 적용되는 예외도 있지만, JPA나 Hibernate에서만 적용되는 서브클래스들도 존재한다.
    4. 기술에 독립적인 UserDao 만들기
      • UserDao를 인터페이스로 만들어보자.

          public interface UserDao {
          	void add(User user);
          	User get(String id);
          	...
          }
        
      • UserDao의 구현체를 만들어보자

          public class UserDaoJDbc implements UserDao{
          	...
          }
        
          <bean id="userDao" class="springbook.dao.UserDaoJdbc">
          	...
          </bean>