Apache Commons DbUtils 配置参数详解
详解 Apache Commons DbUtils 1.8.1 的可配置项:QueryRunner 构造与 DataSource 适配、语句级配置与列映射策略、ResultSetHandler 结果形态,默认值均附实测回显。
Apache Commons DbUtils 配置参数详解
目标:把 Apache Commons DbUtils 1.8.1 所有可配置的行为一次性讲清——它没有配置文件,可配置点全在 QueryRunner 构造、语句配置与结果集处理器上。文中每个默认值与生效条件都给出实测回显。已在 Ubuntu 22.04(JDK 17 + Maven,连接 H2 内嵌数据库)实测通过。
一、环境信息
本章先交代实测环境与依赖坐标,让后续每个示例都可以独立复现。Commons DbUtils 1.8.1 编译目标是 Java 8,本文用 JDK 17 运行,并用 H2 内嵌数据库提供真实 JDBC 连接。
cat /etc/os-release | grep -E "^(NAME|VERSION)="
java -version
mvn -v | grep -E "Apache Maven|Java version"
实测回显:
NAME="Ubuntu"
VERSION="22.04.5 LTS (Jammy Jellyfish)"
openjdk version "17.0.20.1" 2026-08-18
OpenJDK Runtime Environment (build 17.0.20.1+1-1-22.04-Ubuntu)
Apache Maven 3.6.3
Java version: 17.0.20.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-17-openjdk-amd64
文中示例统一使用的坐标为:
commons-dbutils:commons-dbutils:1.8.1 (Apache Commons DbUtils 本库)
com.h2database:h2:2.5.252 (H2 内嵌数据库驱动)
💡 说明:DbUtils 没有任何配置文件,也不读取环境变量。所谓「配置」全部体现在 Java 代码的构造参数与处理器选择上,因此本章按「可配置点」而非「配置键」组织。
二、前置条件
本章搭建一个可运行示例的工程骨架,后续所有演示类都放进这个工程。创建目录与 pom.xml:
mkdir -p /opt/dbutils-demo/src/main/java/net/quzuo/demo
cd /opt/dbutils-demo
该命令无输出。pom.xml 内容:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.quzuo.demo</groupId>
<artifactId>dbutils-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.8.1</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.5.252</version>
</dependency>
</dependencies>
</project>
确认依赖可用:
mvn -q dependency:tree
该命令默认无输出(-q 静默)。去掉 -q 可见:
mvn dependency:tree | grep -E "dbutils|h2"
实测回显:
[INFO] +- commons-dbutils:commons-dbutils:jar:1.8.1:compile
[INFO] \- com.h2database:h2:jar:2.5.252:compile
本库只此两项、无更深子树,说明运行期不需要额外依赖。
三、可配置项总览
本章给出「有哪些可配置点」的全景表,后续各章分别展开。DbUtils 的配置维度可分四类:
| 配置维度 | 载体 | 关键类/方法 | 影响 |
|---|---|---|---|
| 数据源接入 | 构造参数 | QueryRunner(DataSource) / QueryRunner() | 连接从哪来、是否自动关闭 |
| 语句级参数 | StatementConfiguration | fetchSize / maxRows / queryTimeout | 单条 SQL 的取值限制、超时、取数批量 |
| 行映射策略 | RowProcessor / BeanProcessor | BasicRowProcessor / GenerousBeanProcessor | 列名如何映射到 Java 字段 |
| 结果形态 | ResultSetHandler 实现 | Bean*/Map*/Scalar/Column*/Keyed/Array* | 查询返回值是 List、单对象还是标量 |
关键差异与选型:数据源接入决定连接归属(谁负责关闭),行映射决定能不能把 created_at 这类下划线列自动映射到 createdAt,结果形态决定调用方拿到什么数据类型。四者互不冲突,可自由组合。
四、QueryRunner 构造与 DataSource 适配
本章讲第一类配置:连接从何而来。DbUtils 用 QueryRunner 作为统一入口,它的构造函数决定了连接来源与关闭责任。列出全部构造函数:
javap -cp ~/.m2/repository/commons-dbutils/commons-dbutils/1.8.1/commons-dbutils-1.8.1.jar org.apache.commons.dbutils.QueryRunner | grep "QueryRunner("
实测回显:
public org.apache.commons.dbutils.QueryRunner();
public org.apache.commons.dbutils.QueryRunner(boolean);
public org.apache.commons.dbutils.QueryRunner(javax.sql.DataSource);
public org.apache.commons.dbutils.QueryRunner(javax.sql.DataSource, boolean);
public org.apache.commons.dbutils.QueryRunner(javax.sql.DataSource, boolean, org.apache.commons.dbutils.StatementConfiguration);
public org.apache.commons.dbutils.QueryRunner(javax.sql.DataSource, org.apache.commons.dbutils.StatementConfiguration);
public org.apache.commons.dbutils.QueryRunner(org.apache.commons.dbutils.StatementConfiguration);
逐项说明:
QueryRunner():不绑定数据源,调用update/query时必须自己传Connection,连接由调用方负责关闭。QueryRunner(DataSource):绑定数据源,调用update/query时不必传Connection,DbUtils 会自己取连接并在方法结束时关闭。QueryRunner(boolean pmdKnownBroken)/QueryRunner(DataSource, boolean):第二个boolean控制是否跳过ParameterMetaData(部分驱动不支持时置 true 可避坑)。- 带
StatementConfiguration的构造:额外配置语句级参数,见第六章。
绑定数据源后可用 getDataSource() 取回:写入 ConfigDemo.java:
package net.quzuo.demo;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.StatementConfiguration;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.h2.jdbcx.JdbcDataSource;
import java.sql.SQLException;
public class ConfigDemo {
public static void main(String[] args) throws Exception {
JdbcDataSource ds = new JdbcDataSource();
ds.setURL("jdbc:h2:mem:cfg;DB_CLOSE_DELAY=-1");
ds.setUser("sa");
ds.setPassword("");
QueryRunner qr = new QueryRunner(ds);
System.out.println("getDataSource() 是否为传入实例: " + (qr.getDataSource() == ds));
try {
new QueryRunner().query("select 1", new ScalarHandler<>());
} catch (SQLException e) {
System.out.println("无 DataSource 且未传 Connection 时: " + e.getMessage());
}
qr.update("create table t(id int primary key, v varchar(16))");
qr.update("insert into t values(?, ?)", 1, "a");
qr.update("insert into t values(?, ?)", 2, "b");
qr.update("insert into t values(?, ?)", 3, "c");
StatementConfiguration cfg = new StatementConfiguration.Builder().maxRows(1).build();
QueryRunner limited = new QueryRunner(ds, cfg);
System.out.println("maxRows=1,无参数查询(走 createStatement): "
+ limited.query("select id, v from t order by id", new MapListHandler()));
System.out.println("maxRows=1,带参数查询(走 prepareStatement): "
+ limited.query("select id, v from t where id > ? order by id", new MapListHandler(), 0));
}
}
编译运行:
mvn -q compile exec:java -Dexec.mainClass=net.quzuo.demo.ConfigDemo
实测回显:
getDataSource() 是否为传入实例: true
无 DataSource 且未传 Connection 时: QueryRunner requires a DataSource to be invoked in this way, or a Connection should be passed in
maxRows=1,无参数查询(走 createStatement): [{ID=1, V=a}, {ID=2, V=b}, {ID=3, V=c}]
maxRows=1,带参数查询(走 prepareStatement): [{ID=1, V=a}]
逐行解读:第 1 行证明 getDataSource() 返回构造时传入的同一实例;第 2 行是实测的默认行为——用无数据源的 QueryRunner() 走不带 Connection 的方法会抛出提示「requires a DataSource … or a Connection should be passed in」;第 3、4 行是第六章的实测结论(maxRows 对无参查询不生效、对带参查询生效),本章先记住「连接来源由构造函数决定」这一点。
五、行映射策略:RowProcessor 与 BeanProcessor
本章讲第二类配置:ResultSet 的列如何映射到 Java 字段。DbUtils 用 RowProcessor 完成「一行 → 对象」的转换,默认实现是 BasicRowProcessor,其内部用 BeanProcessor 把列名匹配到 Bean 属性。默认 BeanProcessor 的规则是列名与属性名做大小写不敏感匹配,但不做下划线到驼峰的转换——因此数据库常见的 created_at 列无法映射到 createdAt 属性。要自动转换,需替换为 GenerousBeanProcessor。
💡 技术拓展:Java 里「把
ResultSet转成对象」有多种实现路线,下表对比常见方案:
| 方案 | 定位 | 依赖 | 对象映射 | 适用场景 |
|---|---|---|---|---|
| 原生 JDBC | JDK 内置接口 | 无 | 手写 rs.getXxx() 循环 | 逻辑极简单、零依赖 |
| Spring JdbcTemplate | Spring 生态的 JDBC 封装 | spring-jdbc | RowMapper 手动映射或 BeanPropertyRowMapper | 已用 Spring 的工程 |
| Commons DbUtils | 轻量 JDBC 结果集工具 | 仅 JDBC 驱动 | 内置 Bean/Map/Scalar 等 Handler | 不想引入 Spring 的轻量项目 |
| MyBatis | 全功能持久层框架 | mybatis 等 | XML/注解 + 内部映射器 | 需要 SQL 管理、缓存、动态 SQL |
本文使用 Commons DbUtils,其默认 BeanProcessor 不做下划线转换,需要时用 GenerousBeanProcessor 补齐。
用代码实测两种处理器的差异。先定义一个带 createdAt 属性的 Bean:
package net.quzuo.demo;
import java.sql.Timestamp;
public class User {
private int id;
private String name;
private String email;
private int age;
private Timestamp createdAt;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public Timestamp getCreatedAt() { return createdAt; }
public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; }
@Override
public String toString() {
return "User{id=" + id + ", name='" + name + "', email='" + email
+ "', age=" + age + ", createdAt=" + createdAt + "}";
}
}
再写对比程序 GenerousDemo.java:
package net.quzuo.demo;
import org.apache.commons.dbutils.BasicRowProcessor;
import org.apache.commons.dbutils.GenerousBeanProcessor;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.Connection;
import java.sql.DriverManager;
public class GenerousDemo {
public static void main(String[] args) throws Exception {
Connection conn = DriverManager.getConnection("jdbc:h2:mem:mapcol;DB_CLOSE_DELAY=-1");
QueryRunner runner = new QueryRunner();
runner.update(conn, "create table users(id int primary key, name varchar(32), "
+ "email varchar(64), age int, created_at timestamp)");
runner.update(conn, "insert into users values(1,'alice','alice@example.com',30,'2026-01-05 09:00:00')");
String sql = "select id, name, email, age, created_at from users";
System.out.println("默认 BasicRowProcessor/BeanProcessor: "
+ runner.query(conn, sql, new BeanListHandler<>(User.class)));
BeanListHandler<User> generous = new BeanListHandler<>(
User.class, new BasicRowProcessor(new GenerousBeanProcessor()));
System.out.println("GenerousBeanProcessor(下划线转驼峰): "
+ runner.query(conn, sql, generous));
conn.close();
}
}
编译运行:
mvn -q compile exec:java -Dexec.mainClass=net.quzuo.demo.GenerousDemo
实测回显:
默认 BasicRowProcessor/BeanProcessor: [User{id=1, name='alice', email='alice@example.com', age=30, createdAt=null}]
GenerousBeanProcessor(下划线转驼峰): [User{id=1, name='alice', email='alice@example.com', age=30, createdAt=2026-01-05 09:00:00.0}]
结论清晰:默认处理器下 createdAt=null(created_at 列没被映射),换成 GenerousBeanProcessor 后 createdAt=2026-01-05 09:00:00.0 被正确填充。因此只要库里用下划线命名列、Bean 用驼峰命名属性,就必须显式传入 new BasicRowProcessor(new GenerousBeanProcessor())。
六、语句级参数:StatementConfiguration
本章讲第三类配置:单条 SQL 的语句参数。StatementConfiguration 可配置 5 项,通过 Builder 构建。查看其 API:
javap -cp ~/.m2/repository/commons-dbutils/commons-dbutils/1.8.1/commons-dbutils-1.8.1.jar "org.apache.commons.dbutils.StatementConfiguration\$Builder" | grep -E "maxRows|queryTimeout|fetchSize|fetchDirection|maxFieldSize|build"
实测回显:
public org.apache.commons.dbutils.StatementConfiguration build();
public org.apache.commons.dbutils.StatementConfiguration$Builder fetchDirection(java.lang.Integer);
public org.apache.commons.dbutils.StatementConfiguration$Builder fetchSize(java.lang.Integer);
public org.apache.commons.dbutils.StatementConfiguration$Builder maxFieldSize(java.lang.Integer);
public org.apache.commons.dbutils.StatementConfiguration$Builder maxRows(java.lang.Integer);
public org.apache.commons.dbutils.StatementConfiguration$Builder queryTimeout(java.lang.Integer);
public org.apache.commons.dbutils.StatementConfiguration$Builder queryTimeout(java.time.Duration);
逐项说明与默认行为:
| 参数 | 类型 | 默认 | 作用 |
|---|---|---|---|
fetchSize | Integer | 未设置(不调用 setFetchSize) | 提示驱动一次取多少行,影响游标批量 |
maxRows | Integer | 未设置(不调用 setMaxRows) | 限制单条查询返回的最大行数 |
queryTimeout | Integer(秒)/ Duration | 未设置 | 单条语句超时秒数 |
fetchDirection | Integer | 未设置 | 结果集取数方向 |
maxFieldSize | Integer | 未设置 | 单个字段最大字节数 |
默认全部「未设置」,即不主动调用对应 JDBC 方法。用第四章 ConfigDemo 里 maxRows(1) 的实测对照可见关键结论:
maxRows=1,无参数查询(走 createStatement): [{ID=1, V=a}, {ID=2, V=b}, {ID=3, V=c}]
maxRows=1,带参数查询(走 prepareStatement): [{ID=1, V=a}]
同为 maxRows=1:无参数查询仍返回 3 行,带参数查询才被限制为 1 行。这不是巧合,而是 QueryRunner 的执行路径决定的。查看其源码可见分叉(org/apache/commons/dbutils/QueryRunner.java):
if (params != null && params.length > 0) {
final PreparedStatement ps = this.prepareStatement(conn, sql);
stmt = ps;
this.fillStatement(ps, params);
resultSet = this.wrap(ps.executeQuery());
} else {
stmt = conn.createStatement();
resultSet = this.wrap(stmt.executeQuery(sql));
}
而 StatementConfiguration 只在 prepareStatement 内部的 configureStatement(stmt) 里被应用:
private void prepareStatement(final Connection conn, final String sql) throws SQLException {
final PreparedStatement ps = conn.prepareStatement(sql);
try {
configureStatement(ps);
} catch (final SQLException e) {
ps.close();
throw e;
}
return ps;
}
结论:无参数查询走的是 conn.createStatement() 分支,根本不经过 configureStatement,所以 StatementConfiguration 对无参查询完全无效。 要让 maxRows/queryTimeout/fetchSize 生效,该查询必须带占位符参数(哪怕参数是恒真条件),或用 Connection 层面的 JDBC 设置自行控制。这是 1.8.1 的实测行为,使用前务必按此处规则判断。
⚠️ 注意:
StatementConfiguration.Builder.maxRows的参数是Integer,写成maxRows(1L)会编译报错incompatible types: long cannot be converted to java.lang.Integer;应写maxRows(1)。
七、结果集形态:ResultSetHandler 选择
本章讲第四类配置:查询返回值长什么样。QueryRunner.query 的最后一个参数是 ResultSetHandler,选不同的实现就得到不同形态的返回值。常见 Handler 与返回形态对照:
| Handler | 返回类型 | 形态说明 |
|---|---|---|
BeanHandler<T> | T | 首行 → 单个 Bean(无行则 null) |
BeanListHandler<T> | List<T> | 每行 → Bean,装进 List |
BeanMapHandler<K,V> | Map<K,V> | 指定列做 key,每行 → Bean |
MapHandler | Map<String,Object> | 首行 → 列名到值的 Map |
MapListHandler | List<Map<String,Object>> | 每行 → Map,装进 List |
ScalarHandler<T> | T | 单值(如 count(*)) |
ColumnListHandler<T> | List<T> | 单列所有行值 |
KeyedHandler<K> | Map<K,Map<String,Object>> | 指定列做 key,每行 → Map |
ArrayHandler | Object[] | 首行 → 各列值数组 |
ArrayListHandler | List<Object[]> | 每行 → 列值数组 |
关键差异与选型:要单一实体用 BeanHandler、要列表用 BeanListHandler、要键值索引用 Keyed/BeanMapHandler、要标量用 ScalarHandler、要列集合用 ColumnListHandler、要原始数组用 Array/ArrayListHandler。它们都只是「取值形态」不同,底层执行同一条 SQL。完整运行演示见《Apache Commons DbUtils 常用 CLI 命令与工具详解》篇;本表用于说明「配置哪一个 Handler 决定结果形态」这一可配置点。
八、生产配置建议
本章给出可直接套用的推荐写法。面向常见业务(中等并发的 OLTP、数据库列用下划线命名),推荐配置如下:
// 1. 连接交给数据源(如连接池),QueryRunner 只管执行与关闭
QueryRunner runner = new QueryRunner(dataSource);
// 2. 需要下划线列 -> 驼峰属性时,统一使用 GenerousBeanProcessor
BeanListHandler<User> userHandler = new BeanListHandler<>(
User.class, new BasicRowProcessor(new GenerousBeanProcessor()));
// 3. 需要限制单次要取的行数/超时,用 StatementConfiguration,
// 并注意:仅对带参数查询生效(见第六章实测)
StatementConfiguration cfg = new StatementConfiguration.Builder()
.maxRows(1000)
.queryTimeout(java.time.Duration.ofSeconds(5))
.build();
QueryRunner limitedRunner = new QueryRunner(dataSource, cfg);
// 4. 复杂查询可传 RowProcessor 复用映射策略
MapListHandler mapHandler = new MapListHandler(new BasicRowProcessor());
适用场景与理由:该模板适用于 8~32GB 内存、以单库读写为主的业务服务。QueryRunner(dataSource) 让连接的获取与归还都交给数据源(连接池),避免手动 close 遗漏;GenerousBeanProcessor 消除列名风格差异;StatementConfiguration 用于给带参查询兜底限流与超时。注意第 3 点的生效条件——脚本里若写的是无参 query(sql, handler),这些限制不会生效,必须确保关键查询带参数。
九、FAQ
Q1:DbUtils 的配置文件在哪个路径?要改哪个参数?
DbUtils 没有配置文件,也不读任何配置键。所有行为都由 Java 构造参数与处理器选择决定:连接来源看 QueryRunner 构造,语句参数看 StatementConfiguration,列映射看 BeanProcessor,结果形态看 ResultSetHandler。
Q2:为什么 StatementConfiguration.maxRows 设了却不生效?
因为无参数查询在 QueryRunner 内部走 conn.createStatement() 分支,不经过 configureStatement,故语句配置未被应用(见第六章源码与实测)。解决:让查询带上占位符参数(走 prepareStatement),或在 Connection 层面自行 setMaxRows。
Q3:Bean 里 createdAt 一直是 null,数据库列名是 created_at,怎么办?
默认 BeanProcessor 不做下划线转驼峰。用 new BeanListHandler<>(User.class, new BasicRowProcessor(new GenerousBeanProcessor())) 即可,实测映射后 createdAt 被正确填充。
Q4:用 QueryRunner()(不绑数据源)调用 query(sql, handler) 报错怎么处理?
报错信息是「QueryRunner requires a DataSource to be invoked in this way, or a Connection should be passed in」。要么改用 QueryRunner(dataSource),要么在方法里显式传入 Connection。
Q5:pmdKnownBroken 这个 boolean 参数什么时候要设成 true?
当使用的 JDBC 驱动对 ParameterMetaData.getParameterType() 支持有缺陷、导致 fillStatement 报错时,把构造函数里的 pmdKnownBroken 置为 true,DbUtils 就不再尝试读取参数元数据,改用逐参数 setObject。
十、参考链接
- Apache Commons DbUtils 官方主页:https://commons.apache.org/proper/commons-dbutils/
QueryRunnerAPI 文档:https://commons.apache.org/proper/commons-dbutils/apidocs/org/apache/commons/dbutils/QueryRunner.htmlStatementConfigurationAPI 文档:https://commons.apache.org/proper/commons-dbutils/apidocs/org/apache/commons/dbutils/StatementConfiguration.htmlGenerousBeanProcessorAPI 文档:https://commons.apache.org/proper/commons-dbutils/apidocs/org/apache/commons/dbutils/GenerousBeanProcessor.htmlResultSetHandlerAPI 文档:https://commons.apache.org/proper/commons-dbutils/apidocs/org/apache/commons/dbutils/ResultSetHandler.html- Maven 中央仓库 1.8.1 目录:https://repo1.maven.org/maven2/commons-dbutils/commons-dbutils/1.8.1/
- H2 Database 主页:https://h2database.com/