본문 바로가기

코딩/JAVA 공부

[Spring] DB의 이해, 예외 추상화와 JDBC Template

앞서 봤던 Transaction의 문제점 중

1. 서비스 계층이 순수하지 않다 --> Transaction  AOP or  Transaction Manager로 트랜잭션 진행

2. 트랜잭션 동기화 문제 --> 트랜잭션 동기화 매니저로 커넥션 관리

3. 트랜잭션 적용 반복 --> Transaction AOP or Transaction Template 사용

으로 해결할 수 있었다.

다음으로 

4. 예외 누수

의 경우 예외 추상화로 해결 가능하다.

이를 위해 먼저 체크 예외, 언체크 예외를 알아야 한다.

 

체크 예외

체크 예외는 쉽게 말하자면 컴파일 단계에서 확인할 수 있는 예외이다.

이에 따라 해당 예외는 catch로 잡거나, throw로 던지는 게 필수로 필요하다.

현재 문제가 되는 SQLException이 체크 예외이기 때문에 서비스 계층까지 넘어왔고 여전히 Throw 하고 있다.

 

언체크 예외

런타임 예외라고 부를 수 있다.

이는 컴파일 단계에선 확인 불가능하고 이에 따라 발생한다고 해서 catch or throw가 필요하진 않다.

 

추상화된 언체크 예외로 예외 누수 해결하기

DB에서 예외가 발생하는 경우는 보통 서비스, 컨트롤러에서 해결할 수 없는 예외이다.

따라서 Throw를 통해 예외가 넘어온다고 해서 딱히 할 수 있는 게 없다.

그래서 계속 Throw 하게 되고, 불필요한 의존관계를 만들게 되는 것이다.

스프링에선 이런 DB관련 예외를 DataAccessException이라는 런타임 예외로 추상화했다.

즉 Repository에서 SQLException과 같이 DB에서 발생하는 체크 예외를, DataAccessException 런타임 예외로 받아서 넘긴다면,

이후 계층에서 예외를 던지지 않아도, 다른 예외를 참조하지 않을 수 있는 것이다.

// Repository

public void save(Member member) {
    
    try {
        // save 로직 실행
    } catch(SQLException e) {
        throw new DataAccessException(e);
    }
}

이러면, repository를 사용하는 service에서 더 이상 SQLException을 Throw 하지 않아도 되고, 예외 누수가 해결된다.

 

여기에 추가적으로,

SQLException은 예외 코드라는 것을 가지고 있다.

이는 DBMS에서 작성해 보내주는 코드로, DBMS와 예외 상황에 따라 코드가 달라진다.

예를 들어

- H2에서 키 중복 오류 발생 시 예외코드는 23505이고,

- MySQL에서 키 중복 오류 발생 시 예외코드는 1062이다.

이런 정보를 활용해 스프링에선 10개의 DBMS에서 사용되는 오류코드 정보를 

sql-error-codes.xml 에 가지고 있다.

또, 이를 활용해 SQLException을 분석해 특정 상황에 맞는 DataAccessException을 반환해 주는 예외 변환기

SQLExceptionTranslator 가 있다.

이는 아래와 같이 사용할 수 있다.

SQLExceptionTranslator = new exTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);
DataAccessException resultEx = exTranslator.translate("select", sql, e);
// 파라미터: 읽을 수 있는 설명, 실행한 sql구문, 발생된 SQLException

이를 Repository에 사용하면

// Repository

private final DataSource datasource;
privatr final SQLExceptionTranslator exTranslator;

// 생성자
public MemberRepository(DataSource dataSource) {
    this.dataSource = dataSource;
    exTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);
}

public Member save(Member member) {

    String sql = "sql 구문";

    Connection con = null;
    PreparedStatement pstmt = null;

    try {
    	// save 로직 실행
    } catch (SQLException e) {
        throw exTranslator.traslate("save", sql, e);
    } finally {
        close(con, pstmt, null);
    }
}

만약 sql 구문이 잘못되었다면 BadSqlGrammerException이 반환될 것이고,

중복 키 값이 예외가 발생했다면 DuplicateKeyException 이 반환될 것이다.

해당 정보는 Service에서 사용 가능한데,

만약 중복 키로 예외 발생 시 자동으로 키 뒤에 값을 붙여 저장하려 한다면, Service에서 DuplicateKeyException을 잡아 다른 로직을 시도하도록 할 수 있다!

이를 통해 예외 누수 문제를 해결했다.

 

마지막으로 JDBC 구문 반복은 JDBC Template로 해결할 수 있다.

 

JDBC Template

JDBC 사용 시

  - 커넥션 조회, 동기화

  - PreparedStatement 생성 및 파라미터 바인딩

  - 쿼리 실행

  - 결과 바인딩

  - 예외 발생 시 예외 변환기 실행

  - 리소스 (con, pstmt, rs) 종료

등은 항상 반복된다.

템플릿 콜백 패턴은 이를 효과적으로 처리해 주며, 스프링은 이를 위해 JdbcTemplate을 제공한다.

아래 코드를 보면 위에 언급한 반복 패턴을 함수 하나로 해결한 걸 볼 수 있다.

// Repository

private final JdbeTemplate template;

public MemberRepository(DataSource dataSource) {
    template = new JdbcTemplate(dataSource);
}

public Member save(Member member) {
    String sql = "sql 구문";
    template.update(sql, member.getMembetId(), member.getMoney());
    return member;
}

이를 통해 JDBC 구문 반복까지 해결할 수 있다.