1. 简介
本文将介绍如何使用 jdbi 查询关系型数据库。
Jdbi 是一个开源的 Java 库(Apache 许可证),它利用 Lambda 表达式 和 反射 提供比 JDBC 更友好的高级接口来访问数据库。
但请注意:Jdbi 不是 ORM 框架。尽管它有一个可选的 SQL Object 映射模块,但它没有会话管理、数据库独立层等典型 ORM 的复杂功能。
2. Jdbi 环境搭建
Jdbi 分为核心模块和多个可选模块。只需在依赖中引入核心模块即可开始使用:
<dependencies>
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-core</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies>
本文示例使用 HSQL 数据库:
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.4.0</version>
<scope>test</scope>
</dependency>
可在 Maven Central 查找最新版本的 jdbi3-core、HSQLDB 及其他 Jdbi 模块。
3. 连接数据库
首先需要配置数据库连接参数。入口是 Jdbi
类:
Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", "sa", "");
这里指定了连接 URL、用户名和密码。
3.1. 额外参数
若需提供其他参数,可使用重载方法传入 Properties
对象:
Properties properties = new Properties();
properties.setProperty("username", "sa");
properties.setProperty("password", "");
Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", properties);
这些示例中,我们将 Jdbi
实例保存在局部变量中,因为后续要用它向数据库发送语句和查询。
⚠️ 注意:调用 create
并不会立即建立数据库连接,只是保存连接参数供后续使用。
3.2. 使用 DataSource
如果通过 DataSource
连接数据库(常见场景),可使用对应的重载方法:
Jdbi jdbi = Jdbi.create(datasource);
3.3. 操作 Handle
数据库的实际连接由 Handle
类表示。推荐使用 Lambda 表达式自动管理连接:
jdbi.useHandle(handle -> {
doStuffWith(handle);
});
useHandle
:无需返回值时使用withHandle
:需要返回值时使用
jdbi.withHandle(handle -> {
return computeValue(handle);
});
虽然可以手动打开连接(不推荐),但必须手动关闭:
Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", "sa", "");
try (Handle handle = jdbi.open()) {
doStuffWith(handle);
}
✅ 好在 Handle
实现了 Closeable
,可直接用于 try-with-resources。
4. 简单语句操作
现在我们来看看如何使用连接。本节将创建一个贯穿全文的示例表。
发送 create table
等语句使用 execute
方法:
handle.execute(
"create table project "
+ "(id integer identity, name varchar(50), url varchar(100))");
execute
返回受影响的行数:
int updateCount = handle.execute(
"insert into project values "
+ "(1, 'tutorials', 'github.com/eugenp/tutorials')");
assertEquals(1, updateCount);
实际上 execute
只是便捷方法,后续会介绍更复杂的用法。在此之前,先学习如何从数据库提取结果。
5. 查询数据库
最简单的数据库结果提取方式是 SQL 查询。使用 Jdbi Handle 执行查询至少需要三步:
- 创建查询
- 选择行映射方式
- 遍历结果
5.1. 创建查询
Jdbi 用 Query
类表示查询,可通过 Handle 获取:
Query query = handle.createQuery("select * from project");
5.2. 结果映射
Jdbi 封装了 JDBC 的 ResultSet
(API 较繁琐),提供多种结果访问方式:
将每行映射为
Map
(键为列名):query.mapToMap();
单列查询映射到 Java 类型:
handle.createQuery("select name from project").mapTo(String.class);
Jdbi 内置了常见类型的映射器,特定库/数据库的映射器在独立模块中提供。当然,也可以自定义映射器(后续章节介绍)。
5.3. 遍历结果
调用映射方法后得到 ResultIterable
对象,用于逐行遍历结果:
转换为 List:
List<Map<String, Object>> results = query.mapToMap().list();
转换为其他集合:
List<String> results = query.mapTo(String.class).collect(Collectors.toSet());
使用 Stream 遍历:
query.mapTo(String.class).useStream((Stream<String> stream) -> { doStuffWith(stream) });
5.4. 获取单个结果
当只需要一行结果时,有专用方法可用:
最多一条结果(返回
Optional
):Optional<Map<String, Object>> first = query.mapToMap().findFirst();
必须且仅有一条结果:
Date onlyResult = query.mapTo(Date.class).findOnly();
❌ 如果结果为空或多于一条,findOnly()
会抛出 IllegalStateException
。
6. 绑定参数
查询通常包含固定部分和参数化部分,优势包括:
- 安全:避免字符串拼接,防止 SQL 注入
- 便捷:无需记忆复杂数据类型(如时间戳)的语法
- 性能:静态部分可一次解析并缓存
Jdbi 支持位置参数和命名参数:
位置参数(用
?
表示):Query positionalParamsQuery = handle.createQuery("select * from project where name = ?");
命名参数(以
:
开头):Query namedParamsQuery = handle.createQuery("select * from project where url like :pattern");
使用 bind
方法设置参数值:
positionalParamsQuery.bind(0, "tutorials");
namedParamsQuery.bind("pattern", "%github.com/eugenp/%");
⚠️ 注意:索引从 0 开始(不同于 JDBC)。
6.1. 批量绑定命名参数
可通过对象一次性绑定多个命名参数。例如:
Query query = handle.createQuery(
"select id from project where name = :name and url = :url");
Map<String, String> params = new HashMap<>();
params.put("name", "REST with Spring");
params.put("url", "github.com/eugenp/REST-With-Spring");
使用 Map 绑定:
query.bindMap(params);
使用 JavaBean 对象绑定:
query.bindBean(paramsBean);
还支持绑定对象字段或方法,详见 Jdbi 文档。
7. 执行复杂语句
现在将查询、值和参数的知识应用到语句操作。前文的 execute
只是便捷方法。
DDL/DML 语句由 Update
类表示,通过 Handle 的 createUpdate
获取:
Update update = handle.createUpdate(
"INSERT INTO PROJECT (NAME, URL) VALUES (:name, :url)");
Update
拥有与 Query
相同的绑定方法(第 6 节适用)。调用 execute
执行语句:
int rows = update.execute();
返回受影响的行数。
7.1. 提取自增列值
当插入语句包含自增列(如自增主键或序列)时,可用 executeAndReturnGeneratedKeys
获取生成值:
Update update = handle.createUpdate(
"INSERT INTO PROJECT (NAME, URL) "
+ "VALUES ('tutorials', 'github.com/eugenp/tutorials')");
ResultBearing generatedKeys = update.executeAndReturnGeneratedKeys();
ResultBearing
与 Query
实现相同接口,使用方式一致:
generatedKeys.mapToMap()
.findOnly().get("id");
8. 事务管理
当需要将多个语句作为原子操作执行时,必须使用事务。与 Handle 类似,通过闭包方法引入事务:
handle.useTransaction((Handle h) -> {
haveFunWith(h);
});
事务在闭包返回时自动关闭,但必须显式提交或回滚:
handle.useTransaction((Handle h) -> {
h.execute("...");
h.commit();
});
❌ 如果闭包抛出异常,Jdbi 会自动回滚事务。
需要返回值时使用 inTransaction
:
handle.inTransaction((Handle h) -> {
h.execute("...");
h.commit();
return true;
});
8.1. 手动事务管理
虽然不推荐,但也可以手动管理事务:
handle.begin();
// ...
handle.commit();
handle.close();
9. 总结与延伸阅读
本文介绍了 Jdbi 的核心功能:查询、语句和事务。未涉及的高级特性包括:
- 自定义行/列映射
- 批处理
- SQL Object 扩展模块
详情请参考 Jdbi 官方文档。
所有示例代码可在 GitHub 项目 中找到(Maven 项目,可直接导入运行)。