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