原生 JDBC 代碼冗長、重複:獲取連接、創建 Statement、設置參數、執行、遍歷 ResultSet 映射對象、關閉資源(finally 塊)。DbUtils(Apache Commons 組件)僅 1 個 JAR(~100KB),封裝了這些樣板代碼,讓 DAO 層開發回歸「業務邏輯」。
| 類/接口 | 核心作用 |
|---|---|
QueryRunner | 核心執行類,提供 update/query/batch 等方法 |
ResultSetHandler<T> | 策略接口,定義如何將 ResultSet 轉換為 Java 對象 |
DbUtils | 工具類:closeQuietly 靜默關閉連接/Statement/ResultSet |
| 各種 Handler 實現 | BeanHandler、BeanListHandler、MapHandler、ScalarHandler 等 |
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version> <!-- 或 1.8.1 -->
</dependency>
<!-- 配合 C3P0 連接池 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
// 1. 無參構造:每次操作手動傳 Connection,適合手動事務控制
QueryRunner qr = new QueryRunner();
qr.update(conn, "INSERT INTO user(name) VALUES(?)", "Tom");
// 2. 帶 DataSource 構造:自動獲取/關閉連接,自動提交事務(默認)
ComboPooledDataSource ds = new ComboPooledDataSource(); // 配置 c3p0-config.xml
QueryRunner qr = new QueryRunner(ds);
qr.update("INSERT INTO user(name) VALUES(?)", "Jerry"); // 無需手動連接
// 插入
int rows = qr.update("INSERT INTO user(name, age) VALUES(?, ?)", "Alice", 25);
// 更新
rows = qr.update("UPDATE user SET age=? WHERE name=?", 26, "Alice");
// 刪除
rows = qr.update("DELETE FROM user WHERE name=?", "Alice");
// 批量插入
Object[][] params = {{"Bob", 30}, {"Cathy", 28}, {"David", 35}};
int[] counts = qr.batch("INSERT INTO user(name, age) VALUES(?, ?)", params);
| Handler | 返回類型 | 適用場景 |
|---|---|---|
BeanHandler<T>(T.class) | T | 單行 → 實體對象 |
BeanListHandler<T>(T.class) | List<T> | 多行 → 實體列表(最常用) |
MapHandler() | Map<String, Object> | 單行 → 鍵值對(列名→值) |
MapListHandler() | List<Map<String, Object>> | 多行 → Map 列表 |
ArrayHandler() | Object[] | 單行 → 數組 |
ArrayListHandler() | List<Object[]> | 多行 → 數組列表 |
ScalarHandler<T>() | T | 單值聚合:count/sum/max/min |
ColumnListHandler("col") | List<Object> | 單列值列表 |
KeyedHandler("keyCol") | Map<Object, Map<String, Object>> | 按指定列分組 |
// 查詢所有用戶
List users = qr.query("SELECT * FROM user", new BeanListHandler<>(User.class));
// 查詢單個用戶
User user = qr.query("SELECT * FROM user WHERE id=?", new BeanHandler<>(User.class), 1);
// 統計總數
Long count = qr.query("SELECT COUNT(*) FROM user", new ScalarHandler<>());
// 自定義 Handler(複雜映射)
List users = qr.query("SELECT * FROM user", new ResultSetHandler>() {
@Override
public List handle(ResultSet rs) throws SQLException {
List list = new ArrayList<>();
while (rs.next()) {
User u = new User();
u.setId(rs.getInt("id"));
u.setName(rs.getString("name"));
// 手動處理關聯、嵌套對象等
list.add(u);
}
return list;
}
});
public class User {
private int id;
private String name, password, email, sex, hobby, address, description;
private Date birthday;
private int age;
// Getter/Setter/toString 省略
}
public interface UserDao {
User findByName(String name);
int register(User u);
User login(String username, String password);
}
public class UserDaoImpl implements UserDao {
private QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource()); // C3P0 DataSource
@Override
public User findByName(String name) {
try {
return qr.query("SELECT * FROM user WHERE name=?", new BeanHandler<>(User.class), name);
} catch (SQLException e) {
throw new RuntimeException("查詢用戶失敗", e);
}
}
@Override
public int register(User u) {
String sql = "INSERT INTO user(name,password,age,sex,email,hobby,address,description,birthday) VALUES(?,?,?,?,?,?,?,?,?)";
try {
return qr.update(sql, u.getName(), u.getPassword(), u.getAge(), u.getSex(),
u.getEmail(), u.getHobby(), u.getAddress(), u.getDescription(), u.getBirthday());
} catch (SQLException e) {
return -2; // 註冊失敗
}
}
@Override
public User login(String username, String password) {
try {
return qr.query("SELECT * FROM user WHERE name=? AND password=?", new BeanHandler<>(User.class), username, password);
} catch (SQLException e) {
throw new RuntimeException("登錄查詢失敗", e);
}
}
}
public class JDBCUtils {
private static ComboPooledDataSource ds = new ComboPooledDataSource(); // 讀取 c3p0-config.xml
public static DataSource getDataSource() { return ds; }
// DbUtils 內部自動關閉,無需手動 close
}
Connection conn = null;
try {
conn = ds.getConnection();
conn.setAutoCommit(false); // 開啟手動事務
QueryRunner qr = new QueryRunner();
qr.update(conn, "UPDATE account SET balance=balance-100 WHERE id=1");
qr.update(conn, "UPDATE account SET balance=balance+100 WHERE id=2");
conn.commit(); // 提交
} catch (SQLException e) {
if (conn != null) conn.rollback(); // 回滾
throw new RuntimeException("轉賬失敗", e);
} finally {
DbUtils.closeQuietly(conn); // 靜默關閉
}
QueryRunner 無狀態,可共享單例;DataSource 線程安全。update/query(sql, param1, param2...) 順序必須與 SQL 佔位符 ? 一一對應。BasicRowProcessor 自定義映射)。BeanListHandler 載入幾十萬行,改用 ResultSetHandler 流式處理或分頁查詢(LIMIT offset, size)。SQLException 後拋出 RuntimeException 或自定義異常,由全局異常處理器統一響應。DbUtils 以極小的體積、零侵入的設計,將 JDBC 樣板代碼減少 80% 以上。它不是全功能 ORM(無緩存、無延遲加載、無關聯映射),但對於中小型項目、微服務、舊系統改造,是「輕量級、高性能、易上手」的最佳選擇。掌握 QueryRunner + Handler 組合模式,配合 C3P0/HikariCP 連接池,你的 DAO 層將乾淨利落、專注業務。