Skip to content

Latest commit

 

History

History
317 lines (207 loc) · 13.7 KB

TIL220415.md

File metadata and controls

317 lines (207 loc) · 13.7 KB

Daily to do list

Java


Spring


CS


알고리즘


오늘의 회고

220415 Spring DB

——————————————

자바 예외 처리

  • 예외 계층

자바 기본 예외에 대한 충분한 이해가 필요하다.

모든 자바 객체는 최상이 객체가 Object이다. 인셉션도 객체이기 떄문에 최상위는 Object. Throwable에서 Exception과 Error로 나뉨

Error : 메모리 부족이나 심각한 시스템 오류 같이 복구 불가능한 시스템 예외!! 심각한거 애플리케이션 개발자는 이 예외를 잡으려고 해서는 안된다.

상위 예외를 catch로 잡으면 하위 예외까지 포함된다. 따라서 애플리케이션 로직에서는 Throwable 예외도 잡으면 안된다. 위의 Error예외도 함께 잡을 수 있기 떄문이다. 애플리케이션 로직은 이러한 이유로 Exception부터 필요한 예외로 생각하고 잡으면 된다.

Exception : 체크 예외 애플리케이션 롲기에서 사용할 수 있는 실질적인 최상위 예외. Exception과 그 하위 예외는 모두 컴파일러가 체크하는 체크 예외이다.

단 RuntimeException은 예외. RuntimeException : 언체크 예외, 런타임 예외

————————————— 예외 기본 규칙

예외는 폭탄 돌리기와 같다. 잡아서 처리하거나 처리할 수 없으면 밖으로 던져야 한다!!

예외를 처리하지 못하면 호출한 곳으로 예외를 계속 던진다.

  • 예외에 대한 기본 규칙 2가지
  1. 예외를 잡아서 처리하거나 던져야 한다.
  2. 예외를 잡거나 던질 때 지정한 예외뿐만 아니라 그 예외의 자식들도 함께 처리된다. 예로 Exception을 catch로 잡으면 하위 예외들도 모두 잡을 수 있다. 예로 Exception을 throws로 던지면 하위 예외들도 모두 던질 수 있다.

참고 : 예외를 처리하지 못하고 계속 던지면?? 자바 main 쓰레드의 경우 예외 로그를 출력하면서 시스템이 종료 웹 애플리케이션인 경우 사용자의 요청을 처리하기 떄문에 하나의 예외 때문에 시스템이 종료되면 안된다. WAS가 해당 예외를 받아서 처리하는데 ….. 주로 사용자에게 개발자가 지정한 오류 페이지를 보여준다.

 이렇게 에러를 만들면 컴파일러에서 에러를 밖으로 던지던지 처리하던지 둘 중 하나를 해야한다.

 이렇게 call을 불러보면 throw는 에러를 상위로 던져서 이 곳에서 처리하도록 해야함.

예외를 던질 떄 해당 타입과 하위 타입을 모두 던질 수 있다. 하지만, 최대한 자세히 던져줘야지 에러를 특정하기 쉽다.

  • 체크 예외 장단점 체크 예외는 예외를 잡아서 처리할 수 없을 떄 예외를 밖으로 던지는 throws 예외를 필수로 선언 장점 : 개발자가 실수로 예외를 누락하지 않음(컴파일러가 잡아줌) 단점 : 실제로는 개발자가 모든 예외를 잡거나 처리해야하기 때문에 너무 번거로움.. 추가로 의존관계에 따른 단점도 있음.

————————————————— 언체크 예외 기본 이해

RuntimeException과 그 하위 예외 말 그대로 컴파일러가 예외를 체크하지 않는다는 뜻 체크예외와 기본적으로 동일하나. 예외를 던지는 throws를 선언하지 않고 생략 가능. 생략하면 자동으로 던짐

 이렇게 생략 가능하며 생략 시 자동 밖으로 던짐

언체크 예외는 주로 생략하지만, IDE를 통해 좀 더 편리하게 인지할 수 있다.

언체크 예외 장단점.

장점 : 신경쓰고 싶지 않은 언체크 예외를 무시할 수 있다. 단점 : 개발자가 누락할 수 있다.

정리 : 체크 예외와 언체크 예외의 차이는 사실 예외를 처리할 수 없을 때 밖으로 던지는 부분이 있다. 이 부분을 필수로 선언해야 하는가 생략할 수 있는가의 차이다.

———————————————————— 체크 예외 활용

언체크 예외는 런타임 예외라도 많이 불린다.

기본 원칙 2가지..

  1. 기본적으로 언체크(런타임)예외를 사용하자.
  2. 체크 예외는 비즈니스 로직상 의도적으로 던지는 예외에만 사용하자
  • 이 경우 해당 예외를 잡아서 반드시 처리해야 하는 문제일 때만 체크 예외를 사용해야 한다.

  • 예제) 계좌 이체 실패, 결제시 포인트 부족, 로그인 ID,PW 불일치

  • 체크예외의 문제점 명시적, 누락 체크 해줌 안전하고 좋지만 체크 예외는 문제가 된다.

서비스 입장에서는 두 곳에서 올라오는 체크 예외인 SQL, Connect Exception 두개를 처리해야 한다. 하지만 이것을 Service를 처리할 수 있는가? 이런 문제들은 애플리케이션 로직에서 처리할 방법이 없다. 결국 Service는 둘 다 처리할 수 없어서 밖으로 던진다. 체크 예외이기 때문에 throws 를 붙여줘야 한다.. 심지어 controller도 처리 못해서 밖으로 던진다.

보통 이런 문제들은 사용자에게 자세히 설명하기 어렵다. 그래서 사용자에게는 일반적인 메시지를 보여준다. API라면 보통 HTTP 상태코드 500을 사용해서 응답을 내려줌 이렇게 해결 불가능한 오류는 별도의 오류를 남기고 개발자가 빨리 인지할 수 있또록 메일이나 알람을 통해서 전달 받아야 한다. 예를 들어 SQLException이 잘못된 SQL을 작성했다면 개발자가 수정해서 배포하기 전까지 같은 문제를 겪게 된다.

즉 controller, Service에서 해결 불가능한 문제를 throws라는 것을 쓰고 처리를 해줘야 하는 불편함 발생..

2가지 문제

  1. 복구 불가능한 예외

  2. 의존 관계에 대한 문제

  3. 복구 불가능한 예외 대부분의 예외는 복구가 불가능하다. SQLException을 예를 들면 데이터베이스에 무언가 문제가 있어서 발생하는 예외. 특히나 대부분의 서비스나 컨트롤러는 이런 문제를 해결할 수 없다. 따라서 이런 문제들은 일관성 있게 공통으로 처리해야 한다. 오류 로그를 남기고 개발자가해당 오류를 빠르게 인지한느 것이 필요. 서블릿 필터, 스프링 인터셉터, 스프링의 ControllerAdvice를 사용하면 공통으로 해결 가능.

  4. 의존 관계에 대한 문제 체크 예외의 또 다른 심각한 문제는 예외에 대한 의존 관계 문제이다. 앞서 대부분의 예외는 복구 불가능한 예외라고 했다. 그런데 체크 예외이기 때문에 컨트롤러나 서비스 입장에서는 본인이 처리할 수 없어도 어쩔 수 없이 throws를 통해 던지는 예외를 선언해야 한다. *** 이렇게 되면 서비스, 컨트롤러에서 SQLException을 의존하기 때문에 문제가 된다.

즉 향후에 리포지토리를 JDBC 기술이 아닌 다른 기술로 변경한다면? 그래서 SQLException이 아니라 JPAException으로 예외가 변경 된다면?!

서비스나 컨트롤러 입장에서는 어차피 본인이 처리할 수도 없는 예외를 욎ㄴ해야 하는 큰 단점; 결과적으로는 OCP, DI를 통해 클라이언트 코드의 변경 없이 대상 구현체를 변경할 수 있다는 장점이 체크 예외 떄문에 발목을 잡게 됨.

정리

  • 처리할 수 있는 체크 예외라면 서비스나 컨트롤러가 처리하겠지만 지금처럼 데이터베이스나 네트워크 통신처럼 시스템 레벨에서 올라온 예외들은 대부분 복구 불가능.
  • 문제는 이런 체크 예외를 사용하면 아래에서 올라온 복구 불가능한 예외를 서비스, 컨트롤러 같은 각각의 ㅡㄹ래스가 모두 ㅇ라고 있어야 한다. 그래서 불필요한 의존관계 문제가 발생

————————————————————— 언체크 예외 활용

 런타임예외로 바꿈

 이런식으로 체크 예외에서 런타임예외로 변경해서 보내줌.

런타임 예외 - 대부분 복구 불가능한 예외 일관성 있게 공통으로 처리해야함

런타임 예외 - 의존 관계에 대한 문제

런타임 예외이기 때문에 컨트롤러나 서비스가 예외를 처리할 수 없다면 생략 가능

공통 예외처리는 바꿔주어야 하지만 이전 일일이 다 바꿔주는 것에 비하면..

정리: 처음에 자바를 설계할 당시에는 체크 예외가 더 나은 선택이라고 생각했다. 그런데 시간이 흐르면서 복구 할 수 없는 예외가 너무 많아졌다.. 라이브러리도 많아지면서 처리해야 하는 예외도 더 많아 졌다.

추가로 런타임 예외는 놓칠 수 있기 때문에 문서화가 중요하다..

  • 런타임 예외는 문서화 런타임 예외는 문서화를 잘해야 한다. 또는 코드에 throws 런타임예외를 남겨서 중요한 예외를 인지할 수 있게 해준다.

—————————————————————— 예외 포함과 스택 트레이스

예외를 전환할 때는 꼭 이전 예외를 포함해줘야 한다. 그렇지 않으면 스택 트레이스를 확인할 때 심각한 문제가 발생.

SQLException을 RuntimeSQLException으로 전환하는 등 예외를 전환할 떄 이전 예외를 포함시키지 않으면 어떤 예외로 지금 Exception이 왔는지를 찾을 수 없게 된다.

예외를 전환할 떄는 꼭 기존 예외를 포함시켜야 한다!!

———————————————————— 스프링과 문제 해결 - 예외 처리, 반복

  • 체크 예외와 인터페이스 서비스 계츠은 가급적 순수하게 유지해야 한다. 이렇게 하려면 예외에 대한 의존도 함께 해결해야 한다.

예를 들어 SQLException의 의존을 제거하려면 서비스가 처리할 수 없으므로 런타임 예외로 전환해서 서비스 계층에 던지면 된다. 

그렇다면 기존에 Repository는 왜 안만들었는가?? SQLException이 체크 예외이기 때문에 만들 수 없었다. 체크 예외를 사용하려면 인터페이스에도 해당 체크 예외가 있어야 한다..

만약 체크 예외가 있으면서 도입을 했다면   일일이 메서드에 던지는 부분이 있어야 하고 쉽게 이야기하면 클래스와 인터페이스모두 SQLexception을 의존하고 있고 그렇게 되면 Repository 변경에 따른 Exception변경을 해야하므로 안된다..

  • 특정 기술에 종속되는 인터페이스 구현 기술을 변경하기 위해서는 인터페이스를 도입하더라도 SQLException이 있어서 결국은 기술에 종속적으로 설정을하게 된다..

  • 런타임 예외와 인터페이스 런타임 예외는 이런 부분에서 자유롭다. 인터페이스는 런타임 예외를 따로 지정하지 않아도 된다.

—————————————— 런타임 예외 적용

 런타임 예외인 MyDbException을 만들어서 이예외를 throw한다. 그러면 인터페이스에서 Exception 종속이 풀리게 된다.  **********그리고 필수로 기존 예외를 꼭 변환해줘야 한다!

이렇게 하면 Repository와 Service 코드도 이제는 throw SQLException에서 벗어나게 된다!

  • 정리 체크 예외를 런타임 예외로 변환하면서 인터페이스와 서비스 계층의 순수한 코드를 유지할 수 있다. 덕분에 향후 JDBC에서 다른 구현 기술로 변경하더라도 서비스 계층의 코드를 변경하지 않고 유지할 수 있다.

  • 남은 문제… 리포지토리에서 넘어오는 특정한 예외의 경우 복구를 시도할 수도 있다. ex) 같은 아이디 중복.. 그런데 지금 방식은 항상 MyDBException이라는 예외만 넘어와서 예외를 구분할 수 없다.. 예외를 구분해서 처리할 수 있을지

——————————————— 위에 문제처럼 서비스에서 특정한 예외들은 처리하고 싶을 떄 방법으로

 원하는 체크 예외를 런타임 예외로 변환해서 전달해주면 된다. 그러면 이전처럼 throws를 쓰지 않아도 되면서 런타임 에러를 처리해 줄 곳에서만 처리를하면 된다.

 catch를 이렇게 여러개 쓸 수도 있다.

정리: SQL ErrorCode로 데이터베이스에 어떤 오류가 있는지 확인할 수 있었다. 예외 변환을 통해 SQLException을 특정 기술에 의존하지 않는 직접 만든 예외인 MyDuplicateKeyException으로 변환할 수 있었다. 리포지토리 계층 예외를 변환해준 덕분에 서비스 계층은 특정 기술에 의존하지 않는 MyDuplicateKeyException을 사용해서 문제를 복구하고 서비스 계층도 순수성을 유지할 수 있었다.!!

남은 문제: SQL ErrorCode는 db마다 다르다..

————————————————————— 스프링 예외 추상화!

 각각의 예외는 특정 기술에 종속적이지 않게 설계되어 있다. 따라서 서비스 계층에서도 스프링이 제공하는 예외를 사용하면 된다!

  • 스프링이 제공하는 예외 변환기 스프링은 데이터베이스에서 발생하는 오류 코드를 자동으로 스프링이 정의한 예외로 바꿔주는 변환기를 제공한다.

 이렇게  사용하면 된다 첫번쨰 파라미터는 읽을 수 있는 설명, 두번째는 실행한 sql, 마지막은 발생된 SQLException

 이 파일에 대입해서 어떤 스프링 데이터 접근 예외로 전환해야 할지 찾아낸다..

정리 : 스프링 데이터 접근 계층에 대한 일관된 예외 추상화를 제공한다. 스프링 예외 추상화 덕분에 특정 기술에 종속적이지 않게 되었다. 물론 스프링이 제공하는 예외를 사용해서 스프링에 대한 기술 종속성..