728x90

4.1에서 예외의 전환 이유는 보통 2가지라고 말했었다.

하나는 예외를 더 구체적이고 의미 있도록 바꿔주는 것이고, 다른 하나는 런타임 예외로 바꾸어 catch를 줄여주는 것이었다.

 

JdbcTemplate은 SQLException을 DataAccessException등으로 바꿔줬었다.

여기에서도 예외를 더 구체적으로 알 수 있게 했으며, 런타임 예외로 바꾸던 것이다.

 

JDBC의 한계

JDBC는 데이터베이스에 접근하여 데이터를 가져오고 수정할 수 있기에 자바의 가장 많이 사용되는 API 중 하나라고 한다.

 

//MSSQL
SELECT TOP 5 * FROM USERS;	

//MYSQL
SELECT * FROM USERS LIMIT 5;

 

하지만 이렇게 SQL 중에서도 Database마다 다른 문법이 존재한다. 그럼 우리가 만든 UserDao는 MySQL이라는 데이터베이스에 종속되어 버리며, 다른 데이터베이스로 전환은 거의 불가능해진다. 그리고 여기서 발생하는 SQLException의 에러 정보도 DB마다 다르다.

if(e.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY)

이런 코드가 있다면 MySQL 전용코드가 되어버리기에 여기에서도 MySQL에 종속되는 것이다. 만약 데이터베이스가 MySQL에서 MSSQL로 바뀐다면 현재 사용하는 query도 사용하지 못하고, 에러코드도 제대로 동작하지 못하는 것이다.

 

그렇기에 SQLException은 예외가 발생한 경우 DB의 상태 정보를 가져올 수 있도록 한다. getSQLState() 메서드를 통해 상태정보를 가져올 수 있도록 한다. 이때는 DB별로 다른 에러 코드를 통합하기 위해, Open Group의 XOPEN SQL 스펙의 SQL 상태코드를 따르도록 한다고 한다.

 

DB 에러 코드 매핑을 통한 전환

사용하는 DB를 바꾸더라도, 에러코드를 바꾸지 않으려면 이런 에러코드를 원하는 에러로 가져올 수 있도록 해야한다. 그렇기에 DB별 에러 코드를 참고해서 원하는 예외로 바꾸어야 하는 것이다.

 

오라클에서 PK 중복으로 발생하는 에러코드는 1이다.

이 에러코드를 duplicateKeyCodes로 변경해야 하는 것이다.

 

그렇기에 이런 에러 코드를 스프링의 예외 클래스로 매핑할 수 있도록 xml 파일을 작성할 수 있다.

<bean id="Oracle" class="org.springframework.jdbc.support.SQLErrorCodes">
	<property name="duplicateKeyCodes">
    	<value>1</value>
    </property>
</bean>

 

이렇게 작성해서 매핑한다.

 

JDBCTemplate 자체가 다 런타임 예외라서 예외를 많이 신경쓰지 않지만, 그럼에도 처리하고 싶은 예외가 있다면 이렇게 예외를 가져와서 처리할 수 있는 것이다.

 

DAO 인터페이스와 DataAccessException 계층구조

이렇게 Dao를 만들어서 사용하는 이유가 뭘까?

데이터베이스에 접근하는 로직을 분리하기 위해서다. 데이터 액세스 기술을 신경쓰지 않고, 그냥 단순히 Dao를 가져가서 사용하도록 만드는 것이다.

그렇기에 UserDao 인터페이스를 만들고, DI를 통해 주입받아서 사용하며 클라이언트에게 감추며 만드는 것이다.

public interface UserDao{
	public void add(User user);
}

 

이렇게 인터페이스를 만들 수 있을까?

지금은 JDBC를 사용하기에 별다른 예외가 발생하지 않아 가능하지만, 위 인터페이스는 구현 메서드에서 에러가 발생하기라도 하면 사용할 수 없는 인터페이스이다.

데이터 엑세스 기술에 따라 각각 다른 예외를 던지기 때문이다. 그렇다고 해서 모두를 포괄하는 Exception을 throws 할 수도 없을 것이다.

 

그렇기에 위의 인터페이스를 사용하려면 구현 메서드에서 체크 예외가 아닌 모두 런타임 예외를 사용해야 한다. 만약 체크 예외가 발생한다면 런타임 예외로 전환해주어야 할 것이다.

 

그럼 이제 add(User user) 메서드를 사용하면 체크 예외를 처리할 필요가 없을텐데, 그러면 모든 예외를 무시해도 되는가?

당연히 아닐 것이다.

JdbcTemplate은 에러가 발생하면 그에 맞는 DataAccessException의 서브 클래스로 바꿔준다. 그렇기에 그 계층 구조를 파악하고 있다가 처리 가능한 예외는 시도해 볼 수 있는 것이다.

 

DataAccessException의 서브 클래스들을 어느정도는 파악하고 있다가, 처리 가능한 예외는 처리해보도록 하자.

 

기술에 독립적인 UserDao 만들기

지금까지 개발했던 UserDao를 인터페이스랑 분리래보자.(사실 원래 이렇게 했어야 하는데...)

public interface UserDao {

    void add(User user);
    User get(String id);
    List<User> getAll();
    void deleteAll();
    int getCount();
}

 

public class UserDaoImpl implements UserDao {
}

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="dataSource"
          class = "org.springframework.jdbc.datasource.SimpleDriverDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test"/>
        <property name="username" value="root"/>
        <property name="password" value="1204"/>
    </bean>

    <bean id="jdbcContext"
          class = "seungkyu.JdbcContext">
        <constructor-arg ref="dataSource"/>
    </bean>

    <bean id="userDao" class="seungkyu.UserDaoImpl">
        <constructor-arg ref="dataSource" />
    </bean>

</beans>
    @BeforeEach
    public void setUp() {
        this.userDao = applicationContext.getBean("userDao", UserDao.class);
        userDao.deleteAll();
    }

 

마지막 코드는 테스트 클래스의 일부인데, 여기서도 UserDaoImpl이 아닌 UserDao로 가져오는 것을 볼 수 있다.

빈의 이름은 인터페이스로 만들고, 그 구현체를 등록하는 방법을 사용해야 의존성이 낮아지기 때문이다.

이렇게 의존하고 있어야 나중에 편하게 구현체를 변경 가능하다.

 

그대로 테스트를 수행해보니

성공적으로 통과하는 것을 볼 수 있다.

 

이렇게 JDBC에서 발생하는 에러도 꼼꼼하게 테스트하면서 개발 할 수 있도록 하자.

+ Recent posts