Apache Commons DbUtils 常用 CLI 命令与工具详解
讲清 Apache Commons DbUtils 1.8.1 的 ResultSetHandler 家族与 QueryRunner API:query/update/insert/batch 形态,并演示 java -cp 运行查询工具。
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) | 随结果集大小增长 | 结果量可控的一般查询(本文所用) |
| 逐行迭代 | ResultSetIterator | Iterator<Object[]> | 只占一行 | 超大结果集,边读边处理 |
| 异步执行 | AsyncQueryRunner | Future<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 |
MapHandler | Map<String,Object> | 无参 | 首行 → 列名到值的 Map |
MapListHandler | List<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 |
ArrayHandler | Object[] | 无参 | 首行 → 各列值数组 |
ArrayListHandler | List<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.columns | ColumnHandler | Boolean/Byte/Double/Float/Integer/Long/Short/String/Timestamp/SQLXML | 从 ResultSet 取列值并转成指定 Java 类型,供 BeanProcessor 组装 Bean 时使用 |
handlers.properties | PropertyHandler | Date / 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;
四组命令的用途与返回值:
| 方法组 | 典型签名 | 返回值 | 用途 |
|---|---|---|---|
query | query(sql, handler) / query(sql, handler, 可变参数) | T(由 handler 决定) | SELECT 查询,结果由处理器转换 |
update | update(sql) / update(sql, 可变参数) | int(受影响行数) | INSERT/UPDATE/DELETE 与 DDL |
insert | insert(sql, handler, 可变参数) | T | INSERT 后用 handler 取回自增主键 |
batch | batch(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,一并补上即可。
十、参考链接
- Apache Commons DbUtils 官方主页:https://commons.apache.org/proper/commons-dbutils/
QueryRunnerAPI 文档:https://commons.apache.org/proper/commons-dbutils/apidocs/org/apache/commons/dbutils/QueryRunner.htmlResultSetHandler接口文档:https://commons.apache.org/proper/commons-dbutils/apidocs/org/apache/commons/dbutils/ResultSetHandler.htmlhandlers包(各 ResultSetHandler 实现)API 文档:https://commons.apache.org/proper/commons-dbutils/apidocs/org/apache/commons/dbutils/handlers/package-summary.htmlhandlers.columns包(列类型处理器)API 文档:https://commons.apache.org/proper/commons-dbutils/apidocs/org/apache/commons/dbutils/handlers/columns/package-summary.html- Maven 中央仓库 1.8.1 目录:https://repo1.maven.org/maven2/commons-dbutils/commons-dbutils/1.8.1/
- H2 Database 主页:https://h2database.com/