스프링 JDBC 탐험하기

Spring Data Access Object(DAO)는 JDBC, Hibernate, JDO와 같은 표준 방법으로 데이터에 접근하는 기술 사용을 쉽게 해준다. 스프링 프레임워크는 JDBC 코드 중복을 줄여주는 API를 제공한다. Spring JDBC는 데이터 접근에 대한 상세한 내용을 숨기고 비즈니스 로직에 집중할 수 있게 해준다. 이로 인해 데이터베이스를 쉽고 간단하게 사용할 수 있다.

일반 JDBC 코드에서는 connection을 맺거나 SQL 구문을 실행할때 SQLException과 같은 checked 예외를 catch 해야한다. 스프링을 이용하면 스프링이 예외처리를 하기 때문에 직접 예외를 catch할 필요가 없다. 스프링은 checked 예외를 던지거나 먹어버리지 않고 uncheced 예외로 변경한다.

스프링은 확장할 수 있는 추상 DAO 클래스들을 제공한다. 추상 클래스들은 현재 사용되고 있는 기술들에 대해서 데이터 소스와 다른 설정들을 제공하는 메서드들을 가지고 있다.

DAO 지원 클래스들은 다음과 같은 것들이 있다.

  • JdbcDaoSupport
  • HibernateDaoSupport
  • JdoDaoSupport
  • JpaDaoSupport

일반적은 JDBC 코드에서 데이터베이스에 접근하기 위해서 다음과 같은 절차를 따른다.

  1. connection 매개변수 정의하기
  2. connection 열기
  3. statement 정의하기
  4. statement 준비하고 실행하기
  5. results에 대해서 루핑 구성
  6. 각 결과값에 대해서 작업 수행
  7. 예외 처리
  8. 트랜젝션 처리
  9. connection 닫기

스프링은 매우 긴 JDBC 코드 작성을 줄여준다. 우리는 다음 내용의 코드만 작성하면 된다.

  • statement 정의하기
  • 각 결과값에 대해서 작업 수행

스프링은 JDBC의 로우레벨의 귀찮은 작업들을 모두 처리해 준다.

스프링-JDBC 추상 프레임워크는 다음 4개의 패키지로 구성되어 있다.

  • org.springframework.jdbc.core
  • org.springframework.jdbc.datasource
  • org.springframework.jdbc.object
  • org.springframework.jdbc.support

org.springframework.jdbc.core 패키지는 다음을 포함하고 있다.

  • JdbcTemplate 클래스
  • 다양한 callback 인터페이스
  • 다양한 관련 클래스

org.springframework.jdbc.datasource 패키지는 다음을 포함하고 있다.

  • DataSource에 쉽게 접근하게 해주는 유틸리티 클래스
  • J2EE 컨테이너 밖에서 JDBC 코드를 수정하지 않고 테스트하고 실행시킬 수 있게 하는 간단한 DataSource 구현체들
  • JNDI로 부터 connection을 열고 닫게 해주는 정적 메서드를 제공하는 유틸리티 클래스들
  • DataSourceTransactionManager와 사용할때 필요한 스레드 범위의 connection을 제공한다.

org.springframework.jdbc.object 패키지는 다음을 포함하고 있다.

  • RDBMS 쿼리, 업데이트, 저장 프로시저를 스레드 세이프하고 재사용 가능한 개체로 제공하는 클래스들
  • 이 접근은 JDO로 모델링되어서 쿼리 결과 개체는 데이터베이스로부터 연결이 해제된 상태로 존재한다.
  • 고수준 JDBC 추상화는 org.springframework.jdbc.core 패키지의 저수준 추상화를 기반으로 한다.

org.springframework.jdbc.support 패키지는 다음을 포함하고 있다.

  • SQLException 변환 함수와 유틸리티 클래스들
  • JDBC 처리중에 발생하는 예외는 org.springframework.dao 패키지에 있는 예외로 전환된다.
  • 스프링 JDBC 추상 계층을 사용하는 코드는 JDBC나 RDBMS 특유의 에러 핸들링을 구현할 필요가 없다.
  • 전환된 예외는 모두 unchecked 예외이기 때문에 예외 처리를 호출자에게 전파시키도록 할 수도 있다.

