Apache Commons DbUtils를 사용한 CRUD 작업

Apache Commons DbUtils는 JDBC 코딩의 부담을 크게 줄여주는 Apache 재단에서 제공하는 오픈 소스 JDBC 유틸리티 라이브러리입니다. 이 라이브러리는 JDBC API를 단순화하여 개발 생산성을 향상시키면서도 성능 저하 없이 사용할 수 있습니다.

주요 API는 다음과 같습니다:

  • org.apache.commons.dbutils.QueryRunner: SQL 쿼리 실행을 단순화합니다.
  • org.apache.commons.dbutils.ResultSetHandler: ResultSet을 다양한 객체 형태로 변환하는 인터페이스입니다.
  • org.apache.commons.dbutils.DbUtils: JDBC 리소스(Connection, Statement, ResultSet) 관리를 위한 유틸리티 클래스입니다.

주요 API 사용법

DbUtils

DbUtils 클래스는 JDBC 드라이버 로딩 및 리소스 해제와 같은 일반적인 작업을 위한 정적 메소드를 제공합니다.

  • close(Connection conn, Statement stmt, ResultSet rs): 제공된 Connection, Statement, ResultSet이 null이 아닌 경우 해당 리소스를 닫습니다.
  • closeQuietly(...): 리소스를 닫을 때 발생하는 SQLException을 숨기고 null 값도 안전하게 처리합니다.
  • commitAndClose(Connection conn): 트랜잭션을 커밋하고 Connection을 닫습니다.
  • commitAndCloseQuietly(Connection conn): 트랜잭션을 커밋하고 Connection을 닫되, 예외를 발생시키지 않습니다.
  • rollback(Connection conn): 트랜잭션을 롤백합니다. Connection이 null이어도 안전하게 처리합니다.
  • rollbackAndClose(Connection conn): 트랜잭션을 롤백하고 Connection을 닫습니다.
  • rollbackAndCloseQuietly(Connection conn): 트랜잭션을 롤백하고 Connection을 닫되, 예외를 발생시키지 않습니다.
  • loadDriver(String driverClassName): JDBC 드라이버를 로드하고 등록합니다. ClassNotFoundException을 처리할 필요 없이 사용 가능하며, 성공 시 true를 반환합니다.

QueryRunner 클래스

QueryRunner는 SQL 쿼리 실행을 단순화하며, ResultSetHandler와 함께 사용되어 대부분의 데이터베이스 작업을 효율적으로 처리할 수 있게 해줍니다.

QueryRunner는 두 가지 생성자를 제공합니다:

  • 기본 생성자
  • javax.sql.DataSource 객체를 인자로 받는 생성자

주요 메소드는 다음과 같습니다:

  • 업데이트 (INSERT, UPDATE, DELETE)
    • update(Connection conn, String sql, Object... params): INSERT, UPDATE, DELETE와 같은 업데이트 작업을 실행하고 영향을 받은 행 수를 반환합니다.
  • 삽입 (INSERT)
    • insert(Connection conn, String sql, ResultSetHandler rsh, Object... params): 자동 생성된 키 값을 포함하는 INSERT 문을 실행하고, ResultSetHandler를 사용하여 결과를 지정된 타입으로 반환합니다.
  • 배치 처리 (Batch Processing)
    • batch(Connection conn, String sql, Object[][] params): 여러 개의 INSERT, UPDATE, DELETE 문을 배치로 실행하고 각 문장의 영향을 받은 행 수를 반환합니다.
    • insertBatch(Connection conn, String sql, ResultSetHandler rsh, Object[][] params): 여러 개의 INSERT 문을 배치로 실행하고, ResultSetHandler를 사용하여 결과를 지정된 타입으로 반환합니다.
  • 조회 (Query)
    • query(Connection conn, String sql, ResultSetHandler rsh, Object... params): SQL 쿼리를 실행하고, ResultSetHandler를 사용하여 결과를 처리합니다. PreparedStatementResultSet의 생성 및 관리를 자동으로 처리합니다.
