4.1 사라진 SQLException
JdbcTemplate으로 바꾼 후 deleteAll() 메소드 : thorws SQLException 선언 사라짐
왜 SQLException이 사라졌을까?
초난감 예외처리
예외 블랙홀
: 예외를 처리하지 않고 넘어가는 경우
예외 무시하고 계속 진행
상황 : catch로 잡은 후 아무것도 하지 않고 넘어감
문제 : 예외가 발생했는데 무시하고 계속 진행 → 최종적 오작동시 발견 힘듬
try{ ... } catch(SQLException e){ }
예외 잡고 화면에 로그 띄우기
} catch(SQLException e){ System.out.println(e); }
상황 : 예외 발생시 화면에 출력해줌
문제 : 로그와 메세지 금방 묻힘, 예외가 처리되지 않음
콘솔, 로그에 예외 출력하는 것은 도움이 되지 않음. 처리를 해야됨
예외 처리시 지켜야 할 핵심 원칙
- 모든 예외 적절하게 복구
- 작업 중단시키고 운영자, 개발자에게 통보
무의미하고 무책임한 thorws
: 기계적으로 모든 메소드에 throw하는 방법
상황 : 모든 예외를 무조건 던져버리는 선언을 모든 메소드에 넣음
문제 : 정말 실행중 예외가 발생한건지 습관적으로 붙여놓은 건지 알 수 없
예외의 종류와 특징
예외 how 다룰까? 큰 이슈 : 체크예외 사용하고 다루는 방법
throw 통해 발생 시킬 수 있는 에러
1. Error
java.lang.Error의 서브클래스
- 에러 : 시스템에 비정상적 상황 발생 → 자바 VM에서 발생 시킴
- catch 블록으로는 대응할 수 없음
- 애플리케이션에서는 신경쓰지 않아도 됨
2. Exception과 체크 예외
체크예외
java.lang.Exception과 서브클래스
- 애플리케이션 코드의 작업중에 예외상황 발생
- Exception의 서브 클래스 , RuntimeException 클래스를 상속 X
- 예외 처리 코드 강제함
- 예외 상황에서 던져질 가능성 있는 것들 대부분 체크 예외
3. RuntimeException과 언체크/런타임 예외
언체크 예외=런타임예외
java.lang.RuntimeException 클래스 상속
- 개발자가 부주의해서 발생하는 경우 만드는 예외
- 명시적인 예외처리 강제하지 않음
- 프로그램 오류가 있을 때 발생
- NullPointException : 오브젝트 할당하지 않은 래퍼런스 변수 사용
- IllegalArgumentException : 허용되지 않는 값 사용해서 메소드 호출
예외처리 방법
일반적인 예외처리 방법 알아보고 나서 효과적 예외처리 전략 알아보기
1. 예외 복구
: 예외 상황 파악하고 문제 해결한 후 정상 상태로 돌려놓는 것
ex ) IOException 발생
문제 : 요청한 파일 읽을 수 없음
해결 : 사용자에게 상황 알려주고 다른 파일 이용하도록 안내
기본 작업 흐름 불가능 시 자연스럽게 다른 작업 흐름으로 유도
- 에러 메세지를 그냥 사용자에게 던지는것 X
- 애플리케이션에서는 정상적으로 설계된 흐름 따라야됨
ex) SQLException 발생
문제 : 원격 DB 서버 접속에 실패
해결 : 일정 시간 대기 후 다시 접속
정해진 횟수만큼 재시도 해서 실패했으며 예외 복구 포기
체크 예외 : 예외를 어떤식으로든 복구할 가능성 있는 경우에 사용
2. 예외처리 회피
: 예외처리를 자신이 담당하지 않고 자신을 호출한 곳으로 던져버리는 것
예외를 자신이 처리하지 않고 회피하는 것
throws 선언해서 알아서 예외 던져지게
public void add() throws SQLException{ //JDBC API }
catch 문으로 잡아 로그 남긴 후 다시 throws
public void add() throws SQLExceiption{ try{ //JDBC API } catch(SQLException e){ //로그 출력 throw e; } }
ex 콜백 오브젝트 (JdbcContext, JdbcTemplate )
- 작업하다 발생하는 SQLException 템플릿으로 던짐
- 콜백 오브젝트의 메소드 모두 throws SQLException 붙어있음
- 예외 회피, 템플릿 레벨에서 처리하도록
ex DAO가 던진다면
- DAO 사용하는 서비스 계층, 웹컨트롤러에서 해결 x
- 예외가 서버로 전달됨
정리
예외회피는 의도가 분명해야됨
- 콜백/템프릿 : 긴밀한 관계에 있는 오브젝트에게 예외처리 책임 지게
- 자신을 사용하는 쪽에서 예외를 다루는게 최선이라는 확신있어야
3. 예외 전환
: 예외를 적절한 예외로 전환하여 메소드 밖으로 던지는 것
→ 예외를 정상적인 사태로 만들 수 없어서
예외 전환의 목적
명확한 의미를 만들기 위해
내부에서 발생한 예외를 그대로 던지는 것 →적절한 의미 부여하지 못하는 경우
Ex. 사용자 등록시 같은 아이디 사용자가 있는 경우
상황 : DB에러 발생 → SQLException 예외 생김
문제 : DAO 메소드가 그대로 던지면 서비스 계층은 왜 예외 발생한지 모름
해결 : DAO에서 예외 정보 해석후 DuplicateUserIdException 같은 예외로 바꿔서 던져줌
예외 전환 방법
중첩예외로 만들기
전환하는 예외에 원래 발생한 예외 담기
getCause() 메소드 : 처음 발생한 예외무엇인지 확인 가능
새로운 예외 만들면서 생성자, initCause() 로 근본 원인이 되는 예외 넣기
//생성자로 근본 예외 넣기 catch(SQLException e){ ... throw DupilicateUserIdException(e); }
//initCause로 근본 예외 넣기 catch(SQLException e){ ... throw DuplicateUserIdException().initCause(e); }
예외 처리하기 쉽고 단순하게 만들기 위해
예외 포장하기
체크예외를 런타임 예외로 바꾸는 경우 사용
의미를 명확하게 만들기 위해
Ex . EJBException : EJB 컴포넌트에서 예외 발생
try { OrderHome orderHome = EJBHomeFactory.getInstance ().getOrderHome () ; Order order = orderHome. findByPrimaryKey (Integer id); } catch (NamingException ne) { throw new EJBException(ne); } catch (SQLException se) { throw new EJBException(se); } catch (RemoteException re) { throw new EJBException(re);}
문제 : 발생하는 체크예외 대부분 복구가능 x, 의미있는 예외 x
해결 : EJBException = 런타임 예외 로 포장해서 던지는 편이 나음
효과 : 트랜잭션 자동 롤백, 클라이언트에서 일일이 예외 잡아 다시 던지지 x
⇒ 잡아도 복구할 방법 x
Ex. 애플리케이션 로직상 예외 발생
- API가 던지는 예외 아님
- 애플리케이션 코드에서 의도적으로 던지는 예외
- 체크 예외 사용 → 대응, 복구 작업
예외 처리 전략
예외를 효과적으로 사용하고 깔끔히 코드 정리
0. 런타임 예외 보편화
체크 예외 : 일반적 예외
런타임 예외 : 시스템 장애, 프로그램상 오류
상황 : 자바 엔터프라이즈 서버 환경
문제 : 서버 계층 예외 발생 시 작업 중단하고 예외사항 복구할 방법 없음
해결 : application 차원에서 예외 상황 미리 파악, 차단하는게 좋음
⇒ 대응 불가능한 체크 예외라면 런타임예외로 전환하는게 낫다!
1. add() 메소드 예외처리
상황 : add() 메소드는 두가지 체크 예외 보냄 (DuplicatedUserIdException, SQLException)
문제 : 전자 - 복구가능, 후자 - 복구 불가 대부분, 애플리케이션 밖으로 던져질 것
해결 : 런타임 예외로 포장해 던져서 다른 메소드들이 신경쓰지 않게 해주자!
과정 :
DuplicatedUserIdException 만들기
add() 보다 더 앞단의 오브젝트에서 다룰 수 있음 → 런타임 예외로 만들기
add() 메소드에는 예외 던진다고 명시적 선언
중첩 예외 만들 수 있게 생성자 추가
public class DuplicateUserIdException extends RuntimeException { public DuplicateUserIdException (Throwable cause) { super(cause); } }
- add() 메소드 수정하기
- SQLException 런타임 예외로 전환해서 던지도록 하기
결론 : add() 메소드 이용하는 obj는 불필요한 throws 선언할 필요 x, 필요의 경우 아이디 중복 사항 처리 위한 예외 처리 이용 가능
2. 애플리케이션 예외
: 애플리케이션 자체 로직에 의해 의도적 예외 발생, 반드시 catch해서 조치 취하게 함
Ex. 요청한 금액을 은행 계좌에서 출금하는 기능 메소드
상황 : 현재 잔고 확인, 허용하는 범위 넘게 요청하면 작업 중단 시킴, 경고 날리기
설계 1 : 정상적 출금 처리와 잔고부족 발생 경우 리턴값 다르게
- 리턴값으로 결과 상태 나타냄
- 단점 : 개발자 사이의 소통 문제로 제대로 동작하지 않을 수도 있음 / 조건문 많은 지저분한 코드
설계 2 : 예외 상황에서만 예외 던지기
잔고 부족시 InsufficientBalancedException 던짐
코드 이해 편함 - try, catch에 따로 정리해둬서
설계 2의 예외 - 체크 에외로 만듬 : 예외로직 구현 강제해줌
SQLException의 행방
- SQLException 복구 가능 ? : 거의 no
- 개발자가 빨리 인식할 수 있도록 발생 예외 전달하는 것이 최선
- 예외 처리 전략 사용 위해 런타임 예외로 전달해야함
JdbcTemplate이 이전략 따름
- 콜백 안에 발생하는 모든 SQLException을 런타임 예외로 포장해 던져줌
- 꼭 필요한 경우에만 UserDao 에서 사용, 아님 무시
- ⇒ 그래서 UserDao에서 사라진 것
4.2 예외 전환
예외 전환 목적
- 런타임 예외로 포장하여 필요하지 않은 catch/throws 줄임
- 로우레벨 예외 더 의미있고 추상화된 예외로 바꿈
JDBC의 한계
상황 : DB별로 다른 API 제공해야 할 경우
문제 : DB 변경시 DAO 코드 변경 해야함, 서로 다른 API 사용법 익혀야 됨
DB를 자유롭게 바꾸어 사용할 수 있는 프로그램 작성의 걸림돌
비표준 SQL
: DB의 특별 기능이용하거나 최적화 된 SQL 만드는 등에 비표준 SQL 사용됨
문제 : 비표준 SQL → DAO코드로 들어감 → DAO는 DB에 종속적인 코드가 됨
다른 DB로 변경시 DAO에 담긴 SQL 코드 수정해야됨
해결 : Dao를 DB별로 만들어 사용 / SQL을 외부에서 독립시켜 바꿔쓰게 함
호환성 없는 SQLException의 DB 에러정보
: DB마다 SQL외에도 에러 종류, 원인 제각각
문제 : SQLException의 에러코드가 DB에 종속됨. 서로 호환 안됨
해결 : DB에 독립적인 예외로 전환되어야 함
DB에러 코드 매핑을 통한 전환
문제 : SQL 상태코드가 정확하지 않음
해결 : DB별 에러코드 참고해서 발생한 예외 무엇인지 해석하는 기능 만들기
DB 종류와 상관없이 동일한 상황에서 일관된 예외 받도록
DataAccessException
- SQLException 대체할 만한 런타임 예외 정의
- 다양한 예외 클래스 제공
문제 : DB별 에러코드가 제각각
해결 : 에러코드 정보를 매핑파일에 담아둠
JdbcTemplate
- DB 에러코드를 DataAccessException 계층 구조 클래스 중 하나로 매핑해줌
- 예외는 모두 서브클래스 타입
- 디비가 달라져도 같은 종류의 에러면 동일 예외 받을 수 있음
- JDBC에서 발생하는 DB관련 외에는 거의 신경쓰지 않아도 됨
DAO 인터페이스와 구현의 분리
DAO 를 분리해서 사용기술과 구현코드 전략 클라이언트에게 숨길 수 있음
문제 : Jdbc가 아닌 기술로 구현하면 사용 할 수 없음
SQLException 던지도록 선언한 인터페이스 메소드 사용 불가능
해결 :
throws Exceptions 선언 모든 예외 다 받게
너무 무책임
JDBc를 이용한 DAO에서 모든 SQLException을 런타임 예외로 포장
문제 → 모든 예외를 무시 할 순 없음 : 중복키 에러
데이터 액세스 추상화와 DataAccessException 계층구조
해결 : 엑세스 기술 사용할때 발생하는 예외들을 추상화해서 DataAccessException 계층구조 안에 정리해둠
에러코드 DB별로 매핑해서 해당하는 서브클래스 중 하나로 전환 후 던짐
발생가능한 대부분의 예외를 계층구조로 분류해둠
Ex) 엔티티/ 오브젝트 단위로 정보 업데이트 하는 경우 낙관적 락킹 발생 가능
같은 정보를 두명 이상 사용자가 순차적으로 업데이트 할때 먼저 업데이트 한것 덮지 않게 막아주는 기능
사용자에게 안내 메세지 보여줘야됨
다른 기술 엑세스들도 서브클래스를 이용해서 예외 전환 가능
템플릿 메소드나 DAO에서 활용가능한 예외도 있음
Ex .queryForObject()
- 한개의 로우만 돌려주는 쿼리에 사용
- 하나 이상 로우 가져올 경우 JDBC에선 에러 나지 않음
- 해결할 서브클래스 있음
기술에 독립적인 DAO 만들기
인터페이스 적용
UserDao 클래스를 인터페이스와 구현으로 분리
- UserDao 인터페이스 : DAO의 기능만 넣음
- setDataSource() 포함 x
- UserDaoJdbc : 구현만 남게
- implement UserDao 해줌
- 설정파일 빈클래스 이름을 변경해줌
테스트 보완
문제 : user 인스턴스 변수 선언도 jdbc 변경?
해결 : @Autowired - 스프링 컨텍스트 내에서 정의된 빈 중 인스턴스 변수에 주입 가능한 타입의 빈 찾아줌
- 구현 기술 상관없이 DAO 기능 체크 : UserDao 인터페이스만 받기
- 구현 내용 관심 갖고 테스트 : 테스트에서 @Autowired로 DI 받을 때 특정 타입 이용
⇒ UserDao 테스트는 DAO의 기능 검증이 목적, 전자가 적합
- 중복키 가진 정보 등록했을 때 테스트
- test를 클라이언트로 생각
- 어떤 예외 발생하는지 보기
- 아이디가 같은 사용자 두번 add() 등록 → 예외 발생할 것 기대
- 어떤 예외 발생했는지 다시 테스트
- 중복키 어떤 예외 발생했는지 test
- expected=DataAccessException.class 빼고 테스트 실행
- 테스트 실패 but 에러메세지 통해 예외 클래스 던져짐
- 서브클래스의 한종류임을 알 수 있게 됨 → 성공
DataAccessException 활용시 주의사항
문제 : DuplicatedKeyException이 아직 JDBC를 이용하는 경우에만 발생
DB에러 코드와 달리 예외들이 세분화 되어있지 않음
DataAccessException이 추상화된 공통 예외로 변환 o → 근본적 한계 있음
DataAccessException 잡아 처리하는 코드 말들 경우 미리 학습테스트 만들어서 전환되는 에외 종류 확인해 볼것
Ex. 학습테스트 만들어 SQLException → DataAccessException으로 변환
SQLException 직접 전환해보기
SQLErrorCodeSQLExceptionTranslator 사용
에러 코드 변환에 필요한 DB종류 알아내기 위해 현재 연결된 DataSource를 필요로 함
DataSource 변수 추가 → DataSource 타입의 빈 받기
public class UserDaoTest{ @Autowired UserDao dao; @Autowired Datasource dataSource; }
학습테스트
@Test public void sqlExceptionTranslate(){ dao.deleteAll(); try{ dao.add(user1); dao.add(user2); } catch (DuplicateKeyException ex){ SQLException sqlEx = (SQLException)ex.getRootCause(); SQLExceptionTranslator set = new SQLErrorCodeeSQLExceptionTranslaotr(this.dataSource); asserThat(set.translate(null,null,sqlEx), is(DuplicateKeyException.class); } }
'Group Study (2022-2023) > Spring 심화' 카테고리의 다른 글
[Spring 심화] 6주차 스터디 - 6장 AOP (1) (0) | 2022.11.21 |
---|---|
[Spring 심화] 5주차 스터디 - 5장 서비스 추상화 (0) | 2022.11.15 |
[Spring 심화] 3주차 스터디 - 3장 템플릿 (0) | 2022.11.01 |
[Spring 심화] 2주차 스터디 - 2장 테스트 (0) | 2022.10.10 |
[Spring 심화] 1주차 스터디 - 1장 오브젝트와 의존관계 (0) | 2022.10.03 |