Apache Commons DbUtils 常用 CLI 命令与工具详解

讲清 Apache Commons DbUtils 1.8.1 的 ResultSetHandler 家族与 QueryRunner API:query/update/insert/batch 形态,并演示 java -cp 运行查询工具。

Apache Commons DbUtilsApache Commons DbUtils2026/10/14

Apache Commons DbUtils 常用 CLI 命令与工具详解

目标:把 Apache Commons DbUtils 1.8.1 提供的全部「工具」讲清——它没有独立命令行程序,工具集就是 QueryRunner 的执行命令与各类 ResultSetHandler。文中每个处理器的构造方式与运行结果都给出实测回显。已在 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 内嵌数据库驱动)

⚠️ 关于「CLI 命令」:Commons DbUtils 是 Java 库,不提供任何 shell 命令行工具,jar 内也没有 Main-Class。本文所说的「命令」指 QueryRunner 提供的方法(query/update/insert/batch),「工具」指 org.apache.commons.dbutils 包内的处理器类;最后再用 java -cp 运行一个自写查询工具,演示脱离 Maven 后的直接调用方式。

二、DbUtils 工具集与包结构概览

本章先看清这个库里到底有哪些「工具」。列出主 jar 中 org.apache.commons.dbutils 根包的全部顶层类:

javap_jar=$HOME/.m2/repository/commons-dbutils/commons-dbutils/1.8.1/commons-dbutils-1.8.1.jar
jar tf "$javap_jar" | grep -E '^org/apache/commons/dbutils/[A-Za-z]+\.class$' | sort

实测回显:

org/apache/commons/dbutils/AbstractQueryRunner.class
org/apache/commons/dbutils/AsyncQueryRunner.class
org/apache/commons/dbutils/BaseResultSetHandler.class
org/apache/commons/dbutils/BasicRowProcessor.class
org/apache/commons/dbutils/BeanProcessor.class
org/apache/commons/dbutils/Column.class
org/apache/commons/dbutils/ColumnHandler.class
org/apache/commons/dbutils/DbUtils.class
org/apache/commons/dbutils/GenerousBeanProcessor.class
org/apache/commons/dbutils/OutParameter.class
org/apache/commons/dbutils/PropertyHandler.class
org/apache/commons/dbutils/ProxyFactory.class
org/apache/commons/dbutils/QueryLoader.class
org/apache/commons/dbutils/QueryRunner.class
org/apache/commons/dbutils/ResultSetHandler.class
org/apache/commons/dbutils/ResultSetIterator.class
org/apache/commons/dbutils/RowProcessor.class
org/apache/commons/dbutils/StatementConfiguration.class

按职责归类如下:

分类类作用
执行入口QueryRunner / AsyncQueryRunner / AbstractQueryRunner执行 SQL,同步或异步
结果处理ResultSetHandler(接口)/ BaseResultSetHandler把 ResultSet 转成任意返回形态
行处理RowProcessor / BasicRowProcessor一行 → Bean 或 Map
列映射BeanProcessor / GenerousBeanProcessor列名 → Java 属性
处理器目录handlers / handlers.columns / handlers.properties现成的结果集与列处理器
语句配置StatementConfiguration每语句的 fetchSize/maxRows/queryTimeout
资源与杂项DbUtils / QueryLoader / ProxyFactory / ColumnHandler / PropertyHandler / OutParameter / Column关闭连接、加载 SQL、代理、扩展点

💡 技术拓展:同一个 ResultSet,DbUtils 提供三种消费方式,选哪种取决于内存与并发要求。下表做对比,本文演示以 ResultSetHandler 为主:

消费方式类返回内存占用适用场景
一次性转换ResultSetHandler 实现全量集合(List/Map)随结果集大小增长结果量可控的一般查询(本文所用)
逐行迭代ResultSetIteratorIterator<Object[]>只占一行超大结果集,边读边处理
异步执行AsyncQueryRunnerFuture<T>由线程池承接需要并发执行多条 SQL

三者共用同一套 SQL 执行逻辑,区别只在「结果怎么取出、什么时候取出」。

三、前置条件与演示工程

本章搭建一个可运行的 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 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

