6. 예외

1. 예외를 다루는 방법

예외

  • 예외는 정상적인 프로그램 흐름을 방해하는 사건

  • 예외적인 상황에서만 사용 (내가 코드를 컨트롤하기 위한 수단으로 사용해서는 안된다)

  • 많은 경우 예외는 프로그램 오류, 버그 때문에 발생

예외가 발생하면?

  • 예외 상황을 복구해서 정상적인 흐름으로 전환할 수 있는가?

    • 첫번째 방법 : 재시도 (외부 DB, API 사용할 경우)

    • 두번째 방법 : 대안을 사용 (만약을 대비한 대안(세컨 서버, 캐시 등..) 을 준비해 놓을 수 있다)

  • 버그인가?

    • 첫번째 고려 : 예외가 발생한 코드의 버그인가?

    • 두번째 고려 : 클라이언트의 버그인가?

  • 제어할 수 없는 예외 상황인가?

예외를 잘못 다루는 경우

예외를 무시하는 코드

  • 단순히 예외를 출력하는 것 만으로는 예외를 처리할 수 없다.

  • 예외를 무시한다고 볼 수 있다. (e.printStackTrace())

무의미하고 무책임한 throws

  • 무의미하게 checked exception 을 throws 만 하고,

  • 실질적으로 처리하지 않는 경우가 있다.

예외의 종류

  • Error

    • 시스템에 비정상적인 상황이 발생

    • 이런 Error 는 catch 한다고해서 할 수 있는 것들이 없다..

    • OutOfMemoryError

    • ThreadDeath

  • Exception(checked)

    • catchthrows 를 강요

    • 코드 내에서 처리할 수 없는 예외를 밖으로 던지는 무의미한 코드가 발생할 가능성이 높다.

    • 초기 라이브러리의 잘못된 예외 설계/사용

    • 복구할 수 없다면 RuntimeException 이나 적절한 추상화 레벨의 예외로 전환해서 던질 것

  • RuntimeException(unchecked)

    • 런타임 예외라도 유의미한 예외라면, throws 를 통해서 명시해주는 것이 좋다. (해당 메서드에서는 해당 예외처리가 필요하다는 것을 명시)

예외의 추상화해 전환

  • 사용 기술에 따라 같은 문제에 대해 다른 종류의 예외 발생

  • 적절한 예외 추상화와 예외 번역이 필요하다.

  • 스프링이 이 부분에 대해서 많은 노력을 해왔다.

2. JPA Repository

주문(Order) 을 저장하는 로직을 추가한다고 생각해보자.

주문을 DB 에 저장하기 위한 구성

  • 스프링 컨테이너에서 싱글톤 빈(인스턴스) 으로 관리

    • DataSource : DB 와 연결하기 위한 객체

    • EntityManagerFactroy : EntityManager 를 생성해주는 객체

    • ObjectRepository : DB 와 관련된 로직으로 처리하는 객체

  • 요청마다 새로운 인스턴스 생성

    • Order : 주문과 관련된 객체

    • EntityManager : DB 에서 관리하기 위한 객체를 DB 와 연결해주는 중간 객체

Order 를 H2 DB 에 저장하는 간단한 로직을 만들어보자.

3. Order 리포지토리와 예외

위 코드에서는 예외 처리에 대한 로직이 누락되어 있다.

예외 발생 시 롤백 후 특정 예외를 던지는 로직으로 변경하자.

  • 클라이언트 입장에서는 리포지토리에서 발생한 예외를 잡아서 복구 작업을 할 수 있을 것이다.

여기서 문제점이 있다.

  • DB 벤더마다 발생하는 예외처리 코드가 다르다면 어떻게 처리할 것인가?

  • DB 를 처리하는 기술 자체가 변경되어 예외처리 코드가 다르다며 어떻게 처리할 것인가?

  • 결국 구체화된 예외코드에 의존하기 때문에, 유연성과 확장성이 떨어진다..

4. 스프링 데이터 엑세스 예외

JDBC SQLException

  • JDBC 를 기반으로 하는 모든 기술에서 발생하는 예외

  • JDBC, MyBatis, JPA, ..

  • DB 의 에러코드에 의존하거나, 데이터 기술에 의존적인 예외처리 코드

DataAccessExcpetion

  • DB 의 에러코드와 데이터 엑세스 기술에 독립적인 예외 구조

  • 적절한 예외 번역 (exception translation) 도구를 제공

다음과 같이 코드를 바꾸고 실행해보면 스프링의 추상화된 예외(org.springframework.dao.DataIntegrityViolationException)가 던져지는 것을 볼 수 있다.

  • 어떤식으로 코드가 변경되는지는 이번 글에서는 중요하지 않다.

  • 하지만 스프링이 다른 환경에서 발생할 수 있는 예외들을 추상화해서 던지는 것은 매우 의미있는 현상이다. (이를 통해 클라이언트 측에서 범용성 있는 코드를 작성할 수 있게 되었다)

  • 체계적인 예외 구조를 만들고, 적절한 예외 처리 방법을 사용하고 있는지 살펴보자. (스프링이 원하는 방향!)

Last updated