본문 바로가기

스프링

[java] 예외처리_비관적인예외 try catch throws throw

은행계좌에서 출금 > 계좌 잔액보다 많으면 작업을 중단시켜야한다.

* 이 메소드를 설계하는 방법 2가지

1.     잔고가 부족한 경우 0 또는 -1 같은 특별한 값을 리턴

많은 개발자들과 개발하다보면 의사소통 문제로 제대로 동작하지 않을 위험이 있다.

결과값을 확인하는 조건문이 자주 등장하여 코드가 지저분해진다.

2.     정상적인 흐름은 그대로 두고, 잔고 부족과 같은 예외 상황에서는 비즈니스적인 의미를 띤 예외를 던지도록 만드는 것.(비관적인 예외)

>  이때 체크예외로 만들어 모든 개발자들에게 로직 구현을 강제해 주는 것이다.

이런식으로 코딩하면서 예외를 던질 수 있는데,

 

예외를 던지면 catch에 들어가 해당 catch의 로직이 돌아가게 코딩가능

 

스프링에서 unchecked 예외인 DuplicateKeyException을 던질 이유가 없지만, 중복키 상황에 대응이 필요한 경우 사용하는 쪽에서 참고하라는 의미로 메소드 선언에 넣어주면 편함. 하지만 예외처리를 강제하지 않음으로 체크예외로 던지게하고 싶다면 아래 스샷 참조

 

* Spring DataAccessException

JdbcTemplate update(), queryForInt(), query() 메소드에 throws 선언이 되어있음(런타임 예외)

(badSqlGrammarException : SQL문법에러) 등과 같은 예외상황을 수십가지로 분류하고 이를 추상화해 정의한 다양한 예외클래스와 매핑정보테이블(리스트4-16)과 같은 내용의 에러 코드 정보가 매핑파일을 이용한다.

JDK 1.6에 포함된 JDBC4.0부터는 기존의 JDBC 단일 예외 클래스였던 SQLException이 좀 더 세분화 해서, 문법 오류시에는 SQLSyntaxException, 제약조건 위반인 SQLIntegrityConstraintViolationException과 같은 식으로 세분화된 예외를 사용하도록 만들었지만, SQLException의 서브클래스이므로 여전히 체크예외라는 점과 세분화 기준이 SQL상태정보를 이용한 다는데서 여전히 문제점이 있다. 예외를 만들어주는 JDBC드라이버가 충분히 보급된다면 모르겠지만, 아직은 스프링 에러코드 매핑을 통한 DataAccessException 방식을 사용하는 것이 이상적이다.

>  JDBC 외의 데이터엑세스 표준기술(JDO, JPA, TopLink, 하이버네이트, Ibatis) 등 기술의 종류와 상관없이 일관된 예외를 발생하도록 해줌

 

만약 JDBC API를 사용하는 Dao를 구현하는 인터페이스는 위처럼 throws를 넣어야한다. 이렇게 정의한 인터페이스는 JDBC가 아닌 다른 데이터 엑세스 기술을 통일할 수 없다.

빨간 네모처럼 선언하는 것도 방법이지만, 무책임한 선언이다.

>  JDO, Hibernate, JPA 등의 기술은 런타임 예외를 사용하므로, throws를 사용하지 않아도 된다. 따라서 JDBC를 이용한 DAO의 모든 SQLException을 런타임 예외로 포장해주면

 

모든 기술에 독립적인 인터페이스 선언이 가능해진다.

과연 충분할까???

> 모든 예외를 다 무시해야 하는 것은 아니지만, 중복키 에러처럼 의미있게 처리할 수 있는 예외도 있으므로, 시스템 레벨에서 각 데이터엑세스기술에 맞는 예외가 일관되어야 한다. 하지만 그렇지 못하므로 결국 클라이언트 입장에서는 DAO의 사용 기술에 따라서 예외 처리 방법이 달라져야 한다.(클라이언트가 DAO의 기술에 의존적이 될 수 밖에 없다.)

 

스프링의 DataAccessException JDBC에서 발생하는 BadSqlGrammarException, 하이버네이트의 HibernateQueryException 등 데이터 엑세스 기술들에 관계없이 프로그램을 잘못작성하면 동일하게 위와 같은 에러가 발생함

낙관적인 락킹 : 같은 정보를 두 명 이상의 사용자가 동시에 조회하고 순차적으로 업데이트할 때 뒤늦게 업데이트한 것이 먼저 업데이트한 것을 덮어쓰지 않도록 막아주는데 쓸 수 있는 편리한 기능.

일관된 예외가 발생하므로 어떤 데이터 엑세스 기술을 사용했더라도 상관없는 락킹 처리하는 코드를 만들어낼 수 있느다는 이야기. 이건 후에 다시 배울 듯. 이정도 까지만 이해하자.

>  결국 스프링의 JbdcTemplate와 같이 데이터 엑세스 지원 기술을 이용해서 DAO를 만들면 사용 기술에 독립적인 일관성 있는 예외를 던질 수 있다.

 

1.     Com.springsource.junit : junit

2.     Commons-logging : Spring-context가 사용

3.     Mysql-connector : Mysql JDBC

4.     Spring-aop : 스프링 기능 자체의 aop