依赖树只有本库与 H2 驱动两个 compile 节点,说明运行期不需要别的 jar。本章还需一个演示用的 Bean,写入 src/main/java/net/quzuo/demo/User.java:

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 + "}";
    }
}

四、ResultSetHandler 家族逐个详解

本章是本文核心:把 handlers 包内的结果集处理器全部列出,并写一个程序逐个运行,取真实回显。先看该包下有哪些类:

jar tf "$javap_jar" | grep -E '^org/apache/commons/dbutils/handlers/[A-Za-z]+\.class$' | sort

实测回显:

org/apache/commons/dbutils/handlers/AbstractKeyedHandler.class
org/apache/commons/dbutils/handlers/AbstractListHandler.class
org/apache/commons/dbutils/handlers/ArrayHandler.class
org/apache/commons/dbutils/handlers/ArrayListHandler.class
org/apache/commons/dbutils/handlers/BeanHandler.class
org/apache/commons/dbutils/handlers/BeanListHandler.class
org/apache/commons/dbutils/handlers/BeanMapHandler.class
org/apache/commons/dbutils/handlers/ColumnListHandler.class
org/apache/commons/dbutils/handlers/KeyedHandler.class
org/apache/commons/dbutils/handlers/MapHandler.class
org/apache/commons/dbutils/handlers/MapListHandler.class
org/apache/commons/dbutils/handlers/ScalarHandler.class

其中 AbstractKeyedHandler、AbstractListHandler 是基类,实际使用的是其余 10 个。它们与返回形态、构造方式对照如下:

Handler返回类型主要构造说明
BeanHandler<T>T(Class<T>) / (Class<T>, RowProcessor)首行 → 单个 Bean,无行返回 null
BeanListHandler<T>List<T>(Class<T>) / (Class<T>, RowProcessor)每行 → Bean
BeanMapHandler<K,V>Map<K,V>(Class<V>) / (Class<V>, int|String|RowProcessor)指定列做 key,值 → Bean
MapHandlerMap<String,Object>无参首行 → 列名到值的 Map
MapListHandlerList<Map<String,Object>>无参每行 → Map
ScalarHandler<T>T无参 / (int) / (String)取单个值(如 count(*))
ColumnListHandler<T>List<T>无参 / (int) / (String)取单列所有行
KeyedHandler<K>Map<K,Map<String,Object>>无参 / (int) / (String) / (RowProcessor)指定列做 key,值 → Map
ArrayHandlerObject[]无参首行 → 各列值数组
ArrayListHandlerList<Object[]>无参每行 → 列值数组

关键差异与选型:单一实体用 BeanHandler、实体列表用 BeanListHandler、键值索引用 Keyed/BeanMapHandler、标量用 ScalarHandler、单列取值用 ColumnListHandler、原始数组用 Array/ArrayListHandler。它们底层执行的是同一条 SQL,唯一区别是把 ResultSet 转成了不同的 Java 形态。

为验证这张表,写入演示程序 src/main/java/net/quzuo/demo/HandlerDemo.java:

package net.quzuo.demo;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ArrayHandler;
import org.apache.commons.dbutils.handlers.ArrayListHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.BeanMapHandler;
import org.apache.commons.dbutils.handlers.ColumnListHandler;
import org.apache.commons.dbutils.handlers.KeyedHandler;
import org.apache.commons.dbutils.handlers.MapHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Arrays;
import java.util.List;

public class HandlerDemo {

