[Spring] 스프링이 DB 기술에 따라 예외를 제공하는 방법
웹 프로젝트를 진행하다 보면 어떤 기술을 사용하냐에 따라 DAO 클래스가 의존하는 DB기술 에 따른 예외 클래스가 존재하게 된다.
스프링에서 제공하는 예외 추상화 기술을 이용하지 않을 때 발생하는 문제점들을 먼저 알아보고, 이를 해결하기 위해 스프링에서 예외 클래스를 어떻게 추상화하여 개발자에게 제공하는지 알아보자
다양한 데이터 접근기술이 존재하는데 JDBC, JPA, H2, Hibernate등등 고유의 예외 클래스가 존재하는데, 이러한 예외를 추상화 하지 않으면 각 기술에 맞는 예외를 개별적으로 처리해야하며, 이는 코드의 복잡성을 증가시킨다.
JDBC, JPA 예외를 개별적으로 처리하는 코드 예제로 알아보기
문제점 1 다양한 기술에 따른 예외 처리의 복잡성 증가
import java.sql.SQLException;
import javax.persistence.PersistenceException;
public class UserService {
public void createUserJdbc(String username, String password) {
try {
// JDBC 코드
} catch (SQLException e) {
// JDBC 예외 처리
e.printStackTrace();
throw new RuntimeException("JDBC 에러 발생", e);
}
}
public void createUserJpa(String username, String password) {
try {
// JPA 코드
} catch (PersistenceException e) {
// JPA 예외 처리
e.printStackTrace();
throw new RuntimeException("JPA 에러 발생", e);
}
}
}
위의 예제코드에서는 2가지 기술 사용에 따른 예외처리를 각자 해줘야 된다. 즉 다양한 예외 처리의 복잡성을 가지게 되는 것이다. 이는 기술 변경시에도 파급효과를 불러일으킨다.
문제점 2 코드 중복 증가
public class UserService {
public void createUserJdbc(String username, String password) {
try {
// JDBC 코드
} catch (SQLException e) {
// 동일한 예외 처리 로직
e.printStackTrace();
throw new RuntimeException("JDBC 에러 발생", e);
}
}
public void createUserJpa(String username, String password) {
try {
// JPA 코드
} catch (PersistenceException e) {
// 동일한 예외 처리 로직
e.printStackTrace();
throw new RuntimeException("JPA 에러 발생", e);
}
}
}
각 데이터 접근 기술마다 예외 처리를 별도로 작성해야 하므로, 중복 코드가 증가하게 된다.
문제점 3 서비스 계층의 비즈니스 로직과 예외 처리로직의 혼합
public class UserService {
public void createUser(String username, String password) {
try {
// 비즈니스 로직
// JDBC 또는 JPA 코드
} catch (SQLException e) {
// 예외 처리 로직
e.printStackTrace();
throw new RuntimeException("JDBC 에러 발생", e);
} catch (PersistenceException e) {
// 예외 처리 로직
e.printStackTrace();
throw new RuntimeException("JPA 에러 발생", e);
}
}
}
예외를 추상화하지 않으면 비즈니스 로직과 예외 처리 로직이 혼합되어 코드의 가독성이 떨어진다.
이는 코드의 유지보수성을 떨어뜨리고, 로직에 오류가 발생하거나 버그를 발생시키기 쉬운 코드 형태가 되어버린다.
이러한 예외 추상화 기능이 제공되지 않는다면 다양한 예외처리의 복잡성, 코드의 중복, 비즈니스 로직과 예외처리에 대한 로직의 혼합의 문제점들이 발생하게 될 것이다.
스프링이 제공하는 예외 추상화 기능 사용하기
위의 문제점들을 보완하고자 스프링에서는 Template을 제공한다.(JdbcTemplate, EntityManager)
즉 다양한 데이터 접근 기술에 대한 일관된 예외 처리를 보장하고(스프링은 CheckedException을 UncheckedException으로 변환하여 예외 처리를 단순화해줌), 가독성도 상승한다.
먼저 인터페이스를 먼저 정의해준다.
Jdbc
// UserDAO.java
import org.springframework.dao.DataAccessException;
public interface UserDAO {
void createUser(String username, String password) throws DataAccessException;
}
인터페이스를 상속받은 DAO클래스
// UserDAOImpl.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.dao.DataAccessException;
@Repository
public class UserDAOImpl implements UserDAO {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void createUser(String username, String password) throws DataAccessException {
String sql = "INSERT INTO users (username, password) VALUES (?, ?)";
jdbcTemplate.update(sql, username, password);
}
}
UserService
클래스는 UserDAO
를 사용하여 비즈니스 로직을 처리한다.
// UserService.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.dao.DataAccessException;
@Service
public class UserService {
@Autowired
private UserDAO userDAO;
@Transactional
public void createUser(String username, String password) {
try {
userDAO.createUser(username, password);
} catch (DataAccessException e) {
// 스프링의 DataAccessException을 사용한 예외 처리
e.printStackTrace();
throw new RuntimeException("Database error occurred", e);
}
}
}
현재 예제 코드에서는 try-catch로 예외를 잡아줬지만, (DataAccesException은 스프링의 예외 추상화 계층에서 최상위 계층에 속한다.) RuntimeException이기 때문에 무시해도 되는 예외라면 예외를 잡아주지 않아도 된다.
서비스 계층의 순수성
위의 작업을 통해 트랜잭션 추상화 + 트랜잭션 AOP 덕분에 서비스 계층의 순수성을 최대한 유지하면서 서비스 계층에서 트랜잭션을 사용할 수 있다.
스프링이 제공하는 예외 추상화와 예외 변환기 덕분에, 데이터 접근 기술이 변경되어도 서비스 계층의 순수성을 유지하면서 예외도 사용할 수 있다.
서비스 계층이 리포지토리 인터페이스에 의존한 덕분에 향후 리포지토리가 다른 구현 기술로 변경되어도 서비스 계층을 순수하게 유지할 수 있다.
DAO클래스에서 JDBC를 사용하는 반복 코드가 JdbcTemplate
으로 대부분 제거되었다.