在本文中,我们将了解如何使用jdbi查询关系数据库。
Jdbi 是一个开源 Java 库(Apache 许可),它使用lambda 表达式和反射来提供比JDBC更友好、更高级别的接口来访问数据库。
然而,Jdbi 不是 ORM。即使它有一个可选的 SQL 对象映射模块,它也没有一个带有附加对象的会话、一个数据库独立层,以及典型 ORM 的任何其他花里胡哨。
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 模块。
首先,我们需要连接到数据库。为此,我们必须指定连接参数。
起点是Jdbi类:
Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", "sa", "");
在这里,我们指定了连接 URL、用户名,当然还有密码。
如果我们需要提供其他参数,我们使用接受Properties对象的重载方法:
Properties properties = new Properties();
properties.setProperty("username", "sa");
properties.setProperty("password", "");
Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", properties);
在这些示例中,我们将Jdbi实例保存在局部变量中。那是因为我们将使用它向数据库发送语句和查询。
事实上,仅仅调用create并没有建立到数据库的任何连接。它只是保存连接参数以供以后使用。
如果我们使用DataSource连接到数据库,通常情况下,我们可以使用适当的create重载:
Jdbi jdbi = Jdbi.create(datasource);
与数据库的实际连接由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 一起使用。
现在我们知道了如何获取连接,让我们看看如何使用它。
在本节中,我们将创建一个我们将在整篇文章中使用的简单表。
要将create table等语句发送到数据库,我们使用execute方法:
handle.execute(
"create table project "
+ "(id integer identity, name varchar(50), url varchar(100))");
执行返回受语句影响的行数:
int updateCount = handle.execute(
"insert into project values "
+ "(1, 'tutorials', 'github.com/eugenp/tutorials')");
assertEquals(1, updateCount);
实际上,execute 只是一种方便的方法。
我们将在后面的部分中查看更复杂的用例,但在此之前,我们需要学习如何从数据库中提取结果。
从数据库中产生结果的最直接的表达式是 SQL 查询。
要使用 Jdbi 句柄发出查询,我们至少必须:
我们现在来看看上面的每一点。
不出所料,Jdbi 将查询表示为Query类的实例。
我们可以从句柄中获取一个:
Query query = handle.createQuery("select * from project");
Jdbi 从 JDBC ResultSet中抽象出来,后者有一个相当繁琐的 API。
因此,它提供了几种访问由查询或其他返回结果的语句产生的列的可能性。我们现在将看到最简单的。
我们可以将每一行表示为一张地图:
query.mapToMap();
地图的键将是选定的列名。
或者,当查询返回单个列时,我们可以将其映射到所需的 Java 类型:
handle.createQuery("select name from project").mapTo(String.class);
Jdbi 内置了许多常见类的映射器。特定于某些库或数据库系统的那些在单独的模块中提供。
当然,我们也可以定义和注册我们的映射器。我们将在后面的部分中讨论它。
最后,我们可以将行映射到 bean 或其他一些自定义类。同样,我们将在专门的部分中看到更高级的选项。
一旦我们决定如何通过调用适当的方法来映射结果,我们就会收到一个ResultIterable对象。
然后我们可以使用它来迭代结果,一次一行。
在这里,我们将看看最常见的选项。
我们只能将结果累积在一个列表中:
List<Map<String, Object>> results = query.mapToMap().list();
或者到另一个Collection类型:
List<String> results = query.mapTo(String.class).collect(Collectors.toSet());
或者我们可以将结果作为流进行迭代:
query.mapTo(String.class).useStream((Stream<String> stream) -> {
doStuffWith(stream)
});
在这里,为了清楚起见,我们显式地键入了流变量,但没有必要这样做。
作为一种特殊情况,当我们期望或只对一行感兴趣时,我们有几个专用的方法可用。
如果我们最多想要一个结果,我们可以使用findFirst:
Optional<Map<String, Object>> first = query.mapToMap().findFirst();
如我们所见,它返回一个Optional值,该值仅在查询返回至少一个结果时才存在。
如果查询返回多行,则仅返回第一行。
相反,如果我们想要一个且只有一个 result,我们使用findOnly:
Date onlyResult = query.mapTo(Date.class).findOnly();
最后,如果结果为零或多个,findOnly会抛出IllegalStateException。
通常,查询具有固定部分和参数化部分。这有几个优点,包括:
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/%");
请注意,与 JDBC 不同,索引从 0 开始。
我们还可以使用一个对象将多个命名参数绑定在一起。
假设我们有这个简单的查询:
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");
然后,例如,我们可以使用地图:
query.bindMap(params);
或者我们可以以各种方式使用一个对象。例如,在这里,我们绑定一个遵循 JavaBean 约定的对象:
query.bindBean(paramsBean);
但我们也可以绑定对象的字段或方法;有关所有支持的选项,请参阅Jdbi 文档。
现在我们已经了解了查询、值和参数,我们可以回到语句并应用相同的知识。
回想一下,我们之前看到的execute方法只是一个方便的快捷方式。
事实上,与查询类似,DDL 和 DML 语句表示为类Update 的实例。
我们可以通过在句柄上调用方法createUpdate来获取:
Update update = handle.createUpdate(
"INSERT INTO PROJECT (NAME, URL) VALUES (:name, :url)");
然后,在Update上,我们拥有Query中的所有绑定方法,因此第 6 节也适用于更新。url
当我们调用,surprise, execute时执行语句:
int rows = update.execute();
正如我们已经看到的,它返回受影响的行数。
作为一种特殊情况,当我们有一个带有自动生成列(通常是自动增量或序列)的插入语句时,我们可能想要获取生成的值。
然后,我们不调用execute,而是调用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");
每当我们必须将多个语句作为单个原子操作执行时,我们都需要一个事务。
与连接句柄一样,我们通过调用带有闭包的方法来引入事务:
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;
});
虽然在一般情况下不建议这样做,但我们也可以手动开始和关闭事务:
handle.begin();
// ...
handle.commit();
handle.close();
在本教程中,我们介绍了 Jdbi 的核心:查询、语句和事务。
我们遗漏了一些高级功能,例如自定义行和列映射以及批处理。
我们还没有讨论任何可选模块,尤其是 SQL 对象扩展。
Jdbi 文档中详细介绍了所有内容。
所有这些示例和代码片段的实现都可以在GitHub 项目中找到——这是一个 Maven 项目,因此应该很容易导入并按原样运行。