Jdbi指南

A Guide to Jdbi

 

 

1.简介

在本文中,我们将研究如何使用jdbi查询关系数据库。

Jdbi是一个开源Java库(Apache许可),它使用lambda表达式和反射来提供比JDBC更友好,更高级的接口来访问数据库。

但是,Jdbi不是ORM。 即使它具有可选的SQL Object映射模块,也没有与附加对象,数据库独立层以及典型ORM的所有其他细节的会话。

2. Jdbi设置

Jdbi被组织成一个核心和几个可选模块。

 

 

首先,我们只需要在依赖项中包含核心模块即可:

 

1
2
3
4
5
6
7
<dependencies>
    <dependency>
        <groupId>org.jdbi</groupId>
        <artifactId>jdbi3-core</artifactId>
        <version>3.1.0</version>
    </dependency>
</dependencies>

 

在本文的整个过程中,我们将展示使用HSQL数据库的示例:

 

1
2
3
4
5
6
<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类:

 

1
Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB","sa","");

 

在这里,我们指定了连接URL,用户名,当然还有密码。

 

 

3.1。 附加参数

如果需要提供其他参数,则可以使用接受Properties对象的重载方法:

 

1
2
3
4
Properties properties = new Properties();
properties.setProperty("username","sa");
properties.setProperty("password","");
Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", properties);

 

在这些示例中,我们已将Jdbi实例保存在本地变量中。 那是因为我们将使用它来将语句和查询发送到数据库。

实际上,仅调用create并不会建立与数据库的任何连接。 它只是保存连接参数供以后使用。

3.2。 使用数据源

如果像通常那样使用数据源连接到数据库,则可以使用适当的创建重载:

 

1
Jdbi jdbi = Jdbi.create(datasource);

 

3.3。 处理手柄

到数据库的实际连接由Handle类的实例表示。

使用句柄并使其自动关闭的最简单方法是使用lambda表达式:

 

1
2
3
jdbi.useHandle(handle -> {
    doStuffWith(handle);
});

 

当我们不必返回值时,我们调用useHandle。

否则,我们使用withHandle:

 

1
2
3
jdbi.withHandle(handle -> {
    return computeValue(handle);
});

 

尽管不建议这样做,也可以手动打开连接句柄。 在这种情况下,我们必须在完成后将其关闭:

 

1
2
3
4
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方法:

 

1
2
3
handle.execute(
 "create table project"
  +"(id integer identity, name varchar(50), url varchar(100))");

 

execute返回受语句影响的行数:

 

 

 

1
2
3
4
5
int updateCount = handle.execute(
 "insert into project values"
  +"(1, 'tutorials', 'github.com/eugenp/tutorials')");

assertEquals(1, updateCount);

 

实际上,执行只是一种方便的方法。

在后面的部分中,我们将介绍更复杂的用例,但在此之前,我们需要学习如何从数据库中提取结果。

5.查询数据库

从数据库产生结果的最直接的表达式是SQL查询。

要使用Jdbi句柄发出查询,我们至少必须:

  • 创建查询

  • 选择如何代表每一行

  • 遍历结果

现在,我们将研究以上各点。

5.1。 创建查询

毫不奇怪,Jdbi将查询表示为Query类的实例。

我们可以从句柄中获得一个:

 

1
Query query = handle.createQuery("select * from project");

 

5.2。 映射结果

Jdbi从JDBC ResultSet中抽象出来,后者具有相当繁琐的API。

因此,它提供了几种访问查询或返回结果的其他语句所产生的列的可能性。 现在,我们将看到最简单的那些。

我们可以将每一行表示为地图:

 

1
query.mapToMap();

 

映射的键将是选定的列名称。

或者,当查询返回单个列时,我们可以将其映射到所需的Java类型:

 

1
handle.createQuery("select name from project").mapTo(String.class);

 

Jdbi具有许多常见类的内置映射器。 某些库或数据库系统专用的模块在单独的模块中提供。

当然,我们也可以定义和注册我们的映射器。 我们将在后面的部分中讨论它。

最后,我们可以将行映射到bean或其他自定义类。 同样,我们将在专用部分中看到更高级的选项。

5.3。 遍历结果

一旦决定了如何通过调用适当的方法来映射结果,我们将收到一个ResultIterable对象。

然后,我们可以使用它遍历结果,一次一行。

在这里,我们将介绍最常见的选项。

我们只能将结果累积在一个列表中:

 

1
List<Map<String, Object>> results = query.mapToMap().list();

 

或到另一个Collection类型:

 

1
List<String> results = query.mapTo(String.class).collect(Collectors.toSet());

 

或者我们可以将结果作为流进行迭代:

 

1
2
3
query.mapTo(String.class).useStream((Stream<String> stream) -> {
    doStuffWith(stream)
});

 

 

 

在这里,为了清楚起见,我们显式地键入了流变量,但这不是必须的。

5.4。 获得单一结果

作为一种特殊情况,当我们只希望或只对一行感兴趣时,可以使用几种专用方法。

如果我们最多想要一个结果,则可以使用findFirst:

 

1
Optional<Map<String, Object>> first = query.mapToMap().findFirst();

 

如我们所见,它返回一个Optional值,仅当查询返回至少一个结果时才存在。

如果查询返回多个行,则仅返回第一行。

如果相反,我们只需要一个结果,则使用findOnly:

 

1
Date onlyResult = query.mapTo(Date.class).findOnly();

 

最后,如果结果为零或不止一个,则findOnly抛出IllegalStateException。

6.绑定参数

通常,查询具有固定部分和参数化部分。 这具有几个优点,包括:

  • 安全性:通过避免字符串连接,我们可以防止SQL注入

  • 轻松:我们不必记住复杂数据类型(例如时间戳)的确切语法

  • 性能:查询的静态部分可以解析一次并缓存

Jdbi支持位置参数和命名参数。

我们在查询或语句中插入位置参数作为问号:

 

1
2
Query positionalParamsQuery =
  handle.createQuery("select * from project where name = ?");

 

命名参数改为以冒号开头:

 

1
2
Query namedParamsQuery =
  handle.createQuery("select * from project where url like :pattern");

 

在任何一种情况下,要设置参数的值,我们使用bind方法的一种变体:

 

1
2
positionalParamsQuery.bind(0,"tutorials");
namedParamsQuery.bind("pattern","%github.com/eugenp/%");

 

请注意,与JDBC不同,索引从0开始。

6.1。 一次绑定多个命名参数

我们还可以使用一个对象将多个命名参数绑定在一起。

假设我们有一个简单的查询:

 

1
2
3
4
5
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");

 

然后,例如,我们可以使用地图:

 

1
query.bindMap(params);

 

或者我们可以通过多种方式使用对象。 例如,在这里,我们绑定一个遵循JavaBean约定的对象:

 

1
query.bindBean(paramsBean);

 

但是我们也可以绑定对象的字段或方法。 有关所有受支持的选项的信息,请参见Jdbi文档。

7.发布更复杂的声明

既然我们已经看到了查询,值和参数,那么我们可以回到语句并应用相同的知识。

回想一下我们前面看到的execute方法只是一个方便的快捷方式。

实际上,类似于查询,DDL和DML语句表示为类Update的实例。

我们可以通过在句柄上调用方法createUpdate获得一个:

 

1
2
Update update = handle.createUpdate(
 "INSERT INTO PROJECT (NAME, URL) VALUES (:name, :url)");

 

然后,在Update上,我们具有查询中具有的所有绑定方法,因此第6节同样适用于更新。

当我们调用,惊讶并执行时,将执行语句:

 

1
int rows = update.execute();

 

正如我们已经看到的,它返回受影响的行数。

7.1。 提取自动增量列值

作为一种特殊情况,当我们有一个包含自动生成的列(通常是自动递增或序列)的insert语句时,我们可能想要获取生成的值。

然后,我们不调用execute,而是executeAndReturnGeneratedKeys:

 

1
2
3
4
Update update = handle.createUpdate(
 "INSERT INTO PROJECT (NAME, URL)"
  +"VALUES ('tutorials', 'github.com/eugenp/tutorials')");
ResultBearing generatedKeys = update.executeAndReturnGeneratedKeys();

 

ResultBearing与我们之前看到的Query类实现的接口相同,因此我们已经知道如何使用它:

 

1
2
generatedKeys.mapToMap()
  .findOnly().get("id");

 

8.交易

每当我们必须将多个语句作为单个原子操作执行时,就需要进行事务处理。

与连接句柄一样,我们通过调用带有闭包的方法来引入事务:

 

1
2
3
handle.useTransaction((Handle h) -> {
    haveFunWith(h);
});

 

并且,与句柄一样,当关闭返回时,事务将自动关闭。

但是,我们必须在返回之前提交或回滚事务:

 

1
2
3
4
handle.useTransaction((Handle h) -> {
    h.execute("...");
    h.commit();
});

 

但是,如果从闭包中抛出异常,Jdbi将自动回滚事务。

与句柄一样,如果要从闭包中返回某些内容,我们有一个专用的方法inTransaction:

 

1
2
3
4
5
handle.inTransaction((Handle h) -> {
    h.execute("...");
    h.commit();
    return true;
});

 

8.1。 手动交易管理

尽管通常不建议这样做,但我们也可以手动开始和关闭事务:

 

1
2
3
4
handle.begin();
// ...
handle.commit();
handle.close();

 

9.结论和进一步阅读

在本教程中,我们介绍了Jdbi的核心:查询,语句和事务。

我们遗漏了一些高级功能,例如自定义行和列映射以及批处理。

我们也没有讨论任何可选模块,尤其是SQL Object扩展。

Jdbi文档中详细介绍了所有内容。

所有这些示例和代码段的实现都可以在GitHub项目中找到–这是一个Maven项目,因此应该很容易直接导入和运行。

posted @ 2024-03-26 12:39  CharyGao  阅读(373)  评论(0)    收藏  举报