Home Spring DB - 예외처리, 반복
Post
Cancel

Spring DB - 예외처리, 반복

체크 예외와 인터페이스

서비스 계층은 가급적이면 특정 구현 기술에 의존하지 않고 순수하게 유지되는 것이 좋음. 그러나 이렇게 하려면 예외에 대한 의존도 함꼐 해결해야 함

예를 들어서 리포지토리가 던지는 SQLException 체크 예외는 서비스에서 처리할수 있든 없든 체크 예외이므로 반드시 SQLException 예외에 의존하게 됨.

이러한 문제를 해결하기 위해 서비스가 처리할 수 없는 리포지토리의 예외를 체크예외가 아닌 런타임 예외로 전환하여 서비스 계층에 던지게 하면 런타임 예외이므로 서비스 계층에선 해당 예외를 무시할 수 있게되고 특정한 구현 기술에 의존하지 않아도 됨

데이터 접근 예외

데이터베이스 오류에는 보통 각 오류에 대한 오류 코드가 존재함

Alt text

각각의 데이터베이스 마다 자신의 오류코드를 직접 정의하며 예외 발생시 해당 오류코드도 같이 전달함

스프링 예외 추상화 이해

Alt text

  • 스프링은 데이터 접근 계층에 대한 수십가지 예외를 정리하여 일관된 예외 계층을 제공
  • 각각의 예외는 특정 기술에 종속적이지 않게 설계되어 있음. 그러므로 서비스 계층에서도 스프링이 제공하는 에외를 사용하면 됨
  • JDBC나 JPA를 사용할 때 발생하는 예외를 스프링이 제공하는 예외로 변환해주는 역할도 스프링이 제공함
  • 예외의 최고 상위는 org.springframework.dao.DataAccessException
  • 스프링이 제공하는 데이터 접근 계층의 모든 예외는 런타임 예외 임
  • DataAccessException는 크게 2가지로 구분하되는데 NonTransient예외와 Transient예외 두가지가 있음
    • NonTransient
      • 일시적이지 않은 예외
      • SQL 문법오류, 데이터베이스 제약조건과 같은 SQL을 그대로 반복해서 실행하면 실패 함
    • Transient
      • 일시적 예외
      • Transient 하위의 예외는 동일한 SQL을 다시 시도했을떄 성공할 가능성이 있음
      • 예를 들면 쿼리 타임아웃, 락과 관련된 오류

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

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

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 - 데이터 접근 핵심 원리(김영한)
This post is licensed under CC BY 4.0 by the author.