Transcript Lecture-04
Chapter 04. 예외 Sep. 2014 Youn-Hee Han http://link.koreatech.ac.kr 4.1 사라진 SQLException 2 4.1.0 SQLException 예외 처리 삭제 3장에서 구성한 JdbcContext와 스프링의 JdbcTemplate의 예외처리 부분 비교 – JdbcTemplate을 사용하는 경우 SQLException 예외를 던지는 코드 가 생략됨 3 4.1.1 초난감 예외처리 개발자들의 코드에 발견되는 초난감 예외처리 종류 – 예외 블랙홀 • 가장 최악의 예외 처리 방법 결국 어떤 기능이 비정상적으로 동작하거나, 메모리나 리소스가 소진되거 나, 예상치 못한 문제를 일으킬 것 4 4.1.1 초난감 예외처리 개발자들의 코드에 발견되는 초난감 예외처리 종류 – 단순 콘솔 출력 • 화면에 출력된 메시지를 보고 바로바로 조취를 취하면 그나마 괜찮음 • 운영서버에 올라가면 콘솔을 모니터링하기 힘듦 • 단순 콘솔 출력은 예외가 적절하게 처리된 것이 아니다. 5 4.1.1 초난감 예외처리 예외 처리에 대한 기본 원칙 – 예외는 처리되거나 아니면 작업을 중단하고 운영자 또는 개발자에 게 분명하게 통보되어야 한다. 앞의 코드 보다는 차라리 다음 코드가 좋다 – 실전에서 이렇게 만들면 안되다. – 단지 예외를 무시하거나 잡아먹어 버리는 코드를 만들지 말라는 차원에서 그나마 나은 코드 – 굳이 예외를 잡아서 뭔가 조치를 취할 방법이 없다면 잡지 말고 차 라리 자신을 호출한 코드에 예외처리 책임을 전가하라. 6 4.1.1 초난감 예외처리 무의미하고 무책임한 throws – 기계적으로 throws Exception을 붙인 코드 • 중요한 예외 상황에 대한 파악을 제대로 할 수 없고, 그러한 예외 상황 발생에 대해 무조건 던져버린다. • 적절한 처리를 통해 복구가 어렵지 않게 가능한 예외상황도 제대로 처 7 리할 수 있는 기회를 박탈당한다. 4.1.2 예외의 종류와 특징 java.lang.Error – java.lang.Error 클래스의 서브클래스에 해당하는 에러들 • java.lang.OutOfMemoryError • ThreadDeath – 시스템에 뭔가 비정상적인 상황이 발생한 경우에 발생 – 주로 자바 virtual machine에서 발생시키는 것 – 개발자 코드에서 잡을 필요도 없고 신경 쓰지 않아도 된다. • 잡아도 대처할 수 있는 바가 전혀 없음 8 4.1.2 예외의 종류와 특징 java.lang.Exception – 개발자들이 만든 애플리케이션 코드의 작업 중에 발생한 예외 – 두가지 종류 • 언체크 예외 (Unchecked Exception)/런타임 예외 • 체크 예외 (Checked Exception) 9 4.1.2 예외의 종류와 특징 언체크 예외 (Unchecked Exception) – java.lang.RuntimeException 클래스를 상속한 예외들 – 런타임 예외라고도 불림 – 대표적인 언체크 예외 • NullPointerException • IllegalArgumentException • ArrayIndexOutOfBoundException – 언체크/런타임 예외가 발생할 수 있는 메소드 호출시에는 catch 문으로 잡거나 throws로 선언하거나 또는 하지 않아도 된다. – 미리 주의 깊게 코딩을 한다면 모두 피할 수 있는 예외 10 4.1.2 예외의 종류와 특징 체크 예외 (Checked Exception) – 일반적으로 말하는 예외 – 체크 예외가 발생함이 선언된 메소드를 사용할 경우 반드시 예외를 처리하는 코드가 있어야 함 • 자신의 메소드 안에서 해당 메소드 호출에 try/catch 블록 적용, 또는 • throws 를 통해 자신의 메소드 밖으로 던짐 최근의 자바 개발자 커뮤니티 상황 – 체크 예외의 강제성 때문에 난잡한 코드가 남발됨을 근거로 체크 예외의 불필요성을 주장하는 사람들이 늘어남 11 4.1.3 예외처리 방법 예외 복구 – 예외상황을 파악하고 문제를 해결하여 정상 상태로 돌리는 방법 – 1) 기본 작업 흐름이 불가능하면 다른 작업 흐름으로 유도해줌 • 예. 파일을 읽으려고 하는데 해당 파일이 없을 때 IOException 발생 (체크 예외) 복구 방법 » 사용자에게 이를 알리고 다른 파일을 읽도록 유도 – 2) 재시도를 통한 예외 복구 • 예. 원격 DB 서버 접속시 연결 설정이 잘 안되면 재시도를 정해진 횟수만큼 재시도 12 4.1.3 예외처리 방법 예외 처리 회피 – 예외 처리를 자신이 담당하지 않고 자신을 호출한 쪽으로 던짐 – 무책임한 회피는 곤란 – 자신을 호출한쪽에서 예외를 처리하는 것이 좋다는 판단이 있을 때에만 회피하고 throw 함 • 예. JdbcTemplate에서 정의하는 콜백 메소드는 모두 throws SQLException이 붙어 있음. 즉, 콜백 자체에서 처리하는 것이 아니라 이를 호출하는 JdbcTemplate에 서 처리하도록 유도하고 있음 • 즉, 자신과 긴밀한 연관관계가 있는 객체일 때에만 예외 처리를 넘김 13 4.1.3 예외처리 방법 예외 전환(Exception Translation)후 회피 – 발생한 예외를 그대로 넘기는 게 아니라, 일단 잡아서 적절한 다른 예외로 전환하여 던짐 – 예외 전환 목적 1 - 그대로 던지는 것 보다 보다 그 의미를 분명하 게 해줄 수 있는 예외로 변환하기 위해 • 좀 더 의미가 분명한 구체적 명칭의 예외 클래스를 작성하여 활용 14 4.1.3 예외처리 방법 예외 전환(Exception Translation)후 던짐(회피) – 예외 전환 목적 1 - 그대로 던지는 것 보다 보다 그 의미를 분명하 게 해줄 수 있는 예외로 변환하기 위해 • 중첩 예외 (Nested exception) 그 의미를 분명하기 하기 위하여 새로운 예외를 만들 때 원래 발 생했던 예외를 새로운 예외 안에 넣어주는 방법 15 4.1.3 예외처리 방법 예외 전환(Exception Translation)후 던짐(회피) – 예외 전환 목적 2 - 예외 처리를 쉽고 단순하게 만들기 위해 포장 (Wrap)하는 것 • 주로 예외처리를 강제하는 체크 예외를 언체크 예외인 런타임 예외로 포장하는 방법 • 예. EJB 컴포넌트에서 발생하는 대부분의 체크 예외는 비즈니스 로직 상 복구 가능한 예외가 아닌 경우가 많음 이런 경우 런타임 예외인 EJBException으로 포장하여 던지는 것이 낫다. 16 4.1.3 예외처리 방법 예외 전환(Exception Translation)후 던짐(회피) – 예외 전환 목적 2 - 예외 처리를 쉽고 단순하게 만들기 위해 포장 (Wrap)하는 것 • 몇가지 런타임 예외는 코딩상에서 특별하게 잡아 처리하지 않아도 스프링 자체 라이브러리 상에서 직접 잡아서 처리를 해줌 예. 트랜잭션 롤백 요즘의 경향 – 어차피 복구가 불가능한 예외라면 애플리케이션 코드에서는… • 1) 자세한 로그를 남김 • 2) 관리자에게 이메일 통보 • 3) 사용자에게는 친절한 안내 메시지 • 4) 마지막으로, 런타임 예외로 포장해서 던짐 17 4.1.4 예외처리 전략 런타임 예외의 보편화 – 여러 체크 예외에 대한 try/catch 블록의 강제적 사용 • 자바 초기때 만들어진 정책 • 하지만 최근에는 많은 개발자들로 부터 비판을 받음 – 특히 자바가 서버 환경에서 사용될 때… • 외부 환경에 인한 여러 예외 발생에 대해 프로그램 코드상으로 처리할 수 없는 상황이 많음 • 이런 경우 예외 전환을 사용하여 런타임 예외로 전환하여 던지는 것 추천 – 즉, 최근에는 새로운 예외 클래스를 정의할 때 항상 복구가 가능한 예외만 체크 예외로 만든다. • 항상 복구 할 수 없다면 런타임 예외로 만든다. – 런타임 예외를 사용한 예외 처리 낙관적 접근법 18 4.1.4 예외처리 전략 add() 메소드(리스트 4-9)의 예외처리 분석 및 변경 – DuplicatedUserIdException • 자신 또는 호출한 쪽에서 충분히 복구 가능한 예외 • 자신이 처리해도 되지만 여기서는 런타임 예외로 전환 및 던짐 만약 아이디를 호출한 쪽에서 정하여 넣었다면 호출한 쪽으로 던져서 아 이디가 중복되었음을 알리는 것이 좋음 호출한 쪽에서 신경을 쓰지 않아도 되는 상황일 수도 있으니 런타임 예외 전환이 좋음 • add() 메소드 자체에서 본 예외 던짐을 선언하여 이용하는 쪽에서 예외 발생 상황을 정확하게 파악하게 함 – SQLException • 잡아보았자 복구 불가능한 예외 • 런타임 예외로 포장하여 그냥 던짐 (어느 정도 회피의 측면) • add() 메소드 자체에 본 예외 던짐 선언 생략해도 됨 필요하면 호출한 쪽에서 try/catch 블록을 넣어 잡아서 적절한 처리 가능 19 4.1.4 예외처리 전략 add() 메소드(리스트 4-9)의 예외처리 분석 및 변경 – DuplicatedUserIdException을 Runtime 예외로 선언 – 적절한 예외 처리 전략이 적용된 add() 20 4.1.4 예외처리 전략 add() 메소드(리스트 4-9)의 예외처리 분석 및 변경 – 새로운 add() 메소드를 호출하는 측에서의 장점 • SQLException에 대해서는 전혀 신경쓰지 않아도 됨 • 필요한 경우에는 DuplicatedUserIdException을 잡아서 적절한 처리 가 능. • 필요없으면 역시 신경쓰지 않아도 됨 – 런타임 예외 전환에 대한 주의점 • 컴파일러가 예외처리를 강제하지 않으므로 예외상황에 너무 둔감해질 수 있음 주의 • API 문서 등을 잘 작성하여 본 메소드에서 발생할 수 있는 예외의 종 류, 원인, 활용 방법 등을 설명해 둘 필요가 있음 – 라이브러리로 제공되는 객체의 메소드를 활용할 때 • 이 때에도, API 문서를 활용하여 해당 메소드를 사용할 때 발생할 수 21 있는 여러 예외에 대해 검토 필요 4.1.4 예외처리 전략 애플리케이션 예외 – 외부 환경이 아니라 애플리케이션 자체의 로직에 의해 의도적으로 발생시키고, 반드시 catch하여 무엇인가 조치를 취하도록 강제적으 로 요구하는 예외 – 예: 예금 인출 (withdraw)시 잔고 부족 예외 • 반드시 체크 예외로 InsufficientBalanceException을 발생 시킴 22 4.1.5 SQLException은 어떻게 되었나? SQLException – 99% 런타임시에 코드 레벨에서 복구 방법이 없다. – 프로그램의 오류 또는 개발자의 부주의 때문에 발생된 에러 • SQL 문법 오류 • DB 제약조건 위반 (foreign key, unique key등…) • DB 서버 다운 • 네트워크 불안정 • DB 커넥션 풀에서 커넥션을 가져올 수 없음 – DAO를 호출한 밖에서 이를 복구할 방법은 더욱 없다. – 필요도 없는 기계적인 try/catch 블록이나 throws 선언이 등장하지 않도록 방치하지 말고 런타임 예외로 전환 필요 23 4.1.5 SQLException은 어떻게 되었나? 스프링의 JdbcTemplate 의 예외 처리 전략 – 템플릿과 콜백에서 발생하는 모든 SQLException을 런타임 예외인 DataAccessException으로 전환하여 던짐 – 템플릿과 콜백을 사용하는 측에서는 필요한 경우 DataAccessException을 잡아서 적절한 처리 – 그 외의 경우 무시해도 됨 24 4.2 예외 전환 25 4.2.0 스프링의 예외 전환 JdbcTemplate의 DataAccessException 예외 – 런타임 예외 – SQLException을 포장함 – 또한, 상세한 예외정보를 의미 있고 일관성 있는 예외로 전환하여 추상화 하려는 의도 26 4.2.1 JDBC의 한계 표준화된 JDBC API의 한계 – 여러 서로 다른 DB를 자유롭게 변경하여 사용할 수 있을 정도로 유 연한 코드를 보장하지 못함 첫번째 문제점 – 비표준 SQL – 대부분의 DB는 표준을 따르지 않는 비표준 문법과 기능을 제공 – 비표준 SQL이 DAO 코드에 들어가면 해당 DAO는 그 DB에 종속됨 – 해결책 • DAO를 DB별로 각각 구현함 • SQL을 DAO 외부로 독립 (xml 등…)시켜서 쉽게 변경하여 사용할 수 있게 해줌 • 7장 참고 27 4.2.1 JDBC의 한계 두번째 문제점 – 호환성 없는 SQLException의 에러 정보 – DB마다 에러의 종류와 원인이 제각각 – JDBC는 데이터 처리 중 발생하는 모든 예외를 SQLException으로 전달 • 대신 getErrorCode()로 발생한 에러 코드를 넘겨줌 • 예. 중복 키 에러 처리 (MySQL 전용 코드) • 하지만, DB별로 이 에러 코드가 서로 다름 위 코드는 오라클이나 SQLServer에서 사용할 수 없음 • 추가로 getSQLState() 메소드로 상태 코드를 던져주긴 하지만 이 마저 도 DB 마다 제각각임 – 결국 호환성 없는 에러 코드와 상태코드를 지닌 SQLException만으 로 DB에 독립적인 유연한 코드 작성은 불가능 28 4.2.2 DB 에러 코드 매핑을 통한 전환 스프링에서 제공하는 DataAccessException의 서브 클래스 – BadSqlGrammarException • SQL 문법 오류 – DataAccessResourceFailureException • DB 커넥션을 가져오지 못했을 때 – DataIntegrityViolationException • 데이터의 제약조건을 위배했거나 일관성을 지키지 않는 작업 수행 – DuplicateKeyException • 중복 키 문제 – 이 외에도 수십가지가 있음 • 참고 http://docs.spring.io/spring/docs/current/javadocapi/org/springframework/dao/DataAccessException.html 29 4.2.2 DB 에러 코드 매핑을 통한 전환 스프링에서 DB 마다 에러 코드가 다른 점에 대한 대처 – DB별로 에러 코드를 분류하여 스프링이 정의한 예외 클래스와 매 핑해 놓은 테이블 (XML 설정)을 만들어 두고 활용 – 예. 오라클용 에러 코드 및 매핑 정보 30 4.2.2 DB 에러 코드 매핑을 통한 전환 스프링에서 DB 마다 에러 코드가 다른 점에 대한 대처 – 즉, DB가 달라져도 위와 같은 매핑정보를 참고하여 같은 종류의 에 러라면 동일한 예외를 받을 수 있음 – 기존 add() 메소드 변경 • 예외가 발생하면 JdbcTemplate에서 DuplicateKeyException안에 기존 SQLException을 넣어줌 • add()을 호출하는 쪽에서 중복 키 상황에 대한 대응이 필요하면 조치 를 취할 수 있도록 메소드 선언에 DuplicateKeyException 기입 추천 • DB가 변경되어도 동일 조건하에 발생되는 에러는 동일한 예외를 받을 수 있음 31 4.2.2 DB 에러 코드 매핑을 통한 전환 런타임 예외를 체크 예외로 전환 – DuplicateKeyException과 같은 중요한 예외는 반드시 add()를 호출 한 측에서 처리하도록 강제하고 싶을 때 – 체크 예외인 DuplicateUserIdException 클래스 등을 만들어 다음과 같이 예외 전환할 수도 있음 32 4.2.3 DAO 인터페이스와 DataAccessException 계층구조 DAO 인터페이스와 구현의 분리 – DAO를 따로 만드는 이유 • 데이터 엑세스 로직을 담은 코드를 성격이 다른 코드에서 분리 • 전략 패턴을 적용하여 구현 방법을 변경해서 사용하기 위해 • DAO를 사용하는 클라이언트에서는 DAO가 내부에서 어떤 데이터 엑세스 기술을 사용하는지 신경쓰지 않아도 된다. DataAccessException의 중요 특징 – 데이터 엑세스 기술의 종류와 상관없이 일관된 예외가 발생하도록 만들어줌 • JDBC, JDO/JPA, iBatis – 즉, 데이터 엑세스 기술에 독립적인 추상화된 예외 제공 33 4.2.3 DAO 인터페이스와 DataAccessException 계층구조 DAO 인터페이스와 구현의 분리 – DAO에서 사용하는 데이터 엑세스 기술의 API가 예외를 던지는 것 을 고려하여 DAO 인터페이스 선언 필요 • 만약 데이터 엑세스에 종속적으로 예외 선언을 하게 되면… //JDBC JDBC보다 늦게 등장한 JPA, Hibernate, JDO는 위 예외들을 모두 런타임예 외로 선언함 위와 같은 선언 필요 없음 • 아래와 같은 선언은 매우 무책임한 선언 • JDBC를 위한 SQLException을 런타임 예외로 전환한다면 다음 처럼 선 언해도 됨 하지만, 중복 키 예외와 같이 호출한 쪽으로 예외 상황을 알려주어야 할 필요도 있음. 34 4.2.3 DAO 인터페이스와 DataAccessException 계층구조 DAO 인터페이스와 구현의 분리 – 중복 키 에러가 발생했을 때의 예외 종류 • JDBC: SQLException • JPA: PersistenceException • Hibernate: HivernateException – 따라서, DAO를 사용하는 클라리언트 측에서는 DAO의 사용 기술 에 따라 예외 처리 방법이 달라져야 한다. • 문제 심각 – 스프링의 해결책 • 다양한 엑세스 기술을 사용할 때 발생하는 예외들을 추상화하여 DataAccessException 계층구조 안에 정리하여 새롭게 예외들을 정의 하였음 35 4.2.3 DAO 인터페이스와 DataAccessException 계층구조 데이터 엑세스 예외 추상화와 DataAccessException 계층 구조 – o.s.dao.InvalidDataAccessResourceUsageException • 데이터 엑세스 기술을 부정확하게 사용하였을 때, SQL 문이 잘못되었 을 때, 타입을 잘 못 사용하면서 데이터를 입력하려고 할 때… – o.s.dao.OptimisticLockingFailureException • 낙관적 락킹과 관련된 예외 36 4.2.3 DAO 인터페이스와 DataAccessException 계층구조 데이터 엑세스 예외 추상화와 DataAccessException 계층 구조 – o.s.dao.IncorrectResultSizeDataAccessException • queryForObject()에서 하나 이상의 로우가 리턴될 때 – o.s.dao.EmptyResultDataAccessException • 기대한 결과가 하나도 리턴이 안될 때 37 4.2.4 기술에 독립적인 UserDao 만들기 인터페이스 적용 – 인터페이스: UserDao • 인터페이스에 setDataSource() 메소드 추가 금지 UserDao를 사용하는 클라이언트에서 사용할 필요가 없는 메소드 – 클래스: UserDaoJdbc, UserDaoJpa, UserDaoHibernate • 클래스 선언 • XML 설정 일반적으로 빈의 이름 (id)은 인터페이스 이름을 따라 구성한다. 38 4.2.4 기술에 독립적인 UserDao 만들기 테스트 보완 – 테스트 코드 내의 UserDao는 그대로 사용 • @Autowired는 스프링의 컨텍스트 내에 정의된 빈중에서 인스턴스 변수에 주입이 가능한 타입의 빈을 찾아 넣어준다. • UserDao 동작에 관심이 있으면서 일종의 UserDao의 클라이언트로 테 스트가 동작하기 때문에 구체적인 구현 클래스인 UserDaoJdbc로 변경 할 필요 없음 39 4.2.4 기술에 독립적인 UserDao 만들기 테스트 보완 – 중복된 키를 가진 정보 등록 테스트 • 테스트 수행 성공 확인 – (expected=DataAccessException.class)를 제외하고 실행하였을 경 우에 콘솔 창에서 확인 가능한 메시지 40