Apache Commons DbUtils 配置参数详解

详解 Apache Commons DbUtils 1.8.1 的可配置项:QueryRunner 构造与 DataSource 适配、语句级配置与列映射策略、ResultSetHandler 结果形态,默认值均附实测回显。

Apache Commons DbUtilsApache Commons DbUtils2026/10/14

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()连接从哪来、是否自动关闭
语句级参数StatementConfigurationfetchSize / maxRows / queryTimeout单条 SQL 的取值限制、超时、取数批量
行映射策略RowProcessor / BeanProcessorBasicRowProcessor / 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 转成对象」有多种实现路线,下表对比常见方案:

方案定位依赖对象映射适用场景
原生 JDBCJDK 内置接口无手写 rs.getXxx() 循环逻辑极简单、零依赖
Spring JdbcTemplateSpring 生态的 JDBC 封装spring-jdbcRowMapper 手动映射或 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);

逐项说明与默认行为:

参数类型默认作用
fetchSizeInteger未设置(不调用 setFetchSize)提示驱动一次取多少行,影响游标批量
maxRowsInteger未设置(不调用 setMaxRows)限制单条查询返回的最大行数
queryTimeoutInteger(秒)/ Duration未设置单条语句超时秒数
fetchDirectionInteger未设置结果集取数方向
maxFieldSizeInteger未设置单个字段最大字节数

默认全部「未设置」,即不主动调用对应 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
MapHandlerMap<String,Object>首行 → 列名到值的 Map
MapListHandlerList<Map<String,Object>>每行 → Map,装进 List
ScalarHandler<T>T单值(如 count(*))
ColumnListHandler<T>List<T>单列所有行值
KeyedHandler<K>Map<K,Map<String,Object>>指定列做 key,每行 → Map
ArrayHandlerObject[]首行 → 各列值数组
ArrayListHandlerList<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。

十、参考链接

  1. Apache Commons DbUtils 官方主页:https://commons.apache.org/proper/commons-dbutils/
  2. QueryRunner API 文档:https://commons.apache.org/proper/commons-dbutils/apidocs/org/apache/commons/dbutils/QueryRunner.html
  3. StatementConfiguration API 文档:https://commons.apache.org/proper/commons-dbutils/apidocs/org/apache/commons/dbutils/StatementConfiguration.html
  4. GenerousBeanProcessor API 文档:https://commons.apache.org/proper/commons-dbutils/apidocs/org/apache/commons/dbutils/GenerousBeanProcessor.html
  5. ResultSetHandler API 文档:https://commons.apache.org/proper/commons-dbutils/apidocs/org/apache/commons/dbutils/ResultSetHandler.html
  6. Maven 中央仓库 1.8.1 目录:https://repo1.maven.org/maven2/commons-dbutils/commons-dbutils/1.8.1/
  7. H2 Database 主页:https://h2database.com/