체크 예외와 인터페이스
서비스 계층은 가급적이면 특정 구현 기술에 의존하지 않고 순수하게 유지되는 것이 좋음. 그러나 이렇게 하려면 예외에 대한 의존도 함꼐 해결해야 함
예를 들어서 리포지토리가 던지는 SQLException 체크 예외는 서비스에서 처리할수 있든 없든 체크 예외이므로 반드시 SQLException 예외에 의존하게 됨.
이러한 문제를 해결하기 위해 서비스가 처리할 수 없는 리포지토리의 예외를 체크예외가 아닌 런타임 예외로 전환하여 서비스 계층에 던지게 하면 런타임 예외이므로 서비스 계층에선 해당 예외를 무시할 수 있게되고 특정한 구현 기술에 의존하지 않아도 됨
데이터 접근 예외
데이터베이스 오류에는 보통 각 오류에 대한 오류 코드가 존재함
각각의 데이터베이스 마다 자신의 오류코드를 직접 정의하며 예외 발생시 해당 오류코드도 같이 전달함
스프링 예외 추상화 이해
- 스프링은 데이터 접근 계층에 대한 수십가지 예외를 정리하여 일관된 예외 계층을 제공
- 각각의 예외는 특정 기술에 종속적이지 않게 설계되어 있음. 그러므로 서비스 계층에서도 스프링이 제공하는 에외를 사용하면 됨
- JDBC나 JPA를 사용할 때 발생하는 예외를 스프링이 제공하는 예외로 변환해주는 역할도 스프링이 제공함
- 예외의 최고 상위는 org.springframework.dao.DataAccessException
- 스프링이 제공하는 데이터 접근 계층의 모든 예외는 런타임 예외 임
- DataAccessException는 크게 2가지로 구분하되는데 NonTransient예외와 Transient예외 두가지가 있음
- NonTransient
- 일시적이지 않은 예외
- SQL 문법오류, 데이터베이스 제약조건과 같은 SQL을 그대로 반복해서 실행하면 실패 함
- Transient
- 일시적 예외
- Transient 하위의 예외는 동일한 SQL을 다시 시도했을떄 성공할 가능성이 있음
- 예를 들면 쿼리 타임아웃, 락과 관련된 오류
- NonTransient
스프링이 제공하는 예외 변환기
스프링은 데이터베이스에서 발생하는 오류 코드를 스프링이 정의한 예외로 자동으로 변환해주는 변환기를 제공 함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
void exceptionTranslator() {
String sql = "select bad grammar";
try {
Connection con = dataSource.getConnection();
PreparedStatement stmt = con.prepareStatement(sql);
stmt.executeQuery();
} catch (SQLException e) {
assertThat(e.getErrorCode()).isEqualTo(42122);
//org.springframework.jdbc.support.sql-error-codes.xml
SQLExceptionTranslator exTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);
//org.springframework.jdbc.BadSqlGrammarException
DataAccessException resultEx = exTranslator.translate("select", sql, e);
log.info("resultEx", resultEx);
assertThat(resultEx.getClass()).isEqualTo(BadSqlGrammarException.class);
}
}
각 DB마다 정의한 SQL 에러코드는 저마다 다른데, 스프링은 이를 sql-error-codes.xml에 정의하여 에러코드를 예외로 변환함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<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>
...
- org.springframework.jdbc.support.sql-error-codes.xml
스프링 예외 추상화 정리
- 스프링은 데이터 접근 계층에 대한 일관된 에외 추상화를 제공함
- 스프링은 예외 변환기를 통해 SQLException의 ErrorCode에 맞는 적절한 스프링 데이터 접근 예외로 변환 해 줌
- 만약 서비스, 컨트롤러 계층에서 예외 처리가 필요하면 특정 기술에 종속적인 SQLException 같은 예외보단 스프링이 제공하는 데이터 접근 예외를 사용하면 됨
- 스프링이 제공하는 예외를 사용하기 때문에 스프링에 대한 기술 종속성은 발생함
JDBC 반복문제 - JdbcTemplate
리포지토리에서 동작하는 각 메소드는 보통 아래와 같은 공통 코드가 필요하게 됨
- 커넥션 조회, 커넥션 동기화
- PreparedStatement 생성 및 파라미터 바인딩
- 쿼리 실행
- 결과 바인딩
- 예외 발생시 스프링 예외 변환기 실행
- 리소스 종료
위와 같은 반복된 코드를 효과적으로 처리하는 방법이 템플릿 콜백 패턴임. 스프링은 JDBC의 반복 문제를 해결하기 위해 JdbcTemplate이라는 템플릿을 제공함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class MemberRepositoryImpl implements MemberRepository {
private final JdbcTemplate template;
public MemberRepositoryImpl(DataSource dataSource) {
template = new JdbcTemplate(dataSource);
}
@Override
public Member save(Member member) {
String sql = "insert into member(member_id, money) values(?, ?)";
template.update(sql, member.getMemberId(), member.getMoney());
return member;
}
@Override
public Member findById(String memberId) {
String sql = "select * from member where member_id = ?";
return template.queryForObject(sql, memberRowMapper(), memberId);
}
@Override
public void update(String memberId, int money) {
String sql = "update member set money=? where member_id=?";
template.update(sql, money, memberId);
}
@Override
public void delete(String memberId) {
String sql = "delete from member where member_id=?";
template.update(sql, memberId);
}
private RowMapper<Member> memberRowMapper() {
return (rs, rowNum) -> {
Member member = new Member();
member.setMemberId(rs.getString("member_id"));
member.setMoney(rs.getInt("money"));
return member;
};
}
}
JdbcTemplate은 JDBC로 개발할 떄 발생하는 반복을 대부분 해결해 줌. 또한 트랜잭션을 위한 커넥션 동기화, 예외 발생시의 스프링 예외 변환기도 자동으로 실행 함.
정리
- 서비스 계층의 순수성
- 트랜잭션 추상화 + 트랜잭션 AOP 덕분에 서비스 계층의 순수성을 최대한 유지하면서 서비스 계층에서 트랜잭션을 사용할 수 있음
- 스프링이 제공하는 예외 추상화와 변환기 덕분에, 데이터 접근 기술이 변경되어도 서비스 계층의 순수성을 유지하면서 예외도 사용할 수 있음
- 서비스 계층이 리포지토리 인터페이스에 의존한 덕분에 향후 리포지토리가 다른 구현 기술로 변경되어도 서비스 계층을 순수하게 유지할 수 있음
- 리포지토리에서 JDBC를 사용하는 반복코드가 JdbcTemplate으로 대부분 제거할 수 있음
참고
- 스프링 DB - 데이터 접근 핵심 원리(김영한)