이제 스프링에서 데이터 접근과 관련된 예외 추상화를 어떻게 하는지 알아보자.
데이터 접근 기술에 대한 예외를 직접 복구하고 싶은 경우 개별 데이터베이스마다 에러코드를 다루어서 처리해야 하는 고충이 존재했다.
언체크 예외를 통해 체크 예외를 감싸 커스텀 예외로 서비스 계층의 의존성을 낮추어도 실제 에러 코드를 식별해 적절한 언체크 예외를 처리하는 코드 블락이 문제가 발생한다.
이는 데이터베이스의 에러코드는 전부 다르고 이를 상수로 처리한다면 변경에 취약해지기 때문이다.
그렇기 때문에 스프링에서는 데이터 접근 기술과 관련된 예외를 추상화해서 제공한다.
스프링은 수십 가지의 예외를 정리해서 일관된 예외 계층을 제공한다. 따라서 개발자가 별도의 데이터 접근 예외를 굳이 만들 필요가 없다. 각각 예외는 모두 특정 기술에 종속적이지 않게 설계되어 있다.
특정 기술에서 발생하는 모든 예외를 스프링 예외로 변경해주는 역할도 스프링이 제공한다.
예외의 최고 상위는 RuntimeException을 상속받은 DataAccessException이다.
DataAccessException 하위 예외는 모두 RuntimeException이며 이는 크게 2가지로 구분된다.
1. Transient 예외
- 이는 일시적인 예외로 SQL을 다시 시도했을 때 성공할 가능성이 존재한다. 쿼리 타임아웃, 락과 관련된 오류들로 데이터베이스 상태에 따라 발생할 수 있다.
2. NonTransient 예외
- 반복해도 성공할 수 없는 예외로 SQL 문법 오류, 데이터 베이스 제약조건 위배 등이 존재한다.
스프링의 편리성은 끝이 아닌데, 데이터베이스에서 발생하는 모든 오류 코드를 스프링이 정의한 예외로 자동 변환 해주는 변환기를 제공한다는 것이다.
@Test
void exceptionTransducer() {
String sql = "select bad grammer";
try {
Connection con = dataSource.getConnection();
PreparedStatement stmt = con.prepareStatement(sql);
stmt.executeQuery();
} catch (SQLException e) {
SQLErrorCodeSQLExceptionTranslator exTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);
DataAccessException resultException = exTranslator.translate("select job", sql, e);
log.info("result Ex", resultException);
}
}
SQLErrorCodeSQLExceptionTranslator를 통해 에러를 처리하게 하면 다음과 같이 스프링에서 제공하는 예외로 변환된다.
이를 통해 Repository에서 예외 변환을 직접할 필요가 없다는 것을 알 수 있다.
스프링은 이를 sql-error-codes.xml파일을 통해 각각의 데이터베이스가 제공하는 SQL ErrorCode까지 고려한다.
sql-error-codes.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<!--
- Default SQL error codes for well-known databases.
- Can be overridden by definitions in a "sql-error-codes.xml" file
- in the root of the class path.
-
- If the Database Product Name contains characters that are invalid
- to use in the id attribute (like a space) then we need to add a property
- named "databaseProductName"/"databaseProductNames" that holds this value.
- If this property is present, then it will be used instead of the id for
- looking up the error codes based on the current database.
-->
<beans>
<bean id="DB2" name="Db2" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="databaseProductName">
<value>DB2*</value>
</property>
<property name="badSqlGrammarCodes">
<value>-007,-029,-097,-104,-109,-115,-128,-199,-204,-206,-301,-408,-441,-491</value>
</property>
<property name="duplicateKeyCodes">
<value>-803</value>
</property>
<property name="dataIntegrityViolationCodes">
<value>-407,-530,-531,-532,-543,-544,-545,-603,-667</value>
</property>
<property name="dataAccessResourceFailureCodes">
<value>-904,-971</value>
</property>
<property name="transientDataAccessResourceCodes">
<value>-1035,-1218,-30080,-30081</value>
</property>
<property name="deadlockLoserCodes">
<value>-911,-913</value>
</property>
</bean>
<bean id="Derby" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="databaseProductName">
<value>Apache Derby</value>
</property>
<property name="useSqlStateForTranslation">
<value>true</value>
</property>
<property name="badSqlGrammarCodes">
<value>42802,42821,42X01,42X02,42X03,42X04,42X05,42X06,42X07,42X08</value>
</property>
<property name="duplicateKeyCodes">
<value>23505</value>
</property>
<property name="dataIntegrityViolationCodes">
<value>22001,22005,23502,23503,23513,X0Y32</value>
</property>
<property name="dataAccessResourceFailureCodes">
<value>04501,08004,42Y07</value>
</property>
<property name="cannotAcquireLockCodes">
<value>40XL1</value>
</property>
<property name="deadlockLoserCodes">
<value>40001</value>
</property>
</bean>
<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>
<property name="dataIntegrityViolationCodes">
<value>22001,22003,22012,22018,22025,23000,23002,23003,23502,23503,23506,23507,23513</value>
</property>
<property name="dataAccessResourceFailureCodes">
<value>90046,90100,90117,90121,90126</value>
</property>
<property name="cannotAcquireLockCodes">
<value>50200</value>
</property>
</bean>
<bean id="HDB" name="Hana" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="databaseProductNames">
<list>
<value>SAP HANA</value>
<value>SAP DB</value>
</list>
</property>
<property name="badSqlGrammarCodes">
<value>
257,259,260,261,262,263,264,267,268,269,270,271,272,273,275,276,277,278,
278,279,280,281,282,283,284,285,286,288,289,290,294,295,296,297,299,308,309,
313,315,316,318,319,320,321,322,323,324,328,329,330,333,335,336,337,338,340,
343,350,351,352,362,368
</value>
</property>
<property name="permissionDeniedCodes">
<value>10,258</value>
</property>
<property name="duplicateKeyCodes">
<value>301</value>
</property>
<property name="dataIntegrityViolationCodes">
<value>461,462</value>
</property>
<property name="dataAccessResourceFailureCodes">
<value>-813,-709,-708,1024,1025,1026,1027,1029,1030,1031</value>
</property>
<property name="invalidResultSetAccessCodes">
<value>-11210,582,587,588,594</value>
</property>
<property name="cannotAcquireLockCodes">
<value>131</value>
</property>
<property name="cannotSerializeTransactionCodes">
<value>138,143</value>
</property>
<property name="deadlockLoserCodes">
<value>133</value>
</property>
</bean>
<bean id="HSQL" name="Hsql" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="databaseProductName">
<value>HSQL Database Engine</value>
</property>
<property name="badSqlGrammarCodes">
<value>-22,-28</value>
</property>
<property name="duplicateKeyCodes">
<value>-104</value>
</property>
<property name="dataIntegrityViolationCodes">
<value>-9</value>
</property>
<property name="dataAccessResourceFailureCodes">
<value>-80</value>
</property>
</bean>
<bean id="Informix" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="databaseProductName">
<value>Informix Dynamic Server</value>
</property>
<property name="badSqlGrammarCodes">
<value>-201,-217,-696</value>
</property>
<property name="duplicateKeyCodes">
<value>-239,-268,-6017</value>
</property>
<property name="dataIntegrityViolationCodes">
<value>-692,-11030</value>
</property>
</bean>
<bean id="MS-SQL" name="SqlServer" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="databaseProductName">
<value>Microsoft SQL Server</value>
</property>
<property name="badSqlGrammarCodes">
<value>156,170,207,208,209</value>
</property>
<property name="permissionDeniedCodes">
<value>229</value>
</property>
<property name="duplicateKeyCodes">
<value>2601,2627</value>
</property>
<property name="dataIntegrityViolationCodes">
<value>544,8114,8115</value>
</property>
<property name="dataAccessResourceFailureCodes">
<value>4060</value>
</property>
<property name="cannotAcquireLockCodes">
<value>1222</value>
</property>
<property name="deadlockLoserCodes">
<value>1205</value>
</property>
</bean>
<bean id="MySQL" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="databaseProductNames">
<list>
<value>MySQL</value>
<value>MariaDB</value>
</list>
</property>
<property name="badSqlGrammarCodes">
<value>1054,1064,1146</value>
</property>
<property name="duplicateKeyCodes">
<value>1062</value>
</property>
<property name="dataIntegrityViolationCodes">
<value>630,839,840,893,1169,1215,1216,1217,1364,1451,1452,1557</value>
</property>
<property name="dataAccessResourceFailureCodes">
<value>1</value>
</property>
<property name="cannotAcquireLockCodes">
<value>1205,3572</value>
</property>
<property name="deadlockLoserCodes">
<value>1213</value>
</property>
</bean>
<bean id="Oracle" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="badSqlGrammarCodes">
<value>900,903,904,917,936,942,17006,6550</value>
</property>
<property name="invalidResultSetAccessCodes">
<value>17003</value>
</property>
<property name="duplicateKeyCodes">
<value>1</value>
</property>
<property name="dataIntegrityViolationCodes">
<value>1400,1722,2291,2292</value>
</property>
<property name="dataAccessResourceFailureCodes">
<value>17002,17447</value>
</property>
<property name="cannotAcquireLockCodes">
<value>54,30006</value>
</property>
<property name="cannotSerializeTransactionCodes">
<value>8177</value>
</property>
<property name="deadlockLoserCodes">
<value>60</value>
</property>
</bean>
<bean id="PostgreSQL" name="Postgres" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="useSqlStateForTranslation">
<value>true</value>
</property>
<property name="badSqlGrammarCodes">
<value>03000,42000,42601,42602,42622,42804,42P01</value>
</property>
<property name="duplicateKeyCodes">
<value>21000,23505</value>
</property>
<property name="dataIntegrityViolationCodes">
<value>23000,23502,23503,23514</value>
</property>
<property name="dataAccessResourceFailureCodes">
<value>53000,53100,53200,53300</value>
</property>
<property name="cannotAcquireLockCodes">
<value>55P03</value>
</property>
<property name="cannotSerializeTransactionCodes">
<value>40001</value>
</property>
<property name="deadlockLoserCodes">
<value>40P01</value>
</property>
</bean>
<bean id="Sybase" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="databaseProductNames">
<list>
<value>Sybase SQL Server</value>
<value>Adaptive Server Enterprise</value>
<value>ASE</value> <!-- name as returned by jTDS driver -->
<value>SQL Server</value>
<value>sql server</value> <!-- name as returned by jTDS driver -->
</list>
</property>
<property name="badSqlGrammarCodes">
<value>101,102,103,104,105,106,107,108,109,110,111,112,113,116,120,121,123,207,208,213,257,512</value>
</property>
<property name="duplicateKeyCodes">
<value>2601,2615,2626</value>
</property>
<property name="dataIntegrityViolationCodes">
<value>233,511,515,530,546,547,2615,2714</value>
</property>
<property name="transientDataAccessResourceCodes">
<value>921,1105</value>
</property>
<property name="cannotAcquireLockCodes">
<value>12205</value>
</property>
<property name="deadlockLoserCodes">
<value>1205</value>
</property>
</bean>
</beans>
스프링은 데이터 접근 계층에 대한 모든 예외에 대해 추상화를 제공한다.
즉 스프링을 사용한다면 특정 기술에 종속적인 예외를 사용하는 것 대신 스프링이 제공하는 예외 추상화를 사용하면 된다.
catch (SQLException e) {
throw exTranslator.translate("update", sql, e);
}
지금까지 우리는 서비스 계층의 순수성을 남기기 위한 노력을 엿보았다.
1. 트랜잭션 매니저를 통한 추상화와 트랜잭션 AOP를 통해 서비스 계층의 순수함을 남기고 서비스 계층에서 트랜잭션을 사용할 수 있었다.
2. 스프링이 제공하는 예외 추상화를 통해 특정 기술에 종속되지 않는 예외를 쉽게 다루고 서비스 계층의 순수성을 유지했다.
추가적으로 데이터베이스의 연결 구조를 알아보았고, 트랜잭션의 흐름, 서비스 계층에서 인터페이스를 통한 DI로 데이터 접근 계층의 의존성을 낮추는 것까지 해보았다.
하지만 서비스 계층에 대한 문제를 개선해나가는 방법을 보고 데이터 접근 계층을 보면 난감하다.
SQLMapper, ORM 기술의 사용법은 일단 제쳐두고 순수 JDBC를 사용하는 코드는 반복의 연속이다.
이를 해결하기 위해 JDBC 기반의 여러 편리한 기술이 등장했다.
다음에는 JDBC 기반의 여러 기술에 대해 상세히 알아볼 예정이다.
'Spring|Spring-boot' 카테고리의 다른 글
Spring Transaction Option (0) | 2022.10.15 |
---|---|
Spring DB (5) (0) | 2022.10.14 |
Spring DB (4) (1) | 2022.09.19 |
스프링 DB (3) (0) | 2022.09.05 |
스프링 DB (2) (0) | 2022.08.15 |
댓글