체크예외와 언체크 예외
체크 예외
: 컴파일러가 예외를 잡았는지 체크할 수 있는 예외처리를 뜻한다. := 컴파일러가 체크하는 체크 예외
→ 예외를 잡아서 처리하지 않으면, 무조건 throws에 던지는 예외를 선언해야 한다.
언체크 예외
: 컴파일러가 예외를 잡았는지 안잡았는지 체크하지 않는 예외처리를 뜻한다.
→ 예외를 잡아서 처리하지 않아도 throws를 생략할 수 있다.
예외 기본적인 계층 구조
- Throwable: 최상위 예외 객체이다.
- Exception: 체크 예외
- 애플리케이션 로직에서 사용할 수 있는 실질적인 최상위 예외
- 컴파일러가 체크하는 체크 예외이다. (단, RuntimeException은 런타임 예외(=언체크 예외))
- Error: 언체크 예외
- 메모리 부족이나 심각한 시스템 오류처럼 애플리케이션에서 복구 불가능한 시스템 예외
- Exception: 체크 예외
예외 기본 규칙
예외는 폭탄 돌리기와 같다.
- 잡아서 처리하거나, 밖으로 던져야 한다.
- 예외를 던질 때 지정한 예외뿐만 아니라, 그 예외의 자식들도 함께 처리된다.ex 2) Exception을 throws로 던지면, 그 하위 예외들도 모두 던질 수 있다.
- ex 1) Exception을 catch로 잡으면, 그 하위 예외들(IOException, RuntimeException..)도 모두 잡을 수 있다.
만약, 예외를 처리하지 못하고 계속 던지면 어떻게 될까?? ⇒ 자바 main() 쓰레드의 경우, 예외 로그를 출력하면서 시스템이 종료된다. ⇒ 웹 애플리케이션의 경우, 사용자의 요청을 처리하기 때문에 하나의 예외 때문에 시스템이 종료되면 안된다. 때문에 WAS가 해당 예외를 받아서 처리하는데, 주로 사용자에게 개발자가 지정한 오류 페이지를 보여준다.
체크 예외의 문제점
체크 예외는 컴파일러가 예외 누락을 체크해주기 때문에 개발자가 실수로 예외를 놓치는 것을 막아준다.
- 심각한 문제들 대부분 애플리케이션 로직 내에서 처리할 방법이 없다.
- 이런 문제들은 일반 사용자에게 어떤 문제가 발생했는지 자세히 설명하기가 어렵다.
- 해결이 불가능한 공통 예외는 별도의 오류 로그를 남기고, 개발자가 빨리 인지할 수 있도록 메일, 알림(문자, 슬랙)등을 통해서 전달받아야 한다. ex) SQLException, ConnectionException
- 복구 불가능한 예외
대부분의 예외는 복구가 불가능하다. 일부 복구 가능한 예외도 있지만 아주 적다.
예) SQLException 발생 원인 → 애플리케이션 로직 상 복구 불가능
- SQL 문법
- Database Server가 중간에 다운
- Database 자체에 뭔가가 문제 발생
⇒ 이런 문제들은 일관성 있게 공통으로 처리해야 한다. 즉, 오류 로그를 남기고 개발자가 빠르게 이를 인지하는 것이 필요하다. Servelt Filter, Spring Interceptor, Spring ControllerAdvice를 사용하면, 이런 문제들은 깔끔하게 공통으로 해결 할 수 있다.
- 의존 관계에 대한 문제
- 체크 예외이기 때문에 컨트롤러나 서비스 입장에서 본인이 처리할 수 없더라도, throws를 통해 던지는 예외를 꼭 선언해야 한다.
- 이는 컨트롤러나 서비스에서 java.sql.SQLException(JDBC 기술)을 의존하기 때문에 문제가 된다.
- 향후, 레파지토리 JDBC 기술이 아닌, PersistenceExcetion(JPAException)으로 예외가 변경된다면, 모든 컨트롤러, 서비스 로직에서 코드를 변경해야 한다!
- 즉, 기술을 바꾸면 예외 코드도 다 뒤져서 다 바꿔야 하는 불필요한 의존 관계 문제가 발생한다.
예외 기본 원칙
아래 기본 원칙을 2가지를 기억하자.
- 기본적으로 언체크(런타임) 예외를 사용하자.
- 체크 예외는 비즈니스 로직 상 의도적으로 던지는 예외에만 사용하자. := 컴파일러를 통해 놓친 예외를 인지하는 것이다.
- 체크 예외 예) - 100%는 아니다.
- 계좌 이체 실패 예외
- 결제 시 포인트 부족 예외
- 로그인 ID, PW 불일치 예외
- 체크 예외를 할 때는 문서화를 잘 해놓도록 하자.
- 체크 예외 예) - 100%는 아니다.
런타임 예외 흐름
체크 예외 → 런타임 예외 로 변경 SQLException → RuntimeSQLException
ConnectionException → RuntimeConnectionException
- 런타임 예외이기 때문에, 컨트롤러 서비스는 해당 예외들을 처리할 수 없다면 별도 선언 없이 그냥 두면 된다.
- 이 때, 기존 예외를 포함해 주어야 한다. 이유는 외 출력 시 Stack Trace에서 기존 예외도 함께 확인하기 위해서 이다.
⇒ 런타임 예외를 사용하면, 중간에 기술이 변경되어도 해당 예외를 사용하지 않는 컨트롤러, 서비스에서는 코드를 변경할 필요가 없다.
구현 기술이 변경 된 경우 예외를 공통으로 처리하는 곳에서는 예외에 따른 다른 처리가 필요할 수 있다. 하지만 한 곳에서만 변경하면 되기 때문에 변경 영향 범위는 최소화 된다.
성장한 점
체크 예외와 언체크 예외의 차이점
- 컴파일러가 예외를 잡았는지 체크할 수 있는가? (체크 o: 체크 예외 / 체크 x: 언체크 예외)
- 예외를 잡아서 처리할 때, throws 생략 유무
예외의 기본적인 계층 구조 학습
- Object - Throwable - Exception - RuntimeException(런타임 예외:=언체크 예외)
- - Error(언체크 예외)
예외 기본 규칙 - catch, throws로 하위 계층까지의 폭탄 돌리기
- 예외는 잡아서 처리(catch)하거나 밖으로 던져야(throws) 한다.
- 예외를 던질 때 지정한 예외 뿐만 아니라, 그 예외의 자식들도 함께 처리된다.
예외 처리에 대한 문제점
- 복구 불가능한 예외: 대부분의 예외는 복구가 불가능하다.
- 의존 관계의 문제: 예를 들어, 비즈니스 로직 상 JDBC 기술의 의존관계 형성 → 모든 코드 수정해야 한다.
예외 처리 결론 - 기본 원칙을 2가지를 기억하자. + 1 원칙 추가
- 기본적으로 언체크(런타임) 예외를 사용하자.
- 런타임 예외를 사용할 때는 반드시 기존 예외를 포함해주도록 하자. 이유는 외 출력 시 Stack Trace에서 기존 예외를 파악해 문제의 원인 분석과 해결을 위해서다.
- 체크 예외는 비즈니스 로직 상 의도적으로 던지는 예외에만 사용하자. for 컴파일러를 통해 놓친 예외를 인지
++ 2023.07.06 내용 추가 ( Transaction)
Spring에서의 Transaction - 예외처리 시
- RuntimeException(언체크예외): 시스템 상 문제 발생 - "복구 불가"라고 생각하자.
- RuntimeException 만날 시, Transaction이 롤백됨 -> Database에 Transaction 내 수정 데이터가 반영되지 않는다.
- ex ) 주문시 내부에 복구 불가능한 예외가 발생 ⇒ <런타임예외> ⇒ 전체 데이터를 롤백
- Exception(체크예외): "비즈니스 상 의미가 있는 예외로 생각해 commit 한다." 고 생각하자.
- Exception 만날 시, Transaction이 커밋됨 ( 비즈니스의 특정 목적이 있는 경우에 사용하자!)
- ex) 주문시 결제 잔고가 부족 ⇒ <체크예외> ⇒ 주문 데이터를 저장하고 ,결제 상태를 대기로 처리한다.
'Java' 카테고리의 다른 글
JVM (0) | 2023.07.10 |
---|---|
System.out.println을 쓰면 안되는 이유 (0) | 2023.07.06 |
Java (0) | 2023.06.23 |
Java - equals, hashCode() (0) | 2023.06.20 |