삽입 테스트
@Test
public void testInsertCustomer() throws SQLException {
    QueryRunner qr = new QueryRunner();
    Connection connection = JDBCUtils.getConnection(); // JDBCUtils는 데이터베이스 연결을 관리하는 유틸리티 클래스
    String sql = "INSERT INTO customers (name, email, birth) VALUES (?, ?, ?)";
    int rowsAffected = qr.update(connection, sql, "홍길동", "hong@example.com", "1990-05-15");
    System.out.println("삽입된 레코드 수: " + rowsAffected);
    JDBCUtils.closeResources(connection, null); // 리소스 해제
}
삭제 테스트
@Test
public void testDeleteCustomer() throws SQLException {
    QueryRunner qr = new QueryRunner();
    Connection connection = JDBCUtils.getConnection();
    String sql = "DELETE FROM customers WHERE id < ?";
    int rowsAffected = qr.update(connection, sql, 5);
    System.out.println("삭제된 레코드 수: " + rowsAffected);
    JDBCUtils.closeResources(connection, null);
}

ResultSetHandler 인터페이스 및 구현 클래스

ResultSetHandler 인터페이스는 java.sql.ResultSet을 처리하여 원하는 형태의 객체로 변환하는 역할을 합니다. 이 인터페이스는 Object handle(ResultSet rs) 메소드를 가지고 있습니다.

주요 구현 클래스는 다음과 같습니다:

  • ArrayHandler: 결과 집합의 첫 번째 행을 객체 배열로 변환합니다.
  • ArrayListHandler: 결과 집합의 각 행을 배열로 변환하여 리스트에 저장합니다.
  • BeanHandler<T>: 결과 집합의 첫 번째 행을 지정된 Java Bean 인스턴스로 매핑합니다.
  • BeanListHandler<T>: 결과 집합의 각 행을 지정된 Java Bean 인스턴스로 매핑하여 리스트에 저장합니다.
  • ColumnListHandler: 결과 집합의 특정 열 데이터를 리스트에 저장합니다.
  • KeyedHandler<K>: 결과 집합의 각 행을 맵으로 변환하고, 이를 다시 키를 기준으로 맵에 저장합니다.
  • MapHandler: 결과 집합의 첫 번째 행을 맵으로 변환합니다 (컬럼 이름을 키로 사용).
  • MapListHandler: 결과 집합의 각 행을 맵으로 변환하여 리스트에 저장합니다.
  • ScalarHandler: 결과 집합에서 단일 값(예: count, max, min)을 조회합니다.
단일 레코드 조회 테스트 (BeanHandler)
@Test
public void testQuerySingleCustomer() throws SQLException {
    QueryRunner qr = new QueryRunner();
    Connection connection = JDBCUtils.getConnection();
    String sql = "SELECT id, name, email, birth FROM customers WHERE id = ?";

    // Customer.class는 id, name, email, birth 필드를 가진 Java Bean
    BeanHandler<Customer> handler = new BeanHandler<>(Customer.class);
    Customer customer = qr.query(connection, sql, handler, 1); // ID가 1인 고객 조회
    System.out.println(customer);
    JDBCUtils.closeResources(connection, null);
}
다중 레코드 조회 테스트 (BeanListHandler)
@Test
public void testQueryCustomerList() throws SQLException {
    QueryRunner qr = new QueryRunner();
    Connection connection = JDBCUtils.getConnection();
    String sql = "SELECT id, name, email, birth FROM customers WHERE id < ?";

    BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class);
    List<Customer> customerList = qr.query(connection, sql, handler, 10); // ID가 10보다 작은 고객들 조회
    customerList.forEach(System.out::println);
    JDBCUtils.closeResources(connection, null);
}
다중 레코드 조회 테스트 (MapListHandler)
@Test
public void testQueryCustomerMapList() throws SQLException {
    QueryRunner qr = new QueryRunner();
    Connection connection = JDBCUtils.getConnection();
    String sql = "SELECT id, name, email FROM customers WHERE id < ?";

    MapListHandler handler = new MapListHandler();
    List> customerDataList = qr.query(connection, sql, handler, 10);
    customerDataList.forEach(System.out::println);
    JDBCUtils.closeResources(connection, null);
}
커스텀 ResultSetHandler 테스트

필요에 따라 직접 ResultSetHandler를 구현하여 복잡한 데이터 매핑 로직을 처리할 수 있습니다.

