들어가기 앞서: 도움 받은 강의와 책들
- 시간이 있으시다면 아래 강의, 책을 직접 보시는 것이 도움 될 것입니다만, 시간적 여유가 없을 분들을 위해 제가 봤을때 도움이 되는 내용들을 정리해서 공유합니다.
- 특히 아래 내용들을 기반으로 정리하였습니다:
- 인프런의 스프링DB 1편 강의, 김영한
- 가장 도움을 많이 받았습니다. JDBC 기본 원리에 대한 정리도 잘 되있으니 관련해서 보충이 필요하다면 추천드립니다.
- 이펙티브 자바 10장
- 단 CheckedException(검사예외)와 RuntimeException/UncheckedException(비검사예외) 에 대한 부분은 요즘 추세와 맞지 않을 수 있다고 느끼니, 유의하시길(해리 강사님이나, 위의 인프런 강의에서나 런타임예외 사용 추세를 언급합니다)
간단 요약
RuntimeException
을 기본으로 사용
CheckedException
은 무조건 예외 처리를 해야 하는 중요한 상황 && 그 예외를 애플리케이션 코드 차원에서 해결할 수 있을 때 사용
CheckedException
은 예외 처리를 강제하기에, 예외 번역을 통해RuntimeException
으로 변환해 준다.
- 예외 번역 시에는 반드시
new
로 생성한 예외에 기존 예외를 담아 준다: Stack Trace 찍기 위함이다.
- DB 접근 기술의 경우 스프링이 에러코드를 읽어 적절한 예외로 변환해주는 예외 번역 기능을 제공한다.
- 예외 번역을 통해, 적절한 세부 예외로 변환하고,
- 처리할 수 있는 예외의 경우 처리해준다.
자바의 예외
예외 개념
- 예외는
catch
하거나,throws
해서 해당 메서드를 호출한 메서드로 전달해야 한다.
- 예외의 종류, 계층은 다음과 같다:
예외 계층도
- 계층도에 대한 설명
Throwable
- 최상위 예외, 예외도 객체이기 때문에
Object
의 자손 Error
- 애플리케이션 차원에서는 복구 불가능한 시스템 예외
- 개발자가 해결할 영역이 아님. 건들지 말자
Error
는catch
할 것이 못 되기에 그것의 상위 예외인Throwable
도 사용하지 말자!Throwable
을 잡으면 그 하위에 있는Error
도 자연히 잡히기 때문에Exception
- 위와 같은 이유로 인해, 실질적으로 개발자가 애플리케이션 로직에서 사용하는 최상위 예외
CheckedException(검사 예외)와 UncheckedException(비검사 예외)의 구별!
- CheckedException
Exception
중RuntimeException
을 제외한 부분- 컴파일러가 해당 예외를 처리(
catch
하거나, 메서드 시그니처에throws
선언)하였는지 검사(Check)하는 종류의 예외임 → 예외 처리가 강제된다! CheckedException
을 새로 만들고 싶다면extends Exception
!
- UnCheckedException
RuntimeException
과 그 하위 예외 +Error
(위에서 언급했듯이,Error
는 사용하는 게 아니기에)RuntimeException
으로 퉁쳐서 말할 때가 많음)- 컴파일러는 해당 종류의 예외를 처리했는지 검사하지 않음!(Unchecked) → 처리 강제 X
UnCheckedException
을 새로 만들고 싶다면extends RuntimeException
!
- 비교
- 예외 처리가 강제되느냐의 문제입니다.
- 예외 처리가 강제되는
CheckedException
의 경우 런타임에 생길수도 있는 예외를 컴파일 시점에 미리 잡을 수 있다는 장점이 있지만, - 예외처리가 강제된다는 점에서, 처리할 수 없는 경우에는 문제가 됩니다.
- 예외 처리가 강제되지 않는
UncheckedException
은 반대입니다.
어떨 때 사용할까?
- 기본적으로
RuntimeException
쓰자!
CheckedException
의 경우, 이 예외를 처리하지 못 할 경우 정말 치명적인 문제가 생기며 && 그 예외를 처리해줄 수 있을 때만 사용하자.
왜 CheckedException
을 가급적 안 쓰는 편이 나을까?
- 실제로 발생하는
CheckedException
은 처리할 수 없는 것들이 많다. - 네트워크 연결 문제나, DB 연결 상태가 깨져서 발생하는 예외를 개발자가 애플리케이션 코드에서 잡아줄 수는 없다!
- 예외 처리 강제로 인해 해당 예외에 대한 의존성이 생긴다.
- 예시)
Repository
Repository
인터페이스를 규정, 그 구현체로JdbcRepository
만들었을 경우JdbcRepository
는SQLException
을 던지게 된다.- 구현체
JdbcRepository
의 메서드 시그니처에throws
문이 붙기 때문에, 해당 메서드를 규정하고 있는 인터페이스의 메서드 시그니처에도throws
문이 붙어야 하는데, 이 경우 JDBC와 관련된SQLException
에 대한 의존성을Repository
인터페이스 자체가 갖게 된다. - 만약 JPA로 변경한다면, 위의
SQLException
에 대한 코드는 불필요한데도 인터페이스가 해당 예외를 달고 있다는 문제점이 생긴다.
- 종합해보면
- 어차피 처리도 못할 예외를
throws
하게 되면 인터페이스도 오염시키고, 해당 계층의 윗 계층까지 오염시키게 된다.- 일단적인 웹 어플리케이션의 경우라면,
Repository
를 호출한Service
도,Service
를 사용하는Controller
도 ,... - 그냥
catch
해서UncheckedException
,RuntimeException
으로 변환해주는 것이 낫다! → 예외 번역
애플리케이션 코드에서 처리할 수 없는 예외는 어떻게 다룰까?
- 예외가
catch
되지 않고 끝까지 간다면 - 자바 콘솔 애플리케이션의 경우
- 예외가
main()
쓰레드까지 올라가고, 예외 로그와 함께 시스템 종료 - 웹 어플리케이션의 경우
- WAS가 해당 예외를 받아서 처리: 보통은 오류 페이지를 보여줌
- 따라서 웹 어플리케이션의 경우
- 서블릿 오류 페이지
- 스프링을 사용할 경우
ControllerAdvice
사용하여 예외를 공통 처리할 수 있음 - 저 같은 경우 스프링 MVC 강의에서 학습했었는데, 3주차 과제, 웹 애플리케이션 개발 중이시라면 해당 방식을 학습, 적용하셔서 예외 처리하시면 깔끔하리라 생각합니다.
- 위 경우 보통 500에러가 될 것임
- 어차피 개발자가 애플리케이션 코드에서 처리하지 못 할 예외라면, 적절한 오류 로깅 + 빠르게 장애 상황을 인지할 수 있는 알림(슬랙, 지라 등등)이 중요하다!
- “함께 자라기” 라는 책에서는(88쪽, 실수는 예방하는 것이 아니라 관리하는 것이다) 실수를 예방하는 것 보다도 관리하는 것의 중요성에 대해서 이야기합니다. 문제가 안 생기게 노력하는 것도 중요하지만, 어차피 실수(여기서는 예외와 같은 문제상황이 되겠죠)는 어차피 터질 수 밖에 없기 때문에 상황이 발생했을 때 관리할 수 있는 역량을 강조하고 있습니다. 실제로 조직이나, 학습 양 쪽 모두 실수 예방보다 관리의 관점에서 접근할 때 성과가 더 잘 나온다는 얘기도 있네요.
- 실제 기업에서 장애대응을 어떻게 운영하는가에 대한 글입니다: 우아한 장애대응
- 저희가 스프링부트 1주차에서 학습했던
Profile
을 이용하여 상황에 따라 콘솔, 슬랙 알람 등으로 나누어 로깅하는 전략에 대한 글입니다: 링크
예외와는 무관하게, 위와 관련하여 읽어볼만한 이야기는:
예외 번역
- 어차피 개발자가 애플리케이션 코드에서 처리할 수 없는
CheckedException
은 거추장스럽기만 합니다.RuntimeException
을 상속받는 새 예외를 정의하거나, 적절한RuntimeException
(의 하위 예외)로 변환해줍시다.
- 예외를
catch
하여 다른 예외를 던져 줍니다. - 위의 코드의 경우
CheckedException
인SQLException
을 잡아서,RuntimeException
인SomeException
으로 전환, 다시 예외를 던지고 있습니다. - 이제 예외처리가 강제되지 않습니다.
- 중요!!! 새 예외를 생성할 때는 인자로 기존 예외를 담아줘야 합니다! 아니면 예외의 Stack trace가 날라가 버려서 원인 파악에 상당한 문제가 생깁니다!
UnCheckedException
은 예외처리가 강제되지 않기에 실수로 놓칠 수도 있습니다. 때문에- JavaDocs에 표기해줍니다.
- 예외 표기가 필요하다 싶을 때는 메서드 시그니처에 명시해줍니다. (명시가 강제되지 않는 것일 뿐, 표기는 가능)
Stack Trace
- 위에서 언급했듯이, 예외 번역을 통해 새 예외를 생성할 때는 인자로 반드시 기존 예외를 담아줍니다.
- 예외 발생시 로그에는 stack trace를 다음과 같이 담습니다.
-
{}
없이 추가적으로e
를 전달하면 Stack Trace 출력됩니다. (위의 경우{}
하나, 전달받는 인자 하나 + e라서 출력)
- 예외 발생시
e.stackTrace()
로 Stack Trace 찍지 말고 로그로 남깁시다.
처리 가능한 예외
- 데이터 접근 계층에서 발생하는 오류라고 해서 모두 처리 불가능한 것은 아님
- 예를 들어, 특정 객체를 저장하려고 할 때
- 객체의 ID값을 애플리케이션에서 할당한다면 (즉, DB 측에서 ID를 할당하는 상황이 아니라면)
- PK값을 실수로 이전에 저장된 것과 똑같은 값으로 저장하려고 할 때 예외가 발생할 것임
- 이 경우, PK인 ID값을 변경해주고, 저장을 다시 시도한다면, 해당 예외로 인해 발생한 문제는 해결될 것임
- 어떻게 DB 계층에서 발생한 예외의 종류를 알 수 있을까? → 에러 코드
e.getErrorCode()
를 통해 에러 코드를 얻을 수 있음- 해당 에러코드일 경우 상황에 맞는 예외(Ex.
DuplicatePrimaryKeyException
)를 던지고, 위 계층에서 해결을 시도하면 됨 - 하지만 이 에러코드의 경우 각 DB에 따라 달라짐! → 스프링 예외 번역기를 통해 해결 가능
- H2DB의 DuplicateKeyCodes 에러 코드는 23001, 23505
- DB2의 DuplicateKeyCodes 에러 코드는 803
스프링의 예외 처리 기술
- 스프링 데이터 접근 예외 계층
- 스프링은 데이터 접근 예외를 새로 규정하지 않아도 되게 위와 같이 사전 정의해 두었음
- 분류
DataAccessException
: 스프링 제공 예외의 최상위 예외TransientException
: 동일 SQL을 다시 시도했을 때 성공할 가능성이 있는일시적
예외- 예시: 쿼리 타임아웃, 락
NonTransientException
: 일시적이지 않음, 같은 SQL을 아무리 반복해서 시도해도 실패- 예시: SQL 문법 오류, DB 제약조건 위배
- 스프링 예외 번역기
- 스프링은 DB에러코드를 읽어들여, 상황에 맞는 예외로 알아서 변환해 줌
translate()
메서드- 파라미터1: 읽을 수 있는 설명
- 파라미터2: 실행한 SQL
- 파라미터3: 발생한
Exception
org.springframework.jdbc.support.sql-error-codes.xml
- 각 DB별 예외 코드, 거기에 해당하는 스프링 예외가 정리되어 있음
- 대략 아래와 같은 식:
- 스프링이 알아서 DB 별 코드를 읽어, 예외를 번역해주기에 DB를 변경한다고 해도 예외 처리 코드를 바꿀 필요 없음!