- jpa
- join
- 1차원 배열
- 스프링
- ORM
- @transactional
- 스프링부트
- 데이터베이스
- DI
- sql
- mysql
- Docker
- springboot
- spring boot
- session
- 문자열
- AWS
- SSL
- Django
- spring security 6
- spring
- Git
- 자바
- 프로그래머스
- PYTHON
- select
- java
- spring mvc
- nginx
- string
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
개발하는 자몽
스프링 예외 추상화 본문
이 글은 인프런 김영한 님의 강의를 바탕으로 작성한 글입니다.
Service 계층은 비즈니스 로직에만 집중해야 한다. Repository에서 발생한 DB 오류로 인해 Service 단에서 DB 관련 의존관계를 갖지 않도록 체크 예외를 언체크 예외(런타임 예외)로 변환하여 던졌다.
체크 예외를 런타임 예외로 변환하기 위해 개발자가 일일이 처리하기에는 데이터 접근 계층이 수백 개인 경우 큰 문제가 된다. 이 부분을 스프링이 해결해 준다.
스프링 데이터 접근 예외 계층
스프링은 데이터 접근 예외 계층을 추상화해서 제공해 준다. 개발자는 해당 예외 계층을 사용하면 된다.
스프링은 데이터 접근 계층에 대한 수많은 예외를 정리해서 일관된 예외 계층을 제공해준다. 또한 스프링이 제공하는 예외는 순수 특정 기술에 종속적이지 않으므로 서비스 계층에서 스프링이 제공하는 예외를 사용했을 때, DB 접근 기술에 대한 종속성 문제는 해결된다. (스프링에는 종속적이지만 우리는 개발할 때 스프링 프레임워크를 쓰기 때문에 OK)
스프링은 데이터 접근 예외 계층의 최상위 클래스는 DataAccessException이다. 해당 예외는 RuntimeException을 상속받았으며, 따라서 스프링이 제공하는 모든 데이터 접근 계층 예외는 런타임 예외다.
DataAccessException 분류
- NonTransientDataAccessException
- Non 일시적 예외
- 애플리케이션 단에서는 해결될 방법이 없을 가능성이 높음
- 개발자가 의도적으로 개입해서 수정이 필요한 예외
- Ex. 잘못된 SQL을 반복해도 실행이 안 되는 건 당연함
- TransientDataAccessException
- 일시적 예외
- (동일한 SQL을 다시 시도했을 때) 해당 에러가 발생하고 시간이 흐른 뒤 다시 시도하면, 성공할 가능성이 있음
- Ex. 쿼리 타임아웃, 락과 관련된 오류 → DB 상태가 좋아지거나, Lock이 풀렸을 때 다시 시도하면 성공
스프링이 제공하는 예외 변환기
스프링은 DB에서 발생하는 예외를 ErrorCode를 바탕으로 스프링이 제공하는 DB 계층 예외로 변환해 주는 예외 변환기를 제공한다. 예외 변환기를 사용하면 만들어지는 예외 모두 특정 DB 기술에 종속적이지 않기 때문에 DI를 유지하면서 개발 생산성도 높일 수 있다.
//스프링이 제공하는 SQL 예외 변환기는 다음과 같이 사용하면 된다.
SQLExceptionTranslator exTranslator = new SQLErrorCodeExceptionTranslator(dataSource);
DataAccessException resultEx = exTranslator.translate("select", sql, e);
translate() 메서드의 파라미터
- Task - 첫 번째 : 읽을 수 있는 설명. 로그에 표시되는 용도
- SQL - 두 번째 : DB에서 실행한 SQL
- Exception - 마지막 : Catch문에서 잡은 SQLException
스프링 - 모든 DB의 예외 변환 - 설정 파일
DB의 SQL ErrorCode는 모두 다른데, 스프링은 각각의 DB가 제공하는 SQL ErrorCode를 고려하여 예외를 변환한다. 스프링 SQL 예외 변환기는 sql-error-codes.xml 설정 파일에 SQL ErrorCode를 대입하여 어떤 스프링 데이터 접근 예외로 전환할지 찾아낸다. 예를 들어 H2 데이터베이스에서 42000이 발생하면 badSqlGrammarCodes이기 때문에 BadSqlGrammarException을 반환한다.
SpringExceptionTranslator 테스트 코드
@Slf4j
public class SpringExceptionTranslatorTest {
DataSource dataSource;
@BeforeEach
void init() {
dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
}
@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);
//BadSqlGrammarException
DataAccessException resultEx = exTranslator.translate("select", sql, e);
log.info("resultEx", resultEx);
assertThat(resultEx.getClass()).isEqualTo(BadSqlGrammarException.class);
}
}
}
SQLExceptionTranslator를 만들고 여기에 발생한 Exception과 실행한 SQL을 넘겨주면, 어떤 이유로 Exception이 발생했는지 분석하여 Spring이 제공하는 DB계층으로 변경하여 로그에 표시해 준다.
select bad grammar [42122-214]]; SQL was [select bad grammar] for task [select] 13:50:37.001 [main] INFO hello.jdbc.exception.translator.SpringExceptionTranslatorTest - resultEx org.springframework.jdbc.BadSqlGrammarException: select; bad SQL grammar [select bad grammar]; nested exception is org.h2.jdbc.JdbcSQLSyntaxErrorException: Column "BAD" not found; SQL statement: select bad grammar [42122-214]
실행 결과 Exception이 BadGrammarException으로 변경된 것을 확인할 수 있다. 또한 위에서 언급한 것처럼 첫 줄에 for task [select] 부분은 translate() 메서드의 첫 번째 파라미터를 보여주고 있다.
SpringExceptionTrnaslator - 실제 코드 적용
@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 (?, ?)";
//Connectino 설정
try {
//Connection 연결 및 쿼리 실행
} catch (SQLException e) {
throw exTranslator.translate("save", sql, e);
} finally {
// Connnection close
}
}
...
}
앞서 말한 데로 SpringExceptionTranslator가 만들어주는 예외는 런타임 예외이기 때문에 Service 계층에서는 해당 예외를 체크하지 않아도 된다.
Summary
- 스프링은 데이터 접근 계층에 대한 일관된 예외 추상화를 제공한다.
- 스프링은 예외 변환기를 통해서 SQLException의 ErrorCode에 맞는 적절한 스프링 데이터 접근 예외로 변환해 준다.
- 스프링 예외 추상화 덕분에 특정 기술에 종속적이지 않게 되었지만 스프링이 제공하는 예외를 사용하기 때문에 스프링에 대한 기술 종속성은 발생한다.
- 스프링에 대한 기술 종속성까지 완전히 제거하려면 예외를 모두 직접 정의하고 예외 변환도 직접 해야 하므로 실용적이지 않다.
- 결과적으로 서비스 계층은 특정 구현 기술이 변경되어도 그대로 유지할 수 있게 되었다. DI를 제대로 활용할 수 있게 된 것이다.
- 문제 마지막으로 Repository 계층에서 JDBC를 사용하기 때문에 발생하는 반복 문제가 남아있다.
'Java > Spring' 카테고리의 다른 글
[JPA] Hibernate의 데이터베이스 초기화 전략 (0) | 2022.12.21 |
---|---|
[Spring]JdbcTemplate (0) | 2022.09.30 |
@SpringBootTest, @TestConfiguration (0) | 2022.09.24 |
[트랜잭션] @Transactional AOP (0) | 2022.09.23 |
[Spring MVC] Model, @ModelAttribute (1) | 2022.09.10 |