    public static void main(String[] args) throws Exception {
        Connection conn = DriverManager.getConnection("jdbc:h2:mem:handlers;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')");
        runner.update(conn, "insert into users values(2,'bob','bob@example.com',28,'2026-01-06 10:30:00')");
        runner.update(conn, "insert into users values(3,'carol','carol@example.com',35,'2026-01-07 14:15:00')");
        String sql = "select id, name, email, age, created_at from users order by id";

        System.out.println("[MapListHandler] " + runner.query(conn, sql, new MapListHandler()));

        System.out.println("[MapHandler] " + runner.query(conn, sql, new MapHandler()));

        System.out.println("[BeanListHandler] " + runner.query(conn, sql, new BeanListHandler<>(User.class)));

        System.out.println("[BeanHandler] " + runner.query(conn, sql, new BeanHandler<>(User.class)));

        System.out.println("[ScalarHandler] " + runner.query(conn, "select count(*) from users", new ScalarHandler<>()));

        System.out.println("[ColumnListHandler] " + runner.query(conn, sql, new ColumnListHandler<String>("name")));

        System.out.println("[KeyedHandler] " + runner.query(conn, sql, new KeyedHandler<Integer>(1)));

        System.out.println("[BeanMapHandler] " + runner.query(conn, sql, new BeanMapHandler<Integer, User>(User.class, 1)));

        Object[] arrayRow = runner.query(conn, "select id, name from users where id = 2", new ArrayHandler());
        System.out.println("[ArrayHandler] " + Arrays.toString(arrayRow));

        List<Object[]> arrList = runner.query(conn, "select id, name from users order by id", new ArrayListHandler());
        System.out.println("[ArrayListHandler] " + arrList.stream().map(Arrays::toString).toList());

        conn.close();
    }
}

程序分三个动作:用 QueryRunner.update 建 users 表并插入 3 行;对同一张表依次套用 10 个处理器;其中 ScalarHandler 换成 count(*)、ArrayHandler 只取 id=2 一行、KeyedHandler 与 BeanMapHandler 用第 1 列 id 做 key、ColumnListHandler 取 name 列。编译运行:

mvn -q compile exec:java -Dexec.mainClass=net.quzuo.demo.HandlerDemo

实测回显:

[MapListHandler] [{ID=1, NAME=alice, EMAIL=alice@example.com, AGE=30, CREATED_AT=2026-01-05 09:00:00.0}, {ID=2, NAME=bob, EMAIL=bob@example.com, AGE=28, CREATED_AT=2026-01-06 10:30:00.0}, {ID=3, NAME=carol, EMAIL=carol@example.com, AGE=35, CREATED_AT=2026-01-07 14:15:00.0}]
[MapHandler] {ID=1, NAME=alice, EMAIL=alice@example.com, AGE=30, CREATED_AT=2026-01-05 09:00:00.0}
[BeanListHandler] [User{id=1, name='alice', email='alice@example.com', age=30, createdAt=null}, User{id=2, name='bob', email='bob@example.com', age=28, createdAt=null}, User{id=3, name='carol', email='carol@example.com', age=35, createdAt=null}]
[BeanHandler] User{id=1, name='alice', email='alice@example.com', age=30, createdAt=null}
[ScalarHandler] 3
[ColumnListHandler] [alice, bob, carol]
[KeyedHandler] {1={ID=1, NAME=alice, EMAIL=alice@example.com, AGE=30, CREATED_AT=2026-01-05 09:00:00.0}, 2={ID=2, NAME=bob, EMAIL=bob@example.com, AGE=28, CREATED_AT=2026-01-06 10:30:00.0}, 3={ID=3, NAME=carol, EMAIL=carol@example.com, AGE=35, CREATED_AT=2026-01-07 14:15:00.0}}
[BeanMapHandler] {1=User{id=1, name='alice', email='alice@example.com', age=30, createdAt=null}, 2=User{id=2, name='bob', email='bob@example.com', age=28, createdAt=null}, 3=User{id=3, name='carol', email='carol@example.com', age=35, createdAt=null}}
[ArrayHandler] [2, bob]
[ArrayListHandler] [[1, alice], [2, bob], [3, carol]]

逐行解读:

  • [MapListHandler]:3 行全部转成 Map,列名(大写)为 key,值为 Object,CREATED_AT 是 Timestamp。
  • [MapHandler]:仅取首行,仍是 Map。
  • [BeanListHandler]:3 行映射为 User,createdAt 全为 null——因为列名是下划线 CREATED_AT,默认 BeanProcessor 不做下划线转驼峰。
  • [BeanHandler]:仅取首行的 User。
  • [ScalarHandler]:标量结果 3(count(*))。
  • [ColumnListHandler]:只取 name 列,得到 [alice, bob, carol]。
  • [KeyedHandler]:以第 1 列 id 为 key,每行是列名到值的 Map。
  • [BeanMapHandler]:以第 1 列 id 为 key,每行是 User 对象。
  • [ArrayHandler]:首行各列值数组 [2, bob](只查了 id, name)。
  • [ArrayListHandler]:每行一个数组,装进 List。