JdbcTemplate은 org.springframework.jdbc.core 패키지의 대표 클래스이다. 이것은 자원의 생성과 해제를 알아서 처리하기 때문에 JDBC 사용을 쉽게 만들어 준다. connection을 닫지 않는것과 같은 일반적인 에러를 막아주고, statement 생성과 실행의 JDBC 핵심 작업흐름을 어플리케이션 코드와 섞이지 않고 실행되게 해준다.

전화번호를 저장하는 전화번호부 어플리케이션을 스프링 JDBC와 일반적인 JDBC를 이용해서 만들어 보겠다. 이를 통해서 스프링 JDBC의 간편함과 재사용성에 대해서 알게 될 것이다. 데이터 저장소로는 Apache Derby를 사용하겠다. Derby는 다음에서 다운로드 받을 수 있다. http://db.apache.org/derby/.

H2와 같은 보다 나은 내장 데이터베이스를 사용할 수도 있다. 이것은 Derby보다 다양한 기능과 적은 제약사항을 가지고 있다. 여기에서는 단순함을 위해서 Derby를 사용한다.

Derby를 실행하기 위한 절차는 다음과 같다.

  1. 바이너리 파일을 다운로드 받아서 압축을 해제한다. 압축을 해제한 경로를 DERBY_HOME으로 설정한다.
  2. 윈도우 PC에서는 DERBY_HOME\bin 폴더로 가서 startNetworkServer.bat을 실행한다.
  3. 커맨드 창이 열리고 데이터베이스가 시작됐다는 다음과 같은 메시지가 출력될 것이다. started and ready to accept connections on port 1527.

스프링 JDBC 최신 버전 JAR와 다음에 있는 의존성 있는 라이브러리들을 다운로드 받는다. http://maven.springframework.org/release/org/springframework/spring/.

