-
스프링 예외 추상화스프링/스프링 기초DB 2023. 3. 19. 17:00
예외가 발생했을 때 해당 예외를 그대로 반환하는 것이 아니라 런타임 예외를 따로 만들어서 해당 예외를 반환하는 것이 효율적이다.
@Test void duplicateKeySave() { service.create("myId"); service.create("myId"); // 같은 아이디 저장 시도 } @Slf4j @RequiredArgsConstructor static class Service { private final Repository repository; public void create(String memberId) { try { repository.save(new Member(memberId, 0)); log.info("saveId={}", memberId); } 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; } } private String generateNewId(String memberId) { return memberId + new Random().nextInt(10000); } } @RequiredArgsConstructor static class Repository { private final DataSource dataSource; public Member save(Member member) { String sql = "insert into member(member_id, money) values(?, ?)"; Connection con = null; PreparedStatement pstmt = null; try { con = dataSource.getConnection(); pstmt = con.prepareStatement(sql); pstmt.setString(1, member.getMemberId()); pstmt.setInt(2, member.getMoney()); pstmt.executeUpdate(); return member; } catch (SQLException e) { // h2 db의 경우 if (e.getErrorCode() == 23505) { throw new MYDuplicateKeyException(e); } throw new MyDbException(e); } finally { JdbcUtils.closeStatement(pstmt); JdbcUtils.closeConnection(con); } } }
같은 아이디를 반복 저장하는 상황이다. service에서 반복되는 아이디를 저장한다는 요청이 들어오면 random으로 인해 10000까지의 숫자를 원래 아이디 뒤에 붙여준다. 이 때 에러코드가 23505일 때 MyDuplicateKeyException(e)로 반환하여서 중복 여부를 판단할 수 있다. 이처럼 특별한 예외가 발생했을 때에는 해당 예외처리를 할 수 있는 런타임 예외를 만들어서 반환해주면 된다. 하지만 에러 코드도 db마다 다르고 모든 예외를 저렇게 처리할 수 없으니 스프링에서 지원하는 예외 추상화를 사용하는 것이 좋다.
스프링 예외 추상화
@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); SQLErrorCodeSQLExceptionTranslator exTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource); DataAccessException resultEx = exTranslator.translate("select", sql, e); // 스프링이 제공하는 SQL 예외 변환기 log.info("resultEx", resultEx); assertThat(resultEx.getClass()).isEqualTo(BadSqlGrammarException.class); } }
SQLErrorCodeSQLExceptionTranslator exTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);
DataAccessException resultEx = exTranslator.translate("select", sql, e);
위의 두 줄은 스프링이 제공하는 SQL 예외 변환기이다. 문법 오류가 발생하면 ErrorCode가 42122로 발생하는데 이러한 예외코드들을 db별로 저장해두어서 해당 예외가 발생했을 때 알맞은 예외를 반환해준다. db별로 의존하지도 않기 때문에 추후에 db를 바꿔준다해도 별도의 코드 수정을 최소화로 할 수 있다. 위의 코드는 실제 코드가 아니라 스프링 예외 추상화만을 보기 위한 테스트코드이다.
@Slf4j public class MemberRepositoryV4_2 implements MemberRepository{ private final DataSource dataSource; private final SQLExceptionTranslator exTranslator; public MemberRepositoryV4_2(DataSource dataSource) { this.dataSource = dataSource; this.exTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource); } @Override public Member save(Member member) { String sql = "insert into member(member_id, money) values (?, ?)"; // (?, ?)가 아니라 (memberId, money)라 하면 보안상의 위험 Connection con = null; PreparedStatement pstmt = null; try { con = getConnection(); pstmt = con.prepareStatement(sql); pstmt.setString(1, member.getMemberId()); pstmt.setInt(2, member.getMoney()); pstmt.executeUpdate(); return member; } catch (SQLException e) { throw exTranslator.translate("save", sql, e); } finally { close(con, pstmt, null); // 항상 close가 되도록 finally에서 호출해야 함 } }
원래 코드인 save에 exTranslator.translate 메서드를 통해 스프링 예외 추상화를 진행했다.
'스프링 > 스프링 기초DB' 카테고리의 다른 글
데이터 접근 기술 - JPA (0) 2023.03.26 db 테스트에서의 트랜잭션 (0) 2023.03.25 체크 예외, 언체크(런타임) 예외 (0) 2023.03.19 DB 트랜잭션 - 자동 커밋, 수동 커밋 / DB 락 (0) 2023.03.14 DB - 커넥션풀과 DataSource (0) 2023.03.14