스프링 db - 예외 처리 반복 해결

2023. 1. 30. 12:49spring/db

1/30

 

기존 코드는 모두 체크 예외(SQLException)에 종속되어 있다.

-> 향후 새로운 기술로 변경할 때 코드를 전부 변경해줘야 한다.

 

문제 해결 방향

- 예외 누수 해결

- 반복 예외 던지기 제거

 

* 런타임 예외 이용하기

 

- RuntimeException을 상속 받은 우리만의 런타임예외 만들기

//RuntimeException 상속
public class MyDbException extends RuntimeException {
    public MyDbException() {
    }

    public MyDbException(String message) {
        super(message);
    }

    public MyDbException(String message, Throwable cause) {
        super(message, cause);
    }

    public MyDbException(Throwable cause) {
        super(cause);
    }
}

 

- SQLException이 터지면 MyDbException으로 묶어서 던져준다.

catch (SQLException e) {
    throw new MyDbException(e);

그럼 밑의 throws SQLException 을 모두 제거할 수 있다.

public void delete(String memberId) throws SQLException 

 

주의할 점은 throw new MyDbException(e) 와 같이 예외를 꼭 넣어서 던져야 한다.

 

다음은, 해당 레포지토리를 이용하는 서비스 계층에서도 반복된 예외처리를 제거해야 한다.

 

(우선 반복된 예외 처리가 없는 레포지토리를 인터페이스로 구현하고 상속 받아서 사용하자)

public interface MemberRepository {
    Member save(Member member);
    Member findById(String memberId);
    void update(String memberId, int money);
    void delete(String memberId);

 

 

 

-> 레포지토리를 인퍼페이스로 구현해 놓으면 서비스 계층 등에서 레포지토리를 사용할때 인터페이스를 주입받도록 설정해 놓으면 유지보수가 편해진다. (DI)

-> 레포지토리 기술을 변경할때도 편해진다.

 

서비스 계층에서 해당 인터페이스를 주입받도록 설정한다.

변경전

MemberRepositoryV3 memberRepository) {
    this.memberRepository = memberRepository;
}

변경후

MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
}

 

========================================================================================

 

* 특정 예외 구분하기

 

- 데이터베이스에서는 예외에 해당 오류 코드를 포함해서 보내준다. (db마다 다름)

ex) e.getErrorCode() == 23505

 

- 해당 오류에 대한 예외 클래스를 새로 만든다.

public class MyDuplicateKeyException extends MyDbException {

-> 기술에 종속적이지 않아서 서비스계층의 순수성을 유지할 수 있다.

-> 향후 기술 변경시에도 예외는 그대로 유지해도 된다.

 

* 특정 예외 사용

if (e.getErrorCode() == 23505) {
    throw new MyDuplicateKeyException(e);
}

- 다음과 같이 오류 코드에 따른 예외를 설정해서 던져준다. (예시는 중복 예외)

 

catch (MyDuplicateKeyException e) {
    log.info("키 중복, 복구 시도");
    String retryId = generateNewId(memberId);
    log.info("retryId={}", retryId);
    repository.save(new Member(retryId, 0));
} catch (MyDbException e) {
    log.info("데이터 접근 계층 예외", e);
    throw e;

- 예외처리는 각 예외에 따라 예외처리를 다르게 하여 특정 예외에 따른 대처가 가능하다.

 

* SQL 의 에러코드는 데이터베이스마다 다르다는 한계가 있다.

키 중복 오류를 예시로 들면

H2 는 23505 이고, MySQL 은 1062 이다.

 

==========================================================================================

 

* 스프링에서 제공하는 예외 처리

- 데이터 접근 계층에 대한 수많은 예외를 정리해서 제공한다.

- 기술에 종속되지 않기 때문에 Jdbc든 JPA든 사용할 수 있다.

 

 

최상위 계층으로 DataAccessException 을 상속받는다. (런타임오류)

 

 

* 스프링에서 제공하는 예외 변환기 (무슨 예외인지 확인하고 그에 맞는 예외를 반환해준다.)

void exceptionTranslator(){
    String sql = "select bad grammer";

    try {
        Connection con = dataSource.getConnection();
        PreparedStatement pstmt = con.prepareStatement(sql);
        pstmt.executeQuery();
    } catch (SQLException e) {
        assertThat(e.getErrorCode()).isEqualTo(42122);

        SQLErrorCodeSQLExceptionTranslator exTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);
        DataAccessException resultEx = exTranslator.translate("select", sql, e);
        log.info("resultEx", resultEx);
        assertThat(resultEx.getClass()).isEqualTo(BadSqlGrammarException.class);
    }
}

 

SQLErrorCodeSQLExceptionTranslator ->  예외 변환 객체

.translate(1. "읽을 수 있는 설명 (주로 해당 함수 네임)" 2. sql 문장, 3. 예외(e))

 

--> 파일에 각각 데이터베이스의 에러 코드가 정리되어 있다.