스프링 JDBC 구현하고 코드를 단순화하기 위해 다음 절차를 수행한다.

  1. 이클립스를 실행하고 DatabaseAccess라는 이름으로 Java 프로젝트를 생성한다.
  2. 전화번호 내역을 저장하기 위한 PhoneEntry 클래스를 추가한다. 클래스의 내용은 다음과 같다.
    package com.packt.database.model;
     public class PhoneEntry implements Serializable {
          private static final long serialVersionUID = 1L;
          private String phoneNumber;
          private String firstName;
          private String lastName;
          // getters and setters
     }
    
  3. 전화번호부에 대한 데이터 접근 인터페이스를 생성한다. API는 다음과 같다.
    package com.packt.database.dao;
    import java.util.List;
    import com.packt.database.model.PhoneEntry;
     public interface PhoneBookDao { 
         boolean create(PhoneEntry entry);
         boolean update(PhoneEntry entryToUpdate);
         List<PhoneEntry> searchByNumber(String number);
         List<PhoneEntry> searchByFirstName(String firstName);
         List<PhoneEntry> searchByLastName(String lastName);
         boolean delete(String number);
     }
    
  4. 스프링 의존성 라이브러리들을 추가하기 위해 .classpath를 다음과 같이 수정한다.
  5. 데이터베이스와 통신하기 위한 데이터베이스 접근 인터페이스를 구현한다. 데이터 접근 객체는 다음과 같이 구현한다.

    public class PhoneBookDerbyDao implements PhoneBookDao {
      private String driver =
        "org.apache.derby.jdbc.EmbeddedDriver";
      private String protocol = "jdbc:derby:";
      private String userId = "dbo";
      private String dbName = "phoneBook";
      public PhoneBookDerbyDao() {
        loadDriver();
      }
    
     protected void loadDriver() { 
         try {
              Class.forName(driver).newInstance();
            } catch (ClassNotFoundException cnfe) {
              cnfe.printStackTrace(System.err);
            } catch (InstantiationException ie) {
              ie.printStackTrace(System.err);
            } catch (IllegalAccessException iae) {
              iae.printStackTrace(System.err);
            }
     }
    
     protected Connection getConnection() throws SQLException { 
         Connection conn = null;
         Properties props = new Properties();
         props.put("user", userId);
         conn = DriverManager.getConnection(protocol + dbName +
                  ";create=true",props);
                conn.setAutoCommit(false);
                return conn;
      }
    }
    

    PhoneBookDerbyDao는 derby에 대한 DAO 구현체이다. 이것은 driver, protocol, dbName과 같은 설정 속성과 getter, setter를 가지고 있다. loadDriver() 메서드는 데이터베이스 드라이버를 읽어들인다. 이는 PhoneBookDerbyDao 생성자에서 호출된다. getConnection() 메서드는 Derby 데이터베이스에 연결하고 connection을 취득한다.

  6. create 메서드의 행위를 구현한다.
    @Override
    public boolean create(PhoneEntry entry) {
     PreparedStatement preparedStmt = null;
     Connection conn = null;
     try {
        conn = getConnection();
        preparedStmt = conn
             .prepareStatement("insert into PhoneBook values (?,?,?)");
        preparedStmt.setString(1, entry.getPhoneNumber());
        preparedStmt.setString(2, entry.getFirstName());
        preparedStmt.setString(3, entry.getLastName());
        preparedStmt.executeUpdate();
        // Note that it can cause problems on some dbs if
        //autocommit mode is on
        conn.commit();
        return true;
     } catch (SQLException e) {
         e.printStackTrace();
     } finally {
         if (preparedStmt != null) {
             try {
                 preparedStmt.close();
             } catch (SQLException e) {
                 e.printStackTrace();
             }
         }
         if (conn != null) {
             try {
                 conn.close();
             } catch (SQLException e) {
                 e.printStackTrace();
             } 
         }
     }
     return false;
    }
    
    create 메서드는 데이터베이스 연결을 취득하고 connection으로부터 prepared statement를 생성한다. 다음에 prepared statement에 PhoneEntry의 값들을 넣어준다. prepared statement를 실행하고 commit을 수행한다. finally 구문에서 prepared statement, connection 자원을 닫아준다.
  7. PhoneBookDerbySpringDao 이름으로 PhoneBookDao 인터페이스에 대해서 구현한다. create 메서드를 다음과 같이 스프링 기반으로 구현한다.

    public class PhoneBookDerbySpringDao  implements
         PhoneBookDao {
     private final JdbcTemplate jdbcTemplate;
     public PhoneBookDerbySpringDao(JdbcTemplate jdbcTemplate) {     
         this.jdbcTemplate = jdbcTemplate;
     }
    
     @Override
     public boolean create(PhoneEntry entry) {
         int rowCount = jdbcTemplate.update("insert into PhoneBook values (?,?,?)",
                             new Object[]{entry.getPhoneNumber(),
                             entry.getFirstName(),
                             entry.getLastName()
                         });
         return rowCount == 1;
     }
    }
    

    JdbcTemplate 클래스는 JDBC 사용을 단순화해준다. 이것은 리소스들을 관리해서 connection을 닫지 않는 것과 같은 일반적인 실수를 방지해준다. statement 객체를 생성하고 값을 넣어주고, ResultSet을 통해서 결과값에 순환하여 접근하도록 해주어 어플리케이션 코드와 섞이지 않도록 해준다. PhoneBookDerbySpringDao는 JdbcTemplate를 가지고 있어서 데이터베이스 작업을 JdbcTemplate에 위임시키고 있다. JdbcTemplate는 applicationContext에 정의된 데이터 소스를 사용한다. JdbcTemplate는 insert와 update를 처리하기 위한 update 메서드를 가지고 있다. 이것은 SQL 쿼리와 매개변수를 취한다. 스프링 버전의 create() 메서드는 jbbcTemplate의 update() 메서드를 호출하며 PhoneEntry의 상세내용을 전달한다. create 메서드가 단지 2줄의 코드로 매우 단순해졌다. 스프링 프레임워크가 자원의 생명주기를 관리한다. 54줄밖에 되지 않는 스프링 DAO 클래스를 보라. 깔끔하고 단순하고 가독성이 좋은 클래스이다. 자원을 관리하지 않고 데이터 접근 자체에 집중하고 있다.