⚠️ 注意:[BeanListHandler] 与 [BeanMapHandler] 的输出里 createdAt=null,是默认列映射的预期行为,不是缺陷。要让 CREATED_AT 正确映射到 createdAt,把处理器改成 new BeanListHandler<>(User.class, new BasicRowProcessor(new GenerousBeanProcessor())) 即可。

各处理器的构造签名可用 javap 核对(以 BeanListHandler、KeyedHandler、ScalarHandler、ColumnListHandler、BeanMapHandler 为例):

javap -cp "$javap_jar" org.apache.commons.dbutils.handlers.BeanListHandler | grep public
javap -cp "$javap_jar" org.apache.commons.dbutils.handlers.KeyedHandler | grep public
javap -cp "$javap_jar" org.apache.commons.dbutils.handlers.ScalarHandler | grep public
javap -cp "$javap_jar" org.apache.commons.dbutils.handlers.ColumnListHandler | grep public
javap -cp "$javap_jar" org.apache.commons.dbutils.handlers.BeanMapHandler | grep public

实测回显:

  public org.apache.commons.dbutils.handlers.BeanListHandler(java.lang.Class<? extends T>);
  public org.apache.commons.dbutils.handlers.BeanListHandler(java.lang.Class<? extends T>, org.apache.commons.dbutils.RowProcessor);
  public org.apache.commons.dbutils.handlers.KeyedHandler();
  public org.apache.commons.dbutils.handlers.KeyedHandler(int);
  public org.apache.commons.dbutils.handlers.KeyedHandler(org.apache.commons.dbutils.RowProcessor);
  public org.apache.commons.dbutils.handlers.KeyedHandler(java.lang.String);
  public org.apache.commons.dbutils.handlers.ScalarHandler();
  public org.apache.commons.dbutils.handlers.ScalarHandler(int);
  public org.apache.commons.dbutils.handlers.ScalarHandler(java.lang.String);
  public org.apache.commons.dbutils.handlers.ColumnListHandler();
  public org.apache.commons.dbutils.handlers.ColumnListHandler(int);
  public org.apache.commons.dbutils.handlers.ColumnListHandler(java.lang.String);
  public org.apache.commons.dbutils.handlers.BeanMapHandler(java.lang.Class<V>);
  public org.apache.commons.dbutils.handlers.BeanMapHandler(java.lang.Class<V>, int);
  public org.apache.commons.dbutils.handlers.BeanMapHandler(java.lang.Class<V>, org.apache.commons.dbutils.RowProcessor);
  public org.apache.commons.dbutils.handlers.BeanMapHandler(java.lang.Class<V>, java.lang.String);

要点:KeyedHandler/ColumnListHandler/ScalarHandler 都能用**列序号(int,从 1 开始)或列名(String)**指定目标列;BeanListHandler/BeanMapHandler 可额外传入 RowProcessor 定制映射策略。

五、列类型处理器与属性处理器

本章讲两个扩展子包,它们让「取单个列值」和「Bean 属性赋值」可控到具体类型。列出 handlers.columns 与 handlers.properties 下的类:

jar tf "$javap_jar" | grep -E '^org/apache/commons/dbutils/handlers/(columns|properties)/[A-Za-z]+\.class$' | sort

实测回显:

org/apache/commons/dbutils/handlers/columns/BooleanColumnHandler.class
org/apache/commons/dbutils/handlers/columns/ByteColumnHandler.class
org/apache/commons/dbutils/handlers/columns/DoubleColumnHandler.class
org/apache/commons/dbutils/handlers/columns/FloatColumnHandler.class
org/apache/commons/dbutils/handlers/columns/IntegerColumnHandler.class
org/apache/commons/dbutils/handlers/columns/LongColumnHandler.class
org/apache/commons/dbutils/handlers/columns/SQLXMLColumnHandler.class
org/apache/commons/dbutils/handlers/columns/ShortColumnHandler.class
org/apache/commons/dbutils/handlers/columns/StringColumnHandler.class
org/apache/commons/dbutils/handlers/columns/TimestampColumnHandler.class
org/apache/commons/dbutils/handlers/properties/DatePropertyHandler.class
org/apache/commons/dbutils/handlers/properties/StringEnumPropertyHandler.class

