4.2.1 JDBC의 한계
- JDBC 표준 인터페이스를 통해 DB 종류에 상관없이 일관된 방법으로 프로그램을 개발할 수 있음
- 하지만, DB 종류에 상관없이 사용할 수 있는 데이터 엑세스 코드를 작성하는 일은 쉽지 않음
- 또한, 유연한 코드를 보장하지 못함
- 두 가지 걸림돌이 존재
- 비표준 SQL
- 호환성 없는 SQLException의 DB 에러 정보
4.2.2 비표준 SQL
- SQL은 어느정도 표준화된 언어이고, 몇가지 표준이 존재
- 하지만, 비표준 문법은 최적화된 SQL 및 DB의 특별한 기능을 제공하기 위해 사용됨
- DB는 자주 변경되지 않으므로 대부분의 DB는 비표준 SQL을 지원하고 있음
- 해결책
- 호환 가능한 표준 SQL만 사용
- 표준 SQL만 사용하면 페이징 쿼리에서 부터 문제가 됨. 현실성 없음
- DB별 별도의 DAO를 만들거나 SQL을 외부에 독립시켜 DB에 따라 변경해 사용
- 💡 7장에서 자세히 다룰 것임
- 호환 가능한 표준 SQL만 사용
4.2.3 호환성 없는 SQLException의 DB 에러 정보
- SQLException 발생 원인은 수백여 가지 존재
- 또한, DB 마다 에러의 종류와 원인이 제각각임
- JDBC API는 SQLException 한 가지만 던지도록 되어 있어 안에 담긴 에러 코드와 상태정보를 참조해야 함
- 에러코드는 getErrorCode()를 통해 가져올 수 있음
- 에러코드는 DB 마다 달라지므로 DB마다 처리를 다르게 해 주어야 함
- 때문에, getSQLState() 상태정보를 가져올 수 있음
- DB별로 달라지는 에러 코드를 대신할 수 있도록, Open Group의 XOPEN SQL 스펙에 정의된 상태 코드를 따르도록 되어 있음
- 하지만, JDBC 드라이버에서 SQLException의 상태정보를 정확히 만들어주지 않음
- 때문에, SQL 상태 코드를 믿고 결과를 파악하는 것은 매우 위험함
// DB마다 다르게 처리
if(e.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY){...}
💡 SQLException만으로 DB에 독립적인 유연한 코드를 작성하는 것은 불가능에 가까움
4.2.4 DB 에러 코드 매핑을 통한 전환
- SQLException의 상태정보는 신뢰할 수 없으므로 고려하지 않음
- 차라리, DB 전용 에러 코드가 더 정확한 정보
- DB 종류에 상관없이 동일 상황(중복키 존재 예외, ...)에서 일관된 예외를 얻을 수 있으면 효과적인 대응 가능
- 스프링은 예외의 효과적 대응을 위해 DataAccessException을 제공하고 있음
- DataAccessException는 SQLException을 대체할 수 있는 런타임 예외이고, 세분화된 서브 클래스를 정의하고 있음
- DB마다 에러 코드가 제각각이기 때문에, 스프링은 DB 별로 에러 코드를 분류해서 스프링이 정의한 예외 클래스와 매핑해놓은 에러 코드 매핑정보 테이블을 만들고 이용함
- JdbcTemplate는 SQLException을 런타임 예외인 DataAccessException로 포장할 뿐만이 아니라, DB의 에러 코드를 DataAccessException 계층구조 클래스 중 하나로 매핑함
- JdbcTemplate가 던지는 예외는 모두 DataAccessException의 서브클래스 타입임
- 드라이버나 DB 메타 정보를 참고하여 DB 종류를 확인하고, 매핑 정보를 참고해서 적절한 예외 클래스를 선택하기 때문에 DB에 상관 없이 같은 종류의 에러면 동일한 예외를 받음
4.2.5 중복 키로 발생하는 예외
- JdbcTemplate는 체크 예외인 SQLException을 런타임 예외인 DataAccessException로 변경함
- 때문에, 별다른 예외 포장을 하지 않아도 됨
- 또한, 중복 키로 발생하는 예외는 DataAccessException의 서브 클래스인 DuplicateKeyException으로 매핑돼서 던져짐
- DuplicateKeyException는 DB별로 미리 준비된 에러 코드와 비교 및 매핑 된것임
- 중복 키는 충분히 대응 가능한 문제이기 때문에 DuplicateUserIdException 체크 예외로 변환해도 됨
public void add() throws DuplicateUserIdException {
try{
... //jdbcTemplate 코드
} catch(DuplicateKeyException e){
throw new DuplicateUserIdException(e);
}
}
- JDBC 4.0 부터 SQLException을 DataAccessException처럼 세분화 하고 있음
- 하지만, 여전히 SQLException은 체크 예외라는 문제가 있음
- 때문에, DataAccessException를 사용하는 것이 이상적 방법임
4.2.6 DAO 인터페이스
- DAO를 만들어서 사용하는 이유는 관심사 분리와 클라이언트에게 구체적 방법을 몰라도 사용할 수 있게 하기 위함임
- JDBC 외에 JPA, JDO 등과 같은 테이터 액세스 기술들이 존재
- 각 기술들은 서로 다른 예외를 발생함
- 때문에 메소드 선언에 나타나는 예외정보가 달라 문제가 됨
- throw Exception을 하는 것은 무책임함
- DataAccessException는 기술에 관계없이 일관된 예외가 발생하도록 함
- 또한, 런타임 예외이기 때문에 메소드 선언에 예외 던짐을 하지 않아도 됨
public void add(User user) throws PersistentException; //JPA
public void add(User user) throws HibernateException; //Hibernate
public void add(User user) throws JdoException; //JDO
public void add(User user);
- 하지만, 단순히 체크 예외를 런타임 예외로 변경하는 것은 DAO를 인터페이스로 만들기 부족함
- 메소드 선언에 예외가 없다고 하더라도, 예외 발생시 DAO를 호출한 클라이언트가 DB에 맞춰 예외를 처리해야 하기 때문에 DB 기술에 의존적일 수 밖에 없음
4.2.7 DataAccessException 계층구조
- 스프링은 다양한 데이터 액세스 기술을 사용할 때 발생하는 예외를 추상화해서 DataAccessException 계층구조 안에 정리했음
- JdbcTemplate는 SQLException의 에러 코드를 DB 별로 매핑해 그에 해당하는 의미 있는 DataAccessException의 서브클래스 중 하나로 전환해서 던짐
- DataAccessException는 데이터 액세스 기술에서 발생하는 대부분의 예외를 추상화 하고 있음
- 다른 종류의 예외 및 낙관적인 락킹이 발생을 해도 적절한 예외로 전환해서 던짐
- 낙관적 락킹(Optimistic Locking)
- 두 명 이상의 사용자가 동시에 조회하고 순차적으로 업데이트를 할 때, 뒤늦게 업데이트 한 것이 먼저 업데이터를 한 것을 덮어쓰지 않도록 하는 기능
- 낙관적 락킹(Optimistic Locking)
- JDBC는 낙관적인 락킹이 존재하지 않음
- 낙관적 락킹을 위해 DataAccessException의 계층 구조에 맞게 예외를 추가할 수 있음
- 기술마다 낙관적 락킹 예외 발생 이 다르지만, 해당 예외는 OptimisticLockingFailureException를 상속하기 때문에 해당 예외 클래스를 상속받으면 됨
4.2.8 UserDao에 인터페이스 적용
- 클라이언트에서 필요한 것을 추출
- setDataSource()는 UserDao 구현 방법(JDBC)에 따라 변경될 수 있는 메소드이고, 클라이언트가 알 필요가 없기 때문에 추출하지 않음
- @Autowired는 스프링 컨텍스트 내에서 정의된 빈 중에서 인스턴스 변수에 주입 가능한 타입을 찾아 빈을 찾음
- UserDaoJdbc는 UserDao의 상속을 받기 때문에 굳이 변경하지 않아도 됨
- 만약 테스트가 UserDao의 구현 내용에 관심이 있으면 변경을 해야 함
- 인터페이스와 구현을 분리함으로써 DI가 적용 되었음
public interface UserDao {
void add(User user);
User get(String id);
List<User> getAll();
void deleteAll();
int getCount();
}
public class UserDaoJdbc implements UserDao{
...
}
public class UserDaoTest{
@Autowired
private UserDao dao;
...
}
<bean id="userDao" class="com.ksb.spring.UserDaoJdbc">
<property name="dataSource" ref="dataSource"/>
</bean>
4.2.9 중복키 예외 테스트
- 데이터 액세스 예외 학습 테스트임
- 같은 사용자를 연속으로 등록 했을 때 발생하는 예외를 확인하는 테스트
- DataAccessException의 예외가 던저져야 함
- 구체적으로 DuplicatekeyException이 던저질 것임
public class UserDaoTest {
...
@Test(expected = DataAccessException.class)
// @Test(expected = DuplicatekeyException.class)
public void duplicateKey(){
dao.deleteAll();
dao.add(user1);
dao.add(user1);
}
}
4.2.10 DataAccessException 활용 시 주의사항
- DuplicatekeyException는 JDBC에서만 발생함
- 나머지 기술들에서 예외가 발생하면 스프링이 최종적으로 DataAccessException로 변환함
- 이유는 JDBC를 제외한 나머지 기술들은 세분화가 되지 않았기 때문임
- 예를들어, 하이버네이트에서 중복 키 예외 시 ConstraintViolationException 발생 함
- 해당 예외를 스프링이 포괄적인 DataIntegrityViolationException로 변환함
- 물론 DuplicateKeyException도 DataIntegrityViolationException의 한 종류임
- 하지만, DataIntegrityViolationException는 제약조건 위반 예외 상황에서도 발생하기 때문에 DuplicateKeyException보다 효용성이 떨어짐
- DuplicatekeyException를 잡아서 처리하는 코드를 만드려면 미리 학습 테스트를 만들어 실제 전환되는 예외를 확인해서 적절히 처리해 줘야 함
4.2.11 스프링에서 SQLException을 DataAcceessException으로 전환하는 방법
- 여러 방법이 있음
- DB 에러 코드를 이용하는 것이 효과적임
- SQLException을 코드에서 직접 전환하고 싶으면 SQLExceptionTranslator 인터페이스를 구현한 SQLErrorCodeSQLExceptionTranslator를 사용하면 됨
- SQLErrorCodeSQLExceptionTranslator는 DataSource를 필요로 함
- translate()는 SQLException을 DataAccessException 타입의 예외로 변환함
//왜 안되지?
public class UserDaoTest {
@Autowired
UserDao dao;
@Autowired
DataSource dataSource;
...
@Test
public void sqlExceptionTranslate() {
dao.deleteAll();
dao.add(user1);
dao.add(user1);
try {
dao.add(user1);
dao.add(user1);
} catch (DuplicateKeyException ex) {
SQLException sqlEx = (SQLException) ex.getRootCause();
SQLExceptionTranslator set =
new SQLErrorCodeSQLExceptionTranslator(this.dataSource);
assertThat(set.translate(null, null, sqlEx),
is(DuplicateKeyException.class));
}
}
}
'토비의 스프링 정리' 카테고리의 다른 글
토비의 스프링 - 5.1 서비스 추상화 (0) | 2022.10.03 |
---|---|
토비의 스프링 - 4.3 4장 정리 (0) | 2022.10.03 |
토비의 스프링 - 4.1 사라진 SQLException (0) | 2022.10.02 |
토비의 스프링 - 3.7 3장 정리 (0) | 2022.09.30 |
토비의 스프링 - 3.6 스프링의 JdbcTemplate (0) | 2022.09.30 |