try {
...
} catch(SQLException e) {
}
모든 예외는 적절하게 복구되든지 혹은 작업을 중단시키고 운영자 또는 개발자에게 분명하게 통보되어야 한다.
굳이 예외를 잡아서 취할 조치가 없다면, 메소드 밖으로 throw하고 자신을 호출한 코드에 예외처리 책임을 전가하라.
public void method1() throws Exception {
method2();
...
}
public void method2() throws Exception {
method3();
...
}
public void method3() throws Exception {
...
}
적절한 처리를 통해 복구될 수 있는 예외상황도 제대로 다룰 수 있는 기회를 박탈당한다.
java.lang.Error 클래스의 서브클래스java.lang.Exception의 클래스와 그 서브클래스Exception 클래스의 서브클래스이면서 RuntimeException 클래스를 상속하지 않은 것catch문으로 잡든지, throws를 정의하여 메소드 밖으로 던져야 한다.RuntimeException을 상속한 클래스예외상황을 파악하고 문제를 해결해서 정상 상태로 돌려놓는 것
체크 예외들은 예외를 어떤 식으로든 복구할 가능성이 있는 경우에 사용
int maxretry = MAX_RETRY;
while(maxretry -- > 0) {
try {
...
return;
} catch(SomeException e) {
// 로그 출력, 정해진 시간만큼 대기
} finally {
// 리소스 반납, 정리 작업
}
}
throw new RetryFailedException();
예외처리를 자신이 담당하지 않고 자신을 호출한 쪽으로 던져버리는 것
public void add() throws SQLException {
// JDBC API
}
public void add() throws SQLException {
try {
// JDBC API
} catch(SQLException e) {
// 로그 출력
throw e;
}
}
콜백/탬플릿처럼 긴밀한 관계에 있는 다른 오브젝트에게 예외처리 책임을 분명히 지게 하거나, 자신을 사용하는 쪽에서 예외를 다루는 게 최선의 방법이라는 분명한 확신이 있어야 함
발생한 예외를 그대로 넘기는 게 아니라 적절한 예외로 전환해서 던진다.
public void add(User user) throws DuplicateUserIdException, SQLException {
try {
// logic
} catch(SQLException e) {
if (e.getErrorCode() == MysqlErrorNumbers.EP_DUP_ENTRY)
throw DuplicateUserIdException();
else
throw e;
}
}catch(SQLException e) {
...
throw DuplicateUserIdException().initCause(e);try {
OrderHome orderHome = EJBHomeFactory.getInstance().getOrderHome();
Order order = orderHome.findByPrimaryKey(Integer id);
} catch (NamingException ne) {
throw new EJBException(ne);
} catch (SQLException ne) {
throw new EJBException(ne);
} catch (NamingException ne) {
throw new EJBException(ne); 자바 환경이 서버로 이동하면서 체크 예외의 활용도와 가치는 떨어지는 중
대응이 불가능한 체크 예외라면 빨리 런타임 예외로 전환해서 던지는 것이 좋음
public class DuplicateUserIdException extends RuntimeException {
public DuplicateUserIdException(Throwable cause) {
super(cause);
}
}
SQLException을 직접 메소드 밖으로 던지지 않고, 런타임 예외로 전환하여 throw
→ throws에 포함시킬 필요가 없음
→ DuplicatedUserIdException은 명시적으로 throws에 선언
public void add(User user) throws DuplicateUserIdException {
try {
// logic
} catch(SQLException e) {
if (e.getErrorCode() == MysqlErrorNumbers.EP_DUP_ENTRY)
throw new DuplicateUserIdException(e);
else
throw new RuntimeException(e);
}
}
애플리케이션 예외: 시스템 또는 외부의 예외상황이 원인이 아니라 애플리케이션 자체의 로직에 의해 의도적으로 발생시키고, 반드시 catch해서 무엇인가 조치를 취하도록 요구하는 예외
예외상황에 대해 체크 예외로 만들어놓아 예외상황에 대한 로직 구현을 강제하도록
try {
BigDecimal balance = account.withdraw(amount);
...
} catch(InsufficientBalanceException e) {
BigDecimal availFunds = e.getAvailFunds();
...
// 잔고 출력
}
SQLException은 복구가 가능한가?
→ 99%의 SQLException은 코드 레벨에서는 복구 불가 (원인: SQL 문법 오류, 제약조건 위반, DB서버 문제 등)
스프링 JdbcTemplate 템플릿과 콜백 안에서 발생하는 모든 SQLExcpetion을 런타임 예외인 DataAccesssException으로 포장해서 throw하여 애플리케이션 레벨에서는 신경 쓰지 않도록
→ JdbcTempate를 사용하는 UserDao 메소드에선 꼭 필요한 경우에만 런타임 예외인 dataAccessException을 사용, 그 외에는 무시
스프링 API 메소드에 정의되어 있는 대부분의 예외는 런타임 예외
예외 전환의 목적
1. 런타임 예외로 포장하여 굳이 필요하지 않은 catch/throws를 줄여주는 것
2. 로우레벨의 예외를 좀 더 의미 있고 추상화된 예외로 바꿔서 던지기
JDBC는 자바를 이용해 DB에 접근하는 방법을 추상화된 API 형태로 정의해놓고, 각 DB업체가 JDBC 표준을 따라 만들어진 드라이버를 제공
DB를 자유롭게 바꾸어 사용할 수 있는 DB 프로그램을 작성하는 데는 두가지 걸림돌이 있다.
대부분의 DB는 표준을 따르지 않는 비표준 문법과 기능도 제공
해결책
JDBC는 데이터 처리 중에 발생하는 다양한 예외를 SQLException 하나에 모두 담아버린다.
→ SQLException 안에 담긴 에러 코드와 SQL 상태 정보를 참조
But, DB별로 에러 코드는 모두 다르다.
→ getSQLState() 메소드로 예외상황에 대한 표준화된 상태 정보 확인, 하지만 신뢰할만 하지 않음
DB별 에러 코드를 참고해서 발생한 예외의 원인이 무엇인지 해석
스프링은 DB별 에러 코드를 분류해서 스프링이 정의한 예외 클래스와 매핑해놓은 에러 코드 매핑정보 테이블을 만들어두고 활용
<bean id="DB2" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="databaseProductName">
<value>DB2*</value>
</property>
<property name="badSqlGrammarCodes">
<value>-007,-029,-097,-104,-109,-115,-128,-199,-204,-206,-301,-408,-441,-491</value>
</property>
<property name="duplicateKeyCodes">
<value>-803</value>
</property>
<property name="dataIntegrityViolationCodes">
<value>-407,-530,-531,-532,-543,-544,-545,-603,-667</value>
</property>
<property name="dataAccessResourceFailureCodes">
<value>-904,-971</value>
</property>
<property name="transientDataAccessResourceCodes">
<value>-1035,-1218,-30080,-30081</value>
</property>
<property name="deadlockLoserCodes">
<value>-911,-913</value>
</property>
</bean>
JdbcTemplate에서는 DataAccessException의 서브클래스인 DuplicateKeyException 등을 활용해 계층구조의 예외로 포장
public void add() throws DuplicateKeyException {
// logic..
}
체크 예외인 DuplicateUserIdException으로 바꾸고 싶을 경우
public void add() throws DuplicateUserIdException {
try {
// logic..
} catch(DuplicateKeyException e) {
// logic
throw new DuplicateUserIdException(e);
}
}
DataAccessException은 의미가 같은 예외라면 데이터 액세스 기술의 종류와 상관없이 일관된 예외가 발생하도록
DAO 구현 기술마다 던지는 예외가 다른 문제점
→ DAO에서 모든 SQLException을 런타임 예외로 포장
But, 데이터 엑세스 기술이 달라지면 다른 종류의 예외 throw
스프링은 자바의 다양한 데이터 액세스 기술을 사용할 때 발생하는 예외들을 추상화하여 DataAccessException 계층구조 안에 정리
낙관적인 락킹: 같은 정보를 두 명 이상의 사용자가 동시에 조회하고 순차적으로 업데이트를 할 때, 뒤늦게 업데이트한 것이 먼저 업데이트한 것을 덮어쓰지 않도록 막아주는 기능
다음과 같이 어떤 데이터 액세스 기술을 사용했는지에 상관없이 낙관적인 락킹을 처리

DataAccessException 예외 추상화를 적용하면 데이터 액세스 기술과 구현 방법에 독립적인 이상적인 DAO를 만들 수 있다.
public interface UserDao {
void add(User user);
User get(String id);
List<User> getAll();
void deleteAll();
int getCount();
}
setDataSource() 메소드는 UserDao 구현 방법에 따라 변경될 수 있고, 클라이언트가 알 필요가 없으므로 포함시키지 않음
public class UserDaoJdbc implements UserDao {
<bean id="userDao" class="...UserDaoJdbc">
<property name="dataSource" ref="dataSource" />
</bean>
빈의 이름은 클래스의 구현 인터페이스 이름을 따르는 경우가 일반적

DuplicateKeyException은 JDBC를 활용하는 경우에만 발생
SQLErrorCodeSQLExceptionTranslator를 사용하여 SQLException을 코드에서 직접 전환
public class UserDaoTest {
@Autowired UserDao dao;
@Autowired DataSource dataSource;
@Test
public void sqlExceptionTranslate(0 {
dao.deleteAll();
try {
dao.add(user1);
dao.add(user1);
} catch(DuplicateKeyException ex) {
SQLException sqlEx = (SQLException) ex.getRootCause();
SQLExceptionTranslator set = new SQLErrorCodeSQLExceptionTranslator(this.dataSource);
assertThat(set.translate(null, null, sqlEx), is(DuplicateKeyException.class));
}
}