两类的作用与用法:

子包接口实现类作用
handlers.columnsColumnHandlerBoolean/Byte/Double/Float/Integer/Long/Short/String/Timestamp/SQLXML从 ResultSet 取列值并转成指定 Java 类型,供 BeanProcessor 组装 Bean 时使用
handlers.propertiesPropertyHandlerDate / StringEnum把 ResultSet 列值转成 Bean 属性需要的类型

用法要点:取单列全部值仍用 handlers 包里的 ColumnListHandler<T>(可传列序号或列名)。而这两个子包是 BeanProcessor 的类型转换扩展点:BeanProcessor 通过 ServiceLoader 加载 ColumnHandler 与 PropertyHandler,在「一行 → Bean」时按目标属性类型逐一匹配——BooleanColumnHandler 对应 boolean/Boolean 属性、TimestampColumnHandler 对应 java.sql.Timestamp 属性、java.util.Date 属性则由 DatePropertyHandler 处理。也就是说,这两个子包的实现类不是拿来直接 new 的,而是被 BeanProcessor 在映射时自动选用。

六、QueryRunner 常用 API 命令形态

本章把「执行命令」本身列全。QueryRunner 的公开方法可分为四组,先看签名:

javap -cp "$javap_jar" org.apache.commons.dbutils.QueryRunner | grep -E "public .*(query|update|insert|batch)\("

实测回显(输出节选,实测首尾):

  public int[] batch(java.sql.Connection, java.lang.String, java.lang.Object[][]) throws java.sql.SQLException;
  public int[] batch(java.lang.String, java.lang.Object[][]) throws java.sql.SQLException;
  public <T> T insert(java.sql.Connection, java.lang.String, org.apache.commons.dbutils.ResultSetHandler<T>) throws java.sql.SQLException;
  public <T> T insert(java.sql.Connection, java.lang.String, org.apache.commons.dbutils.ResultSetHandler<T>, java.lang.Object...) throws java.sql.SQLException;
  public <T> T insert(java.lang.String, org.apache.commons.dbutils.ResultSetHandler<T>) throws java.sql.SQLException;
  public <T> T insert(java.lang.String, org.apache.commons.dbutils.ResultSetHandler<T>, java.lang.Object...) throws java.sql.SQLException;
  public <T> T query(java.lang.String, org.apache.commons.dbutils.ResultSetHandler<T>) throws java.sql.SQLException;
  public <T> T query(java.lang.String, org.apache.commons.dbutils.ResultSetHandler<T>, java.lang.Object...) throws java.sql.SQLException;
  public int update(java.sql.Connection, java.lang.String) throws java.sql.SQLException;
  public int update(java.lang.String, java.lang.Object...) throws java.sql.SQLException;

四组命令的用途与返回值:

方法组典型签名返回值用途
queryquery(sql, handler) / query(sql, handler, 可变参数)T(由 handler 决定)SELECT 查询,结果由处理器转换
updateupdate(sql) / update(sql, 可变参数)int(受影响行数)INSERT/UPDATE/DELETE 与 DDL
insertinsert(sql, handler, 可变参数)TINSERT 后用 handler 取回自增主键
batchbatch(sql, Object[][])int[](每行一条的受影响数)同一条 SQL 批量执行多组参数

每组都有「带 Connection」与「不带 Connection」两个版本。带 Connection 时连接由调用方负责关闭;不带时要求 QueryRunner 构造时绑定了 DataSource,由 DbUtils 自己取连接并关闭。除同步版外还有 AsyncQueryRunner,其同名方法返回 Future<T>:

javap -cp "$javap_jar" org.apache.commons.dbutils.AsyncQueryRunner | grep -E "public.*(query|update|batch)"

