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-coreHSQLDB 及其他 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 执行查询至少需要三步:

  1. 创建查询
  2. 选择行映射方式
  3. 遍历结果

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();

ResultBearingQuery 实现相同接口,使用方式一致:

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 项目,可直接导入运行)。


原始标题:A Guide to jdbi

« 上一篇: OpenCSV 4 使用指南
» 下一篇: Maven 依赖范围详解