체크 예외와 인터페이스
서비스 계층은 가급적 특정 구현 기술에 의존하지 않고, 순수하게 유지하는 것이 좋다. 이렇게 하려면 예외에 대한 의존도 함께 해결해야한다.
그렇다면 서비스가 처리할 수 없는 SQLException에 대한 의존은 어떻게 제거할 수 있을까?
이는 리포지토리에서 SQLException 체크 예외를 런타임 예외로 전환해서 서비스 계층에 던지면 된다.
스프링 예외 추상화 이해
런타임 예외 변환으로 체크 예외에 의존하는 것은 해결했지만, 각 예외상황마다 오류 코드가 다른 경우가 있다. 예를 들어 키 중복 오류 코드는 H2의 경우 23505, MySQL의 경우 1062인 것 처럼 말이다. 문제는 이 오류코드가 하나의 데이터베이스에도 수백개인데 또 데이터베이스 별로 다 다르다는 것이다.
만약 각 오류 코드 별로 분기문을 만들어 진행한다면 코드 양이 방대해지고, DB를 바꾸게 되면 전체 코드를 바꿔야 할 수도 있다.
이 문제를 스프링은 데이터 접근과 관련된 예외를 추상화해서 해결해준다. 계층 구조는 다음과 같다.
- 스프링은 데이터 접근 계층에 대한 수십 가지 예외를 정리해서 일관된 예외 계층을 제공한다.
- 각 예외는 특정 기술에 종속적이지 않게 설계되어 있다. 따라서 서비스 계층에서도 스프링이 제공하는 예외를 사용하면 된다.
- 가장 상위에는 DataAccessException 예외가 있다. 이는 런타임 예외를 상속 받았기에 모두 런타임 예외이다.
- DataAccessException
- 이는 NonTransient, Transient 예외로 구분된다.
- Transient는 일시적이라는 뜻으로, Transient 하위 예외는 동일한 SQL을 다시 시도했을 때 성공할 가능성이 있다.
- 예를 들어 쿼리 타임아웃, 락과 관련된 오류들이 있다. 이런 오류들은 데이터베이스 상태가 좋아지거나, 락이 풀렸을 때 다시 시도하면 성공할 수도 있다.
- NonTransient는 일시적이지 않다는 뜻이다. 같은 SQL을 그대로 반복해서 실행하면 실패한다.
- SQL 문법 오류, 데이터베이사 제약조건 위배 등이 있다.
스프링이 제공하는 예외 변환기
스프링은 데이터베이스에서 발생하는 오류 코드를 스프링이 정의한 예외로 자동으로 변환해주는 변환기를 제공한다. 구체적인 코드는 다음과 같다.
SQLExceptionTranslator exTranslator =
new SQLErrorCodeSQLExceptionTranslator(dataSource);
DataAccessException resultEx = exTranslator.translate("select", sql, e);
- translate(a, b, c) : a는 읽을 수 있는 설명이고, b는 실행한 sql, c는 발생된 Exception을 전달하면 된다. 이렇게 하면 적절한 스프링 데이터 접근 계층의 예외로 변환해서 반환해준다.
- 이렇게 하면 일일이 if (e.getErrorCode().equals(40122)) 처럼 코드별로 분기문으로 나눠 오류코드를 처리하지 않아도 된다.
그렇다면 스프링에서는 어떻게 이 코드들을 인식하고 처리하는 걸까? 비밀은 다음 파일에 있다.
// sql-error-codes.xml 파일
<bean id="H2" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="badSqlGrammarCodes">
<value>42000,42001,42101,42102,42111,42112,42121,42122,42132</value>
</property>
<property name="duplicateKeyCodes">
<value>23001,23505</value>
</property>
</bean>
<bean id="MySQL" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="badSqlGrammarCodes">
<value>1054,1064,1146</value>
</property>
<property name="duplicateKeyCodes">
<value>1062</value>
</property>
</bean>
- 각 코드 별로 에러명이 분류되어 있는 것을 확인할 수 있다.
정리
스프링이 예외를 추상화 해준 덕분에, 서비스 계층은 특정 리포지토리의 구현 기술과 예외에 종속적이지 않게 되었다. 따라서 서비스 계층은 특정 구현 기술이 변경되어도 그대로 유지할 수 있게 되었다. 다시 DI를 제대로 적용할 수 있게 된 것이다.
추가로 서비스 계층에서 예외를 catch해서 복구해야 하는 경우, 예외가 스프링이 제공하는 데이터 접근 예외로 변경되어서 서비스 계층에 넘어오기 때문에, 필요한 경우 예외를 잡아서 복구하면 된다.
Reference
인프런 김영한 - '스프링 DB 1편 - 데이터 접근 핵심 원리'
'Spring' 카테고리의 다른 글
[스프링/JDBC Template] 스프링-디비1편 6-2. JDBC Template (0) | 2024.02.02 |
---|---|
[스프링/@Transactional] 4-2. 트랜잭션 AOP 이해 (0) | 2024.02.01 |
[스프링/트랜잭션] 4-1. 스프링의 트랜잭션 (0) | 2024.02.01 |
[스프링/DataSource] 스프링-DB 1편 2. ConnectionPool, DataSource의 이해 (0) | 2024.01.30 |
[Spring] @PostConstruct에 관해서 몇가지 정리 (0) | 2022.03.18 |