实测回显:

  public java.util.concurrent.Future<int[]> batch(java.lang.String, java.lang.Object[][]) throws java.sql.SQLException;
  public <T> java.util.concurrent.Future<T> query(java.lang.String, org.apache.commons.dbutils.ResultSetHandler<T>) throws java.sql.SQLException;
  public <T> java.util.concurrent.Future<T> query(java.lang.String, org.apache.commons.dbutils.ResultSetHandler<T>, java.lang.Object...) throws java.sql.SQLException;
  public java.util.concurrent.Future<java.lang.Integer> update(java.lang.String) throws java.sql.SQLException;
  public java.util.concurrent.Future<java.lang.Integer> update(java.lang.String, java.lang.Object...) throws java.sql.SQLException;

AsyncQueryRunner 用内部 ExecutorService 承接执行,调用方拿 Future 异步取结果,适合并发执行多条 SQL 的场景。

七、用 java -cp 运行自写查询工具

本章演示脱离 Maven 后,如何用 java -cp 直接跑一个自己写的查询工具——这是生产排查、临时脚本最常用的形态。第一步,让 Maven 把依赖的完整类路径打到文件:

cd /opt/dbutils-demo
mvn dependency:build-classpath -Dmdep.outputFile=/tmp/cp.txt -q
cat /tmp/cp.txt

实测回显:

/root/.m2/repository/commons-dbutils/commons-dbutils/1.8.1/commons-dbutils-1.8.1.jar:/root/.m2/repository/com/h2database/h2/2.5.252/h2-2.5.252.jar

得到的是一串以冒号分隔的 jar 绝对路径(Linux 类路径分隔符是 :)。第二步,写一个不依赖工程包结构的查询工具 QueryTool.java(放在 /tmp 下):

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.h2.jdbcx.JdbcDataSource;

import java.sql.Connection;
import java.util.List;
import java.util.Map;

public class QueryTool {
    public static void main(String[] args) throws Exception {
        JdbcDataSource ds = new JdbcDataSource();
        ds.setURL("jdbc:h2:mem:clitool;DB_CLOSE_DELAY=-1");
        ds.setUser("sa");
        ds.setPassword("");
        QueryRunner runner = new QueryRunner(ds);
        try (Connection conn = ds.getConnection()) {
            runner.update(conn, "create table account(id int primary key, name varchar(20), balance decimal(10,2))");
            runner.update(conn, "insert into account values(?,?,?)", 1, "alice", 100.00);
            runner.update(conn, "insert into account values(?,?,?)", 2, "bob", 250.50);
        }
        ResultSetHandler<List<Map<String, Object>>> h = new MapListHandler();
        List<Map<String, Object>> rows = runner.query("select id, name, balance from account order by id", h);
        System.out.println("查询结果行数: " + rows.size());
        for (Map<String, Object> row : rows) {
            System.out.println(row);
        }
        Object count = runner.query("select count(*) from account", new ScalarHandler<Long>());
        System.out.println("记录总数: " + count);
    }
}

这个工具刻意贴近真实使用:用 JdbcDataSource 构造一个数据源交给 QueryRunner(DataSource),所以 query/update 都不再需要显式传 Connection;建表与插入写在 try 内先做,随后用 MapListHandler 查全表、ScalarHandler 取计数。第三步,编译并直接运行(-cp 里同时放入编译产物与依赖 jar):

javac -encoding UTF-8 -cp "$(cat /tmp/cp.txt)" -d /tmp/cliout /tmp/QueryTool.java
java -Dfile.encoding=UTF-8 -cp "/tmp/cliout:$(cat /tmp/cp.txt)" QueryTool

实测回显:

查询结果行数: 2
{ID=1, NAME=alice, BALANCE=100.00}
{ID=2, NAME=bob, BALANCE=250.50}
记录总数: 2

四行输出确认了「数据源接入 → 建表插入 → Map 查询 → 标量查询」整条链路在纯 java -cp 方式下可用。命令要点:编译用 javac -cp "依赖路径" -d 输出目录,运行用 java -cp "输出目录:依赖路径" 主类,两处的类路径都必须带依赖 jar,否则运行期报 NoClassDefFoundError 或 No suitable driver。

八、注意事项与常见坑

本章汇总实测中踩到、且很容易复现的几个坑。第一,类路径漏掉 JDBC 驱动。只放 DbUtils jar 时运行查询工具:

java -Dfile.encoding=UTF-8 -cp "/tmp/cliout:/root/.m2/repository/commons-dbutils/commons-dbutils/1.8.1/commons-dbutils-1.8.1.jar" QueryTool

