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: 자동 생성된 키 값을 포함하는 INSERT 문을 실행하고,rsh, Object... params) ResultSetHandler를 사용하여 결과를 지정된 타입으로 반환합니다.
- 배치 처리 (Batch Processing)
batch(Connection conn, String sql, Object[][] params): 여러 개의 INSERT, UPDATE, DELETE 문을 배치로 실행하고 각 문장의 영향을 받은 행 수를 반환합니다.insertBatch(Connection conn, String sql, ResultSetHandler: 여러 개의 INSERT 문을 배치로 실행하고,rsh, Object[][] params) ResultSetHandler를 사용하여 결과를 지정된 타입으로 반환합니다.
- 조회 (Query)
query(Connection conn, String sql, ResultSetHandler: SQL 쿼리를 실행하고,rsh, Object... params) ResultSetHandler를 사용하여 결과를 처리합니다.PreparedStatement와ResultSet의 생성 및 관리를 자동으로 처리합니다.
삽입 테스트
@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
커스텀 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는 필요 시 추가
}
}