티스토리 뷰
잘못된 예외처리
예외 블랙홀
- 예외를 잡고 아무 것도 하지 않는 경우
try{
//...
} catch(SQLException e){
}
- 예외를 잡아서 출력만 하는 경우
무의미하고 무책임한 throws
- 메소드 선언부에 기계적으로
throws Exception
을 붙이는 경우
예외 종류와 특징
Error
- 시스템에 뭔가 비정상적인 상황이 발생했을 경우에 사용
- e.g. OutOfMemoryError, ThreadDeath
- 어플리케이션 코드에서 잡아도 대응 방법이 없기 때문에 잡을 필요 없음
Checked Exception
- Exception 의 subclass 중 RuntimeException 을 상속하지 않은 클래스
- 이 예외가 발생할 수 있는 메소드를 사용할 경우 반드시 예외를 처리하는 코드를 함께 작성해야 함
UnChecked Exception
- RuntimeException 을 상속한 클래스
- Checked Exception과 달리 catch문으로 잡거나 throws로 선언하지 않아도 됨
- e.g. NullPointerException, IllegalArgumentException
예외처리 방법
예외 복구
- 예외상황을 파악하고 문제를 해결해서 정상 상태로 돌려놓는 것
- e.g. 예외로 인해 기본 작업 흐름이 불가능하면 다른 작업 흐름으로 자연스럽게 유도
- e.g. 네트워크 접속이 원활하지 않아 예외가 발생했다면, 일정 시간 대기했다가 다시 접속 시도
- checkedException 의 경우, 위와 같이 예외를 어떤 식으로든 복구할 가능성이 있는 경우에 사용
int maxRetry = MAX_RETRY;
while (maxRetry-- > 0) {
try {
//...
return;
} catch (SomeException e) {
// 로그 출력. 정해진 시간만큼 대기
}
finally {
// 리소스 반납
}
}
throw new RetryFailedException; //최대 재시도 횟수 넘기면 직접 예외 발생
예외처리 회피
- 예외처리를 자신이 담당하지 않고 자신을 호출한 쪽으로 던져버리는 것
public void add() throws SQLException {
//...
}
public void add() throws SQLException {
try {
//...
}
catch (SQLException e){
//로그 출력
throw e;
}
}
- e.g. 템플릿/콜백 패턴에서 콜백 오브젝트는 exception 을 던져서 템플릿에서 예외를 처리하도록 유도
- 예외를 회피하는 것은 예외를 복구하는 것처럼 의도가 분명해야 함. 즉, 다른 오브젝트에게 예외처리 책임을 지게 하거나, 자신을 호출한 쪽에서 예외를 다루는 게 최선의 방법이라는 확신이 있어야 함
예외 전환
- 적절한 예외로 전환해서 던짐
- case1
- 내부에서 발생한 예외를 그대로 던지는 것이 그 예외 상황에 대한 적절한 의미를 부여해주지 못하는 경우.
- 전환하는 예외에 원래 발생한 예외를 담아서 중첩 예외로 만드는 것이 좋음
public void add(User user) throws DuplicatedUserIdException, SQLException {
try {
// SQLException을 던지는 메소드 호출
} catch (SQLException e) {
if (e.getErrorCode() == MySqlErrorNumbers.ER_DUP_ENTRY) {
throw new DuplicatedUserIdException(e);
} else {
throw e;
}
}
}
- case2
- 예외를 처리하기 쉽고 단순하게 만들기 위해 포장(wrap)하는 경우
- e.g. checkedException 을 RuntimeException으로 바꾸는 경우
- 어차피 복구하지 못할 예외라면 어플리케이션 코드에서는 런타임 예외로 포장해서 던져버리고, 예외처리 서비스 등을 이용해 로그를 남기고, 관리자에게 메일 등으로 통보해주고, 사용자에게는 안내 메시지를 보여주는 것이 바람직
예외처리 전략
런타임 예외의 보편화
- 자바 엔터프라이즈 서버환경 : 수많은 사용자가 동시에 요청을 보내고 각 요청이 독립적인 작업으로 취급된다. 하나의 요청을 처리하는 중에 예외가 발생하면 해당 작업만 중단시키면 됨
- 프로그램의 오류나 외부 환경으로 인해 예외가 발생하는 경우라면 빨리 해당 요청의 작업을 취소하고 서버 관리자나 개발자에게 통보해주는 편이 낫다.
- 즉, 대응이 불가능한 checked exception이라면 빨리 런타임 예외로 전환해서 던지는 게 낫다.
- 자바 표준 스펙 혹은 오픈소스 프레임워크 API 에서도 예외를 uncheckedException으로 정의하는 것이 일반화되고 있음
UserDao add() 메소드 예외처리
public class DuplicateUserIdException extends RuntimeException {
//중첩 예외를 만들 수 있도록 생성자 추가
public DuplicateUserIdException(Throwable cause) {
super(cause);
}
}
public void add(User user) throws DuplicateUserIdException {
try {
// SQLException을 던지는 메소드 호출
} catch (SQLException e) {
if (e.getErrorCode() == MySqlErrorNumbers.ER_DUP_ENTRY) {
//예외 전환
throw new DuplicateUserIdException(e);
} else {
//예외 포장
throw new RuntimeException();
}
}
}
- DuplicateUserIdException 외의 SQLException 은 uncheckedException으로 변경되었음.
- 메소드 선언부에 SQLException 생략 가능
- add() 를 사용하는 쪽에서는 메소드 선언부에 Exception을 두지 않아도 되고, 필요시 DuplicateUserIdException을 catch해서 아이디 중복 예외 처리 가능
어플리케이션 예외
- 어플리케이션 자체의 로직에 의해 의도적으로 발생시키고 반드시 catch 해서 무엇인가 조치를 취하도록 요구하는 예외
- e.g. 사용자가 요청한 금액을 은행계좌에서 출금하는 기능 구현
try {
BigDecimal balance = account.withdraw(amount);
// ... 정상적인 처리
} catch (InsufficientBalanceException e) {
//인출 가능한 잔고금액 정보를 가져옴
BigDecimal availFunds = e.getAvailFunds();
// 잔고 부족 안내 메시지 출력
}
InsufficientBalanceException
: 의도적으로 checkedException으로 만듦- 예외상황에 대한 로직을 구현하도록 강제
JdbcTemplate 로 전환할 때 SQLException 은 어떻게 됐나?
- JdbcTemplate 은 템플릿과 콜백 안에서 발생하는 모든 SQLException을 런타임 예외인 DataAccessException 으로 포장해서 던져줌 -> DAO 메소드에서 SQLException이 사라진 이유..!
예외전환
- 목적 1 : 런타임 예외로 포장해서 필요하지 않은 catch / throws 를 줄임
- 목적 2 : low-level 의 예외를 의미 있고 추상화된 예외로 바꿈
JDBC 의 한계
- DB 마다 에러의 종류와 원인이 제각각 -> JDBC API 는 모두 SQLException 을 던지도록 설계
if (e.getErrorCode() == MySqlErrorNumbers.ER_DUP_ENTRY)
- MySQL 에 의존하는 예외처리 코드
- ErrorCode 에 의존해서는 DB 에 독립적인 유연한 코드를 작성할 수 없음
- 해결책
- 스프링에서는 DataAccessException 의 서브클래스로 세분화된 예외 클래스들을 정의.
- e.g. DuplicateKeyException, BadSqlGrammarException, DataAccessResourceFailureException
- 이러한 서브클래스들은 스프링 내부적으로 다양한 DB의 errorCode와 mapping 되어 있음.
public void add(User user) throws DuplicateKeyException {
//User를 Add 하는 코드
}
- DataAccessException 을 상속받은 DuplicateKeyException 을 이용해 add() 를 간소화. & MySql에 의존적이던 코드 삭제
- 만약 예외처리를 강제하고 싶다면, add() 내에서 DuplicateKeyException 을 catch 해서 커스텀하게 정의한 DuplicateUserIdException(checked Exception)을 던지면 된다.
DataAccessException 또 다른 역할 : DAO 인터페이스에서의 활용
DAO 따로 만드는 이유
- 데이터 엑세스 로직을 담은 코드를 성격이 다른 코드에서 분리하기 위함
- 분리된 DAO는 전략 패턴을 적용해 구현 방법을 변경해서 사용할 수 있게 만들기 위함
public interface UserDao {
public void add(User user);
}
- 만약 JDBC API를 사용하는 구현 클래스라면 SQLException을 던지기 때문에 위와 같은 선언은 불가능.
- 해결책
- SQLException 에러코드 -> DataAccessException 서브클래스로 매핑해서 예외를 전환 후 던짐.
또 다른 기능
- SQLException 에러코드 -> DataAccessException 서브클래스로 매핑해서 예외를 전환 후 던짐.
- 데이터 엑세스 기술에서 발생 가능한 예외를 계층구조화
- e.g. 데이터 엑세스 기술을 부정확하게 사용
- JDBC -> BadSqlGrammarException
- Hibernate -> HibernateQueryException
- 위 같은 예외를 InvalidDataAccessResourceUsageException 으로 통일
- 구현 클래스와 독립적으로 예외 처리 가능
기술에 독립적인 UserDao 만들기
public interface UserDao {
void add(User user);
User get(String id);
List<User> getAll();
void deleteAll();
int getCount();
}
//클래스명 변경, implements 키워드 붙임
public class UserDaoJdbc implements UserDao {
//...
}
//설정파일 내 변경
@Bean
public UserDao userDao() {
UserDao userDao = new UserDaoJdbc(dataSource());
return userDao;
}
'SPRING' 카테고리의 다른 글
토비의 스프링 6. AOP (1) (0) | 2022.05.27 |
---|---|
토비의 스프링 5. 서비스 추상화 (0) | 2022.05.27 |
토비의 스프링 3. 템플릿 (0) | 2022.05.27 |
토비의 스프링 2. 테스트 (0) | 2022.05.27 |
토비의 스프링 1. 오브젝트와 의존관계 (0) | 2022.05.27 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 코테
- 데코레이터패턴
- 코딩테스트
- BOJ
- 메서드레퍼런스
- 객체지향
- 프로그래머스
- 김영한
- 서비스추상화
- java
- ec2
- 백기선
- 템플릿콜백
- 프록시
- 스프링
- 예외처리
- 카카오
- SOLID
- c++
- gracefulshutdown
- 프록시패턴
- 자바스터디
- 토비
- AOP
- provider
- OOP
- 자바
- 토비의봄TV
- 디자인패턴
- 토비의스프링
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
글 보관함