데이터 접근시 예외 처리에대해서 스프링은 어떻게 해결했는지 알아보자.
그전에 예외에대해 간단하게 살펴본다.
예외는 크게 Exception 과 Error로 나뉘며 Error은 개발자가 해결할수 없는 오류이다. 우리가 눈여겨 봐야할건 Exception
Exception은 크게 두가지로 나뉜다.
= 컴파일러가 체크하는 체크예외와
= 컴파일러가 체크하지 않는 언체크 예외.
기본적으로 런타임 예외를 사용하도록 하자
체크 예외는 비즈니스 로직상 의도적으로 던지는 예외에서만 사용 -> 반드시 예외를 잡아서 처리해야하는 문제일 경우(중요 비즈니스 로직.. 계좌이체실패 or 결제 포인트부족 등)
중요한 비즈니스 로직은 개발자가 실수 할수 있으므로 체크 예외로 만들어 컴파일러가 체크하도록 해주자
그렇다면 왜 런타임 예외를 사용해야 할까??? 체크 예외는 컴파일러가 체크해준다는 장점도 있지만 크게 두가지 문제가 있다.
- 복구 불가능한 예외
- 대부분 예외는 복구불가능하다. 서비스단과 컨트룰러에서 처리해야하며 이런 문제들은 일관성있게 공통을 처리해야한다. 로그를 남기고 개발자가 빠르게 인지하는것이 중요하다.
- 의존 관계에 대한 문제
- 순수한 서비스 단에 SQLException(JDBC기술) 에 의존함으로 만약 JDBC 기술이 다른 기술로 바뀌면 해당 기술에 의존하는 코드들도 다 바꿔줘야한다.
체크예외를 언체크 예외로 바꿔주자 (ex: SQLException -> RuntimException)
런타임 예외이기 떄문에 컨트룰러,서비스단에서 예외를 처리할 필요가 없다.
그만큼 throw 반복코드를 사용하지 않아서 코드도 깔끔해지고 의존관계도 해결된다.
변환코드를 따로 작성해줘야함
언체크 예외 활용시 문서화를 잘해주자(컴파일러가 체크해주지 않아 빠뜨리기쉽다)
Try-Catch 문으로 잡을때 SQLException 발생시 RuntimeExcepition으로 바꿔 주었다.
여기서 눈여겨 봐야할점은 변환시 이전 예외를 함께 넣어 보내준다는 점이다.(RuntimeException (e)) 그래야 나중에 로그를 볼때 뭐때문에 예외가 터졌는지 알수 있기 떄문이다.
런타임 예외를 활용하면 중간에 기술이 변경되어도 의존관계를 맺지 않았기때문에 연쇄변경전파는 현저히 떨어지게 된다.
저번에 @Transactional 을 사용해 비즈니스 로직을 깔끔하게 정리했지만 Repository에서 서비스단으로 던지는 예외로 인한 예외누수 문제가 생겼다.
서비스 계층은 가급적으로 특정 구현 기술에 의존하지 않아야 한다는 원칙에 위배된다.
예외를 무시할수 있는 RuntimeException으로 바꿔주자
인터페이스를 도입하면 서비스단에서는 인터페이스에만 의존하면 되고 DI를 활용해 구현기술을 변경할수 있다.
기존 런타임에러가 아닌 체크예외를 사용하면서 인터페이스를 사용할경우 인터페이스, 인터페이스 구현체까지 throws를 해줘야해서 좋지 않은 방법이다. 인터페이스 또한 순수해야 하는데 특정기술에 오염되버리는 문제도 발생
=> 결론적으로 인터페이스 + 런타임에러 방법으로 예외처리를 무시해도 된다는점(서비스단의 순수성과) 인터페이스를 통한 구현체 변환의 자유로움을 챙길 생각이다.
기존코드 catch 부분에서 SQL예외가 발생하면 런타임 에러로 변경해주는 부분이다.
반드시 이전 에러를 같이 넣어서 보내줘야 한다. (그래야 에러 파악)
하지만 모든예외가 MyDbException으로 올라오기때문에 예외를 구분할수가 없다!
에러가 발생할때 데이터베이스에서 제공하는 errorCode라는것도 같이 담겨져 나온다.이 에러코드를 활용하면 데이터베이스에 무슨 문제가 발생했는지 알수 있다.
이 에러코드마다 의미하는 에러내용이 다르고 각각의 데이터베이스마다도 에러코드 번호가 다르다.
에러코드를 활용해 에러가 발생시 다른 상황을 조치하는(복구) 로직을 짜기위해서는 서비스단으로 예외를 던져야 하는데 그러면 순수성이 무너지게 된다. 앞서 배운 방법 런타임에러로 변환해서 던져주자.
id값이 중복 됐다는 예외이다. 기존에 만든 MyDbException을 상속받아서 의미있는 계층 형성을 한다.
우리가 직접만든 예외이기 때문에 특정기술에 의존하지 않는다.
서비스단 순수성을 위한 런타임 변환 + 에러코드 활용해서 예외복구
어떤오류가 일어나는지 알수 있으며 특정기술에 의존하지 않는다는 장점이 있지만 데이터베이스마다 에러코드가 다다르며 그 종류도 엄청나다. 우리가 그걸 하나하나 다 만들수는 없다.
그래서 나온게...
스프링에서는 이미 이런 문제에대해 다 정리가 돼있다.
각각의 예외는 특정 기술에 종속적이지 않게 설계되어 있다. 따라서 서비스 계층에서도 스프링이 제공하는 예외를 사용하면 된다. 예를 들어서 JDBC 기술을 사용하든, JPA 기술을 사용하든 스프링이 제공하는 예외를 사용하면 된다.
런타임 예외가 최상위
- DataAccessException은 크게 두가지로 나뉜다
- Transient = 일시적이라는뜻, 하위 예외는 동일한 SQL을 다시 시도했을때 성공가능성이 있다(ex: DB lock)
- NonTransient = 일시적이지 않다. 동일 SQL실행시 반복실패(ex: 문법오류)
쉽게 말하면 데이터베이스에서 나온 예외-> 스프링에서 정의한 예외로 자동 변환 기능 제공
읽을 수 있는설명,sql, 예외만 파라미터로 넘겨주면 자동으로 변환해준다.