实测回显:

Exception in thread "main" java.sql.SQLException: No suitable driver found for jdbc:h2:mem:clitool;DB_CLOSE_DELAY=-1
        at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:706)
        at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:252)
        at QueryTool.main(QueryTool.java:17)

报错说明:DbUtils 自身零依赖,但真正连库仍需数据库配套驱动,把驱动 jar 加回类路径即可。第二,dbUtil 版本与打印中文。终端默认语言环境不是 UTF-8 时,Java 打印中文会乱码,运行前 export LANG=C.UTF-8 或运行时追加 -Dfile.encoding=UTF-8。第三,下划线列名映射。默认 BeanProcessor 不把 created_at 映射到 createdAt,需要时显式改成 new BasicRowProcessor(new GenerousBeanProcessor())(见第四章实测的 createdAt=null)。第四,列序号从 1 开始。KeyedHandler/ScalarHandler/ColumnListHandler 的 int 构造参数是 JDBC 列序号,第一个字段是 1 而不是 0,实测用 new KeyedHandler<Integer>(1) 即以首列 id 为 key。

九、FAQ

Q1:Commons DbUtils 有没有命令行工具,能不能像 mysql 那样直接连库查数据? 没有。它只提供 Java 类库(jar 内无 Main-Class),「命令」指 QueryRunner 的 query/update/insert/batch 方法。要跑查询,需自己写主类并用 java -cp "依赖jar" 主类 运行,见第七章实测。

Q2:[BeanListHandler] 的输出里 createdAt 为什么是 null? 因为 H2 的列名是下划线风格 CREATED_AT,默认 BeanProcessor 只做大小写不敏感匹配,不做下划线转驼峰。改用 new BeanListHandler<>(User.class, new BasicRowProcessor(new GenerousBeanProcessor())) 即可正确映射(见第四章实测)。

Q3:MapListHandler 返回的 Map 里,key 为什么是大写的? H2 默认把未加引号的标识符折叠为大写,因此返回列名是 ID/NAME 等大写形态。这不是 DbUtils 的处理结果,而是数据库的标识符规则;取值时按实际返回的 key 大小写访问。

Q4:KeyedHandler 和 BeanMapHandler 有什么区别? 两者都以指定列为 key。KeyedHandler<K> 的值是 Map<String,Object>(列名到值),BeanMapHandler<K,V> 的值是映射后的 Bean 对象。实测:前者 key=1 的值是 {ID=1, NAME=alice, EMAIL=alice@example.com, AGE=30, CREATED_AT=2026-01-05 09:00:00.0},后者 key=1 的值是 User{id=1, name='alice', email='alice@example.com', age=30, createdAt=null}。

Q5:query 和 insert 都传了 handler,返回的为什么不一样? query 的结果形态完全由 handler 决定,返回 T;insert 专用于 INSERT 后取回结果(如自增主键),同样用 handler 承接返回值。update 则一律返回 int 受影响行数,不接受 handler。

Q6:java -cp 运行时报 NoClassDefFoundError: org/apache/commons/dbutils/QueryRunner 怎么办? 说明运行时类路径里没有 DbUtils jar。用 mvn dependency:build-classpath 导出依赖路径(见第七章),把 commons-dbutils-1.8.1.jar 一并放进 -cp;若报 No suitable driver,则是缺 JDBC 驱动 jar,一并补上即可。

十、参考链接

  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. ResultSetHandler 接口文档:https://commons.apache.org/proper/commons-dbutils/apidocs/org/apache/commons/dbutils/ResultSetHandler.html
  4. handlers 包(各 ResultSetHandler 实现)API 文档:https://commons.apache.org/proper/commons-dbutils/apidocs/org/apache/commons/dbutils/handlers/package-summary.html
  5. handlers.columns 包(列类型处理器)API 文档:https://commons.apache.org/proper/commons-dbutils/apidocs/org/apache/commons/dbutils/handlers/columns/package-summary.html
  6. Maven 中央仓库 1.8.1 目录:https://repo1.maven.org/maven2/commons-dbutils/commons-dbutils/1.8.1/
  7. H2 Database 主页:https://h2database.com/