5.     Spring-bean : 스프링 빈을 활용하는 경우 필요. 스프링의 XML 설정파일과 자바 애노테이션을 파싱 하는데 필요한 클래스 포함

6.     Spring-context : 스프링 코어를 확장한 많은 클래스가 들어 있는데 모든 클래스는 EJB, JNDI(Java Naming Directory Interface), JMX용 클래스와 연동하는데 applicationcontext기능을 사용해야 하며 스프링 리모팅 클래스, 동적 스크립팅 언어(제이루비, 그루비등)와 연동하는 클래스, 빈 유효성검증(JSR-303) API, 스케줄링을 하는 클래스도 포함되어 있다.

7.     Spring-core : 모든 스프링 모듈에서 필요한 모듈. 다른 스프링 모듈에서 사용하는 공통 클래스가 포함됨.

8.     Spring-dao : EmpltyResultDataAccessException 등 사용을 위한 jar

9.     Spring-expression : 스프링 표현언어(SpEL) 지원 클래스 포함.

10.   Spring-jdbc : 스프링이 지원하는 jdbc.

11.   Spring-test.jar

-       @RunWith : Junit 프레임워크의 테스트 실행방법을 확장시 사용.

-       SpringJUnit4ClassRunner : 어플리케이션컨텍스트를 만들고 관리하는 확장 클래스

-       @ContextConfiguration(경로) : 자동으로 만들어줄 어플리케이션 컨텍스트 설정파일

12    Spring-tx : DuplicateKeyException.class 파일 존재 및 스프링 트랜잭션

 

Jdbc와 같은 데이터엑세스기술(JDO, JPA, Hibernate)와 같은 java의 주요 기술들을 사용시 능동적으로 변할 수 있도록 인터페이스 사용.

인터페이스 네이밍

인터페이스 이름앞에 I라는 접두어를 붙여도됨(IUserDao)

인터페이스 이름은 단순하게(UserDao), 구현 클래스는 (UserDaoJdbc, UserDaoHibernate)라고 붙일 수 있다.

*  UserDaoJdbc를 보면 알겠지만, setDataSource() 구현방법에 따라 변경될 수 도 있는 메서드이고, UserDao를 사용하는 클라이언트가 알고 있을 필요가 없다. 따라서 인터페이스에 포함시키지 않았다.

 

14. id=”userDao” : 보통 빈의 이름은 클래스 이름이 아닌 인터페이스 이름을 따르는 경우가 일반적임. 그래야 나중에 구현 클래스를 바꿔도 혼란이 없다.

Add() 메소드를 사용하는 쪽에서 중복 키 상황에 대한 대응이 필요한 경우 참고할 수 있도록 throws를 넣어주면 편하다. 안 넣어줘도 되지만 이 메서드를 사용하는 쪽에서 catch 를 이용해야하는 경우 아래 UserTest.deleteAll() 참조

 

중복된 키 등록시 예외 테스트. 이때 기본키 중복으로 인해 스프링의 DataAccessException 예외 중의 하나가 던져져야 한다.

이때 어떤 예외인지 확인해 보려면 일부러 예외를 발생시키면 된다. Expected 부분을 빼고 돌려보면 다음과 같이 기본키 에러인 DuplicateKeyException이 발생했음을 알 수 있다.

JPA, Hibernate, JDO 등의 기술은 아쉽게도 기본키 에러시 DuplicateKeyException가 발생하지 않는다. JDBC DB에러코드를 바로 해석하지만, 나머지 기술은 그 기술이 재정의한 예외를 가져와 변환하는데, 아직 세분화 되어있지 않다. 그래서 조금더 포괄적인 추상화 되어있는 Exception으로 변경해줄 수 밖에 없다. 따라서 실제로 전환되는 예외의 종류를 확인해둘 필요가 있다.

> 만약 DuplicateUserIdException 을 기술의 종류와 관계없이 얻고 싶다면, DuplicateUserIdException을 직접 정의해두고 add() 메소드에서 좀더 상세한 예외전환을 해주어야 한다.

 

아래 instanceof를 사용하기 위해 편의상 *

DataSource 타입의 빈을 받아놓음 아래 참조

 

스프링은 SQLExceptionDataAccessException으로 전환하는 다양한 방법을 제공한다. 가장 보편적인 방법은 DB에러코드를 이용하는 것이다.

115. getRootCause() : 중첩되어 있는 SQLException을 가져올 수 있다.

116. SQLException을 코드에서 직접 에러코드로 전환할 때 SQLExceptionTranslator 인터페이스 중 SQLErrorCodeSQLExceptionTranslator 를 사용한다.

116. SQLErrorCodeSQLExceptionTranslator는 에러 코드 변환에 필요한 DB의 종류를 알아내기 위해 현재 연결된 DataSource를 필요로 한다. 그래서 35라인 추가

117. translate() : SQLExceptionDataAccessException 타입 예외로 만들어 준다.

> null : 에러메시지를 만들 때 사용하는 정보이므로 null을 넣어도 됨.

 

이 테스트를 한 이유

DB에 상관없이 항상 DuplicateKeyException 예외로 전환되는지 확인할 때.

>  JDBC 외의 기술을 사용할 때도 SQLException을 가져와서 직접 스프링의 DataAccessException 계층의 예외로 전환할 수 있다.