@Test
public void testQueryWithCustomHandler() throws SQLException {
    QueryRunner qr = new QueryRunner();
    Connection connection = JDBCUtils.getConnection();
    String sql = "SELECT id, name, email, birth FROM customers WHERE id = ?";

    ResultSetHandler<Customer> customHandler = rs -> {
        if (rs.next()) {
            int id = rs.getInt("id");
            String name = rs.getString("name");
            String email = rs.getString("email");
            java.util.Date birth = rs.getDate("birth");
            return new Customer(id, name, email, birth);
        }
        return null;
    };

    Customer customer = qr.query(connection, sql, customHandler, 1);
    System.out.println(customer);
    JDBCUtils.closeResources(connection, null);
}
스칼라 값 조회 테스트 (ScalarHandler)

집계 함수(COUNT, MAX, MIN, AVG, SUM) 등의 결과를 조회할 때 ScalarHandler를 사용합니다.

@Test
public void testQueryScalarValue() throws SQLException {
    QueryRunner qr = new QueryRunner();
    Connection connection = JDBCUtils.getConnection();

    // 예시 1: 고객 수 조회
    String countSql = "SELECT COUNT(*) FROM customers WHERE id < ?";
    ScalarHandler handler = new ScalarHandler();
    long count = (long) qr.query(connection, countSql, handler, 20);
    System.out.println("ID가 20 미만인 고객 수: " + count);

    // 예시 2: 가장 최근 생년월일 조회
    String maxBirthSql = "SELECT MAX(birth) FROM customers";
    Date maxBirth = (Date) qr.query(connection, maxBirthSql, handler);
    System.out.println("가장 최근 생년월일: " + maxBirth);

    JDBCUtils.closeResources(connection, null);
}
리소스 해제 유틸리티 메소드

DbUtils를 사용한 리소스 해제 예시입니다.

public static void closeResources(Connection conn, Statement stmt, ResultSet rs) {
    DbUtils.closeQuietly(rs);
    DbUtils.closeQuietly(stmt);
    DbUtils.closeQuietly(conn);
}

JDBC 요약

데이터베이스 작업을 처리할 때, 트랜잭션 관리가 중요합니다. 일반적으로 다음과 같은 흐름으로 코드를 작성합니다.

@Test
public void testTransactionCRUD() {
    Connection connection = null;
    try {
        // 1. 데이터베이스 연결 (연결 풀 사용 권장: C3P0, DBCP, Druid)
        connection = JDBCUtils.getConnection();
        // 트랜잭션 시작 (자동 커밋 비활성화)
        connection.setAutoCommit(false);

        // 2. CRUD 작업 수행 (QueryRunner 활용)
        QueryRunner qr = new QueryRunner();

        // 예: 데이터 삽입
        String insertSql = "INSERT INTO accounts (owner_name, balance) VALUES (?, ?)";
        qr.update(connection, insertSql, "Alice", 1000.0);

        // 예: 데이터 업데이트
        String updateSql = "UPDATE accounts SET balance = balance - ? WHERE owner_name = ?";
        qr.update(connection, updateSql, 200.0, "Alice");

        // 예: 다른 계정으로 이체 (두 번째 업데이트)
        String transferSql = "UPDATE accounts SET balance = balance + ? WHERE owner_name = ?";
        qr.update(connection, transferSql, 200.0, "Bob");

        // 3. 모든 작업 성공 시 트랜잭션 커밋
        connection.commit();
        System.out.println("트랜잭션 성공: 커밋 완료.");

    } catch (SQLException e) {
        // 4. 오류 발생 시 트랜잭션 롤백
        if (connection != null) {
            try {
                connection.rollback();
                System.out.println("트랜잭션 실패: 롤백 완료.");
            } catch (SQLException rollbackEx) {
                rollbackEx.printStackTrace();
            }
        }
        e.printStackTrace();
    } finally {
        // 5. 리소스 해제 (DbUtils.closeQuietly 사용)
        JDBCUtils.closeResources(connection, null); // Statement는 필요 시 추가
    }
}

태그: java JDBC DbUtils CRUD QueryRunner

8월 7일 15:31에 게시됨