본문 바로가기
JAVA/Spring & Java 학습 기록

스프링의 예외 누수 문제 해결 변천사

by 구본식 2023. 6. 25.

초기 상황


앞서 글에서 말했다시피 예외 누수의 문제가 남아있다.

Repository에 현재 JDBC 기술을 사용하고 있고 JDBC 예외인 SQLException체크 예외이기 때문에 처리하거나 던져야하며, 이로 인해 Service, Controller도 해당 예외를 적어야한다.(throws)

즉,  Service, Controller가 특정 예외에 의존하게 되는 것이다.

(참고로 체크 예외, 언체크 예외의 기본 개념을 안다는 전체하에 설명할 예정이다.!!)

 

체크 예외를 사용할 시 어떤 파급 효과가 발생할 수 있는지 살펴보자.

기본적으로 예외들의 대다수는 복구 불가능 예외이므로, Service, Controller에서는 예외를 던지게 되고

스프링 인터셉터, controllerAdvice, 서블릿 필터에서 공통으로 처리하는 방법을 많이 사용한다.

 

SQLException, ConnectException은 모두 체크 예외이고 Controller, Service에서 처리할 수 없으므로

mothod에 throws 예외로 던져야 할 것이다. (번거롭게 항상 적어줘야되고, 모든 예외를 챙겨야 된다.)

 

또한, JDBC 기술에서 JPA, MyBatis 등의 다른 기술로 교체가 발생할 시 해당 기술에 맞는 예외로 교체해주지 않으면

컴파일 오류가 발생하게 될 것이다. 즉, 특정 기술에 의존하게 되는 것이다.

 

그렇다고, 최상위 타입 Exception 클래스로 throws하면 될까?

물론 컴파일 측면에서는 문제가 없다. 하지만 중간에 반드시 처리되어야 할 중요한 체크 예외가 있을 경우 이를 놓칠 수 있는 문제가 발생할 수 있다.

 

이러한 문제점을 언체크 예외(런타임 예외)를 통해 해결해보자.

 

런타임 예외로 해결해보기


먼저 런타임 예외를 상속받은 사용자 정의 예외 클래스를 만든다.

기존에는 체크 에외 SQLException을 그대로 던졌다면, 이를 런타임 예외로 교체해서 던지게 된다.

런타임 예외로 교체했기 때문에, Service, Controller method에는 해당 예외를 throws로 선언하지 않아도 되고,

특정 예외들을 의존하지 않아도 된다.!!

 

결과적으로 이전과 다르게 기술 교체 시(JPA, MyBatis 등) 파급 효과가 현저히 줄어들게 된다.

Service, Controller 에는 코드 변경이 필요없고, 예외를 공통으로 처리하는 부분만 변경을 하면 되는 것이다.

 

하지만 여전히 문제점은 남아있다.

SQLException에 대해서 MyDBException으로 변환하게 되는데, 실제 SQLException에는 문법 오류, PK 오류, Lock 오류등 다양한 상황들이 존재하게 된다.

실제 다양한 DB 오류 상황을 현재는 하나의 예외로만 던지고 있기 때문에,

특정한 예외를 Service에서 복구(처리)하고 싶은 상황에는 대처하지 못하게 된다.

 

그렇기 때문에, SQLException 내부에 데이터베이스에 어떤 문제가 발생했는지 알려주기 위해 errorCode를 제공한다.

(참고로 DB 마다 errorCode가 다르다.)

 

간단히 코드 통해 보자면, SQLException 내부의 errorCode를 이용해서 다른 런타임 예외를 던지면 된다.

그리고 복구하고 싶은 예외가 있을 경우, Service layer 에서는 필요한 예외를 try~catch 한후 처리하면 된다.

 

하지만 이러한 방식으로는 문제를 해결 할 수 없다. 아래와 같은 이유가 존재하기 때문이다.

1. DB에 수많은 예외가 존재한다. => 필요한 모든 예외를 런타임 예외로 만들어 놓기 쉽지 않고, 변환하기도 쉽지 않다.

2. DB 마다 오류 코드가 다르다. => DB마다 1번을 반복해야 된다.

 

스프링이 이러한 문제를 어떻게 해결했는지 살펴 보도록 하자.

 

스프링의 문제 해결


스프링이 예외 추상화 

일부

스프링은 데이터 접근 계층에 대한 다양한 예외를 런타입 예외로 정리해서 제공해준다.

각각의 예외는 특정 기술(JPA, JDBC)에 종속되지 않게 설계되어 있으며, Service, Controller는 스프링이 제공해주는 예외에 의존하면 된다.

 

스프링이 제공해주는 예외 변환기

또한, 특정 기술에서 발생하는 각기 다른 예외를 스프링이 제공해주는 예외로 변환하는 작업을 스프링이 제공해준다.

더불어, 데이터베이스마다 발생하는 각기 다른 오류 코드를 스프링이 제공해주는 예외로 변환하는 작업도 지원해준다.

 

위의 코드에서 사용된 예외 변환기 SQLExceptionTranslator는 JDBC 전용 변환기이다. JPA 기술을 사용할 시 스프링은 이에 맞는 변환기를 내장하고 있기 때문에 동일하게 동작해준다.

 

스프링은  예외 변환기를 통해 스프링이 제공해주는 예외로 변환해서 반환해주게 된다. 

실제 SQL 문법 예외를 발생시키게 되면 아래와 같이 BasSqlGrammarException의 스프링 예외를 반환해주는 것을 볼 수 있다.

참고로 DB마다 각기 다른 ErrorCode까지 스프링이 고려할 수 있는 이유는 
org.springframework.jdbc.support.sql-error-codes.xml 와 같은 파일을 내장하고 있어, 각 DB 마다 ErrorCode 에 맞는 
스프링 예외로 변환해주게 된다.

 

이와 같이 스프링의 추상화된 예외 덕분에 서비스, 컨트롤러 계층의 특정 기술 예외 의존성을 해결할 수 있었고, 

필요 시 스프링의 예외를 잡아서 사용하면 된다.

 

❗조금 더 나아가,  JPA 기술은 관련 없지만, 앞서와 같이 JDBC 기술을 사용 시

커넥션 조회, 커넥션 동기화, 파라미터 바인딩, 쿼리 실행, 결과 바인딩, 스프링 예외 변환기 실행, 리소스 종료 등과 같이 반복되는 코드가 존재하는 문제점이 발생한다. 이를 해결해보자.

 

JdbcTemplate

앞 포스터에서 Service 계층의 트랜잭션의 반복 코드 문제(트랜잭션 시작, 커밋, 롤백 등)를 해결하기 위해서

사용한 TransactionTemplate 클래스와 같은 템플릿 콜백 패턴의 템플릿 기술이다.

 

앞 포스터와 같이 텔플릿 콜백 패턴은 추후 자세히 공부 후 더 정리해보겠다. 여기서는 JdbcTemplate 사용시 어떻게 바뀌는지를 초점으로 살펴보겠다.

 

JdbcTemplate 를 통해 커넥션 조회, 커넥션 동기화 등은 물론이고 스프링 예외 변환기도 자동으로 실행해주기 때문에,

JDBC 사용시 반복되는 코드 문제를 해결할 수 있다.

 

자료


  •  김영한 스프링 DB 1편-데이터 접근 핵심 원리