Java Jdbi
上次修改时间:2025 年 6 月 6 日
在本教程中,我们将探讨如何使用 JDBI 有效地处理数据,JDBI 是一个功能强大且轻量级的库,它简化了 Java 中的数据库交互。
JDBI 构建于 JDBC 之上,使数据库编程更加直观且不易出错。 它提供自动异常处理、高效的资源管理以及强大的工具,可将查询结果直接映射到 Java 对象。 这大大减少了样板代码并提高了可维护性。
JDBI 的核心是 DBI
实例,它通过 Handle
实例促进与数据库的连接。 Handle
表示与数据库的活动连接,充当标准 JDBC Connection
对象的包装器。 这种抽象允许更简洁的事务管理和对查询的增强控制。
JDBI 支持两种不同的交互方式
- Fluent API: 提供了一种更动态、更具表现力的方式,以链式方式构建查询,从而减少冗余代码。
- Object API: 将 SQL 查询结果直接映射到 Java 对象,无需手动处理结果集。
通过利用 JDBI,开发人员可以轻松地执行 CRUD 操作、管理事务以及与高级 SQL 技术集成。 在接下来的章节中,我们将演示实际示例,包括使用两个 API 插入、检索、更新和删除记录。
在示例中,我们使用 H2 和 PostgreSQL 数据库。
<dependency> <groupId>org.jdbi</groupId> <artifactId>jdbi3-core</artifactId> <version>3.45.2</version> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.7.3</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.2.224</version> </dependency>
对于这些示例,我们需要以下构件:jdbi3-core
、postgresql
和 h2
。
简单查询
在下一个示例中,我们将执行一个简单的查询。
import org.jdbi.v3.core.Jdbi; void main() { String jdbcUrl = "jdbc:h2:mem:"; Jdbi jdbi = Jdbi.create(jdbcUrl); int res = jdbi.withHandle(handle -> handle.createQuery("SELECT 2 + 2") .mapTo(Integer.class) .one()); System.out.println(res); }
该示例创建了一个内存中的 H2 数据库并执行 SELECT 2 + 2
语句。
String jdbcUrl = "jdbc:h2:mem:";
这是与内存中的 H2 数据库的连接。
Jdbi jdbi = Jdbi.create(jdbcUrl);
我们使用 Jdbi.create
创建主入口点。 它是 JDBC 数据源的可配置包装器。 使用它来获取 Handle
实例,并为从中获得的所有句柄提供配置。
int res = jdbi.withHandle(handle -> handle.createQuery("SELECT 2 + 2") .mapTo(Integer.class) .one());
withHandle
是一个方便的函数,它管理句柄的生命周期并将其传递给回调以供客户端使用。 Handle
表示与数据库系统的连接。 它是 JDBC Connection
对象的包装器。 Handle 提供了用于事务管理、语句创建以及与数据库会话相关的其他操作的基本方法。
该查询返回一个整数,因此我们使用 mapTo
将结果映射为一个整数。 由于返回的值是标量,因此我们调用 one
。 它返回结果集中唯一的行。 如果行本身为 null,则返回 null。
$ java Main.java 4
调用数据库函数
我们调用 H2VERSION
函数,该函数返回 H2 数据库的版本。
import org.jdbi.v3.core.Jdbi; void main() { String jdbcUrl = "jdbc:h2:mem:"; Jdbi jdbi = Jdbi.create(jdbcUrl); String res = jdbi.withHandle(handle -> handle.createQuery("SELECT H2VERSION()") .mapTo(String.class) .one()); System.out.println(res); }
由于该函数返回一个字符串,因此我们使用 mapTo
将结果映射为一个字符串。
$ java Main.java 2.2.224
对于 PostgreSQL,VERSION
数据库函数返回数据库版本。
import org.jdbi.v3.core.Jdbi; void main() { String jdbcUrl = "jdbc:postgresql://:5432/testdb"; String user = "postgres"; String password = "s$cret"; Jdbi jdbi = Jdbi.create(jdbcUrl, user, password); String res = jdbi.withHandle(handle -> handle.createQuery("SELECT VERSION()") .mapTo(String.class) .one()); System.out.println(res); }
在此示例中,我们还提供了用户名和密码。
$ java Main.java PostgreSQL 16.0, compiled by Visual C++ build 1935, 64-bit
批处理
批处理是指作为单个单元提交和执行的一组 SQL 语句。 此功能提供了性能优势,并简化了某些数据库操作的代码。
import org.jdbi.v3.core.Jdbi; void main() { String jdbcUrl = "jdbc:postgresql://:5432/testdb"; String user = "postgres"; String password = "s$cret"; Jdbi jdbi = Jdbi.create(jdbcUrl, user, password); jdbi.withHandle(handle -> handle.createBatch() .add("DROP TABLE IF EXISTS cars") .add("CREATE TABLE cars(id serial PRIMARY KEY, name VARCHAR(255), price INT)") .add("INSERT INTO cars(name, price) VALUES('Audi',52642)") .add("INSERT INTO cars(name, price) VALUES('Mercedes',57127)") .add("INSERT INTO cars(name, price) VALUES('Skoda',9000)") .add("INSERT INTO cars(name, price) VALUES('Volvo',29000)") .add("INSERT INTO cars(name, price) VALUES('Bentley',350000)") .add("INSERT INTO cars(name, price) VALUES('Citroen',21000)") .add("INSERT INTO cars(name, price) VALUES('Hummer',41400)") .add("INSERT INTO cars(name, price) VALUES('Volkswagen',21600)") .execute()); System.out.println("Table created and data inserted using JDBI batch."); }
该示例在数据库中创建一个 cars 表。 使用 createBatch
方法创建一个新批处理。 使用 add
方法添加语句。 最后,使用 execute
运行整个批处理。
one 方法
查询使用 select
执行。 当您期望结果恰好包含一行时,one
方法返回。 仅当返回的行映射到 null
时,此方法才返回 null
,如果结果有零行或多行,则抛出异常。
import org.jdbi.v3.core.Jdbi; void main() { String jdbcUrl = "jdbc:postgresql://:5432/testdb"; String user = "postgres"; String password = "s$cret"; Jdbi jdbi = Jdbi.create(jdbcUrl, user, password); String res = jdbi.withHandle(handle -> handle.select("SELECT name FROM cars WHERE id = ?", 3) .mapTo(String.class) .one()); System.out.println(res); }
该示例返回指定 Id 的汽车名称。
$ java Main.java Skoda
findOne 方法
findOne
方法返回一个 Optional
。
import org.jdbi.v3.core.Jdbi; import java.util.Optional; void main() { String jdbcUrl = "jdbc:postgresql://:5432/testdb"; String user = "postgres"; String password = "s$cret"; Jdbi jdbi = Jdbi.create(jdbcUrl, user, password); int id = 3; Optional<String> res = jdbi.withHandle(handle -> handle.select("SELECT name FROM cars WHERE id = ?", id) .mapTo(String.class) .findOne()); res.ifPresentOrElse(System.out::println, () -> System.out.println("N/A")); }
该示例使用 Optional
类型作为返回值。
参数绑定
参数绑定是一种防止 SQL 注入漏洞并提高代码可读性的机制。 您可以将值绑定到 SQL 语句中的占位符,JDBI 会处理为数据库执行设置实际值。
绑定类型
- 位置
- 命名
- bean 绑定
以下是一个位置绑定示例
import org.jdbi.v3.core.Jdbi; import org.jdbi.v3.core.mapper.reflect.ConstructorMapper; import java.util.Optional; void main() { String jdbcUrl = "jdbc:postgresql://:5432/testdb"; String user = "postgres"; String password = "s$cret"; Jdbi jdbi = Jdbi.create(jdbcUrl, user, password); int id = 3; Optional<Car> res = jdbi.withHandle(handle -> handle.select("SELECT * FROM cars WHERE id = ?", id) .registerRowMapper(Car.class, ConstructorMapper.of(Car.class)) .mapTo(Car.class) .findOne()); res.ifPresentOrElse(System.out::println, () -> System.out.println("N/A")); } public record Car(int id, String name, int price) { }
该示例将一行映射到 Car
记录。 registerRowMapper
方法为 Car
注册一个自动映射器。
以下示例使用命名绑定。
import org.jdbi.v3.core.Jdbi; import org.jdbi.v3.core.mapper.reflect.ConstructorMapper; import java.util.Optional; void main() { String jdbcUrl = "jdbc:postgresql://:5432/testdb"; String user = "postgres"; String password = "s$cret"; Jdbi jdbi = Jdbi.create(jdbcUrl, user, password); int id = 3; Optional<Car> res = jdbi.withHandle(handle -> handle.select("SELECT * FROM cars WHERE id = :id") .registerRowMapper(Car.class, ConstructorMapper.of(Car.class)) .bind("id", id) .mapTo(Car.class) .findOne()); res.ifPresentOrElse(System.out::println, () -> System.out.println("N/A")); } public record Car(int id, String name, int price) { }
此外,我们不是在 select
方法中传递值,而是调用 bind
方法,我们在其中将 id
名称绑定到 id
变量的值。
$ java Main.java Car[id=3, name=Skoda, price=9000]
将行映射到列表
在下一个示例中,我们将多行映射到汽车列表。
import org.jdbi.v3.core.Jdbi; import org.jdbi.v3.core.mapper.reflect.ConstructorMapper; import java.util.List; void main() { String jdbcUrl = "jdbc:postgresql://:5432/testdb"; String user = "postgres"; String password = "s$cret"; Jdbi jdbi = Jdbi.create(jdbcUrl, user, password); List<Car> cars = jdbi.withHandle(handle -> handle.select("SELECT * FROM cars") .registerRowMapper(Car.class, ConstructorMapper.of(Car.class)) .mapTo(Car.class) .list()); if (cars.isEmpty()) { System.out.println("No cars found."); } else { System.out.println("List of cars:"); cars.forEach(System.out::println); } } public record Car(int id, String name, int price) { }
在该示例中,我们从表中选择所有汽车。 我们将返回的行映射到汽车列表。
$ java Main.java List of cars: Car[id=1, name=Audi, price=52642] Car[id=2, name=Mercedes, price=57127] Car[id=3, name=Skoda, price=9000] Car[id=4, name=Volvo, price=29000] Car[id=5, name=Bentley, price=350000] Car[id=6, name=Citroen, price=21000] Car[id=7, name=Hummer, price=41400] Car[id=8, name=Volkswagen, price=21600]
事务管理
JDBI 使事务管理变得容易。 您可以在事务中执行多个操作,并在发生错误时回滚。
import org.jdbi.v3.core.Jdbi; void main() { String jdbcUrl = "jdbc:postgresql://:5432/testdb"; String user = "postgres"; String password = "s$cret"; Jdbi jdbi = Jdbi.create(jdbcUrl, user, password); try { jdbi.useTransaction(handle -> { handle.execute("INSERT INTO cars(name, price) VALUES('Toyota', 25000)"); handle.execute("INSERT INTO cars(name, price) VALUES('Mazda', 22000)"); // Uncomment the next line to simulate an error and trigger rollback // if (true) throw new RuntimeException("Simulated error"); }); System.out.println("Transaction committed."); } catch (Exception e) { System.out.println("Transaction rolled back: " + e.getMessage()); } }
useTransaction
方法确保所有语句都在事务中执行。 如果抛出异常,则事务将回滚。
自定义 RowMapper
您可以创建一个自定义 RowMapper
,以将复杂的查询结果映射到 Java 对象,例如在连接表时。
package com.zetcode; import org.jdbi.v3.core.Jdbi; import org.jdbi.v3.core.mapper.RowMapper; import org.jdbi.v3.core.statement.StatementContext; import org.h2.jdbcx.JdbcDataSource; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; class CarWithOwner { public final String carName; public final String ownerName; public CarWithOwner(String carName, String ownerName) { this.carName = carName; this.ownerName = ownerName; } public String toString() { return carName + " owned by " + ownerName; } } class CarWithOwnerMapper implements RowMapper<CarWithOwner> { @Override public CarWithOwner map(ResultSet rs, StatementContext ctx) throws SQLException { return new CarWithOwner(rs.getString("car_name"), rs.getString("owner_name")); } } public class Main { public static void main(String[] args) { // Setup H2 Database JdbcDataSource dataSource = new JdbcDataSource(); dataSource.setURL("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"); dataSource.setUser("sa"); dataSource.setPassword(""); Jdbi jdbi = Jdbi.create(dataSource); // Create tables jdbi.useHandle(handle -> { handle.execute(""" CREATE TABLE owners ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) ) """); handle.execute(""" CREATE TABLE cars ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), owner_id BIGINT, FOREIGN KEY (owner_id) REFERENCES owners(id) ) """); // Insert test data handle.execute("INSERT INTO owners (name) VALUES (?)", "Alice"); handle.execute("INSERT INTO owners (name) VALUES (?)", "Bob"); handle.execute("INSERT INTO cars (name, owner_id) VALUES (?, ?)", "Toyota", 1); handle.execute("INSERT INTO cars (name, owner_id) VALUES (?, ?)", "Honda", 2); }); // Query data with JDBI List<CarWithOwner> list = jdbi.withHandle(handle -> handle.createQuery("SELECT c.name AS car_name, o.name AS owner_name FROM cars c JOIN owners o ON c.owner_id = o.id") .map(new CarWithOwnerMapper()) .list() ); // Print results list.forEach(System.out::println); } }
本示例演示如何使用自定义 RowMapper
将连接的表结果映射到自定义对象。
SqlObjects
Jdbi SqlObjects 是 Jdbi 库的扩展,提供了一种声明式方式来与 Java 中的关系数据库进行交互。
<dependency> <groupId>org.jdbi</groupId> <artifactId>jdbi3-sqlobject</artifactId> <version>3.45.2</version> </dependency>
我们需要添加 jdbi3-sqlobject
依赖项。
import org.jdbi.v3.core.Jdbi; import org.jdbi.v3.core.mapper.reflect.ConstructorMapper; import org.jdbi.v3.sqlobject.SqlObject; import org.jdbi.v3.sqlobject.SqlObjectPlugin; import org.jdbi.v3.sqlobject.statement.SqlQuery; import java.util.Optional; void main() { String jdbcUrl = "jdbc:postgresql://:5432/testdb"; String user = "postgres"; String password = "s$cret"; Jdbi jdbi = Jdbi.create(jdbcUrl, user, password); jdbi.installPlugin(new SqlObjectPlugin()); jdbi.registerRowMapper(Car.class, ConstructorMapper.of(Car.class)); CarDao carDao = jdbi.onDemand(CarDao.class); int searchId = 2; Optional<Car> car = carDao.findById(searchId); if (car.isPresent()) { System.out.println("Car found: " + car.get()); } else { System.out.println("Car with id " + searchId + " not found."); } } public record Car(int id, String name, int price) { } public interface CarDao extends SqlObject { @SqlQuery("SELECT * FROM cars WHERE id = ?") Optional<Car> findById(int id); }
在该程序中,我们使用 CarDao
接口中函数声明的 @SqlQuery
定义查询。
CarDao carDao = jdbi.onDemand(CarDao.class);
DAO 对象使用 onDemand
创建。
int searchId = 2; Optional<Car> car = carDao.findById(searchId);
我们在 DAO 对象上调用 findById
。
来源
在本文中,我们使用了 Java Jdbi 库。
作者
列出所有Java教程。