【译】什么时候使用 Spring 6 JdbcClient

原文地址:Spring 6 JdbcClient: When and How to Use it?

一、前言

Spring 6.1 起,JdbcClient 为 JDBC 查询和更新操作提供了统一的客户端 API,从而提供了更流畅、更简化的交互模型。本教程演示了如何在各种场景中使用 JdbcClient。

二、Spring 中的数据库访问方法

Spring 框架提供了几种不同的数据库访问方法。两种流行的方法是

  • 直接执行 SQL 语句的统一 API,例如 JdbcTemplate
  • ORM 框架支持,例如 Hibernate、JPA

统一 API 提供了一种直接高效的方法,允许开发人员以更直接的方式处理 SQL 查询。这种方法的关键组件包括:JdbcTemplate、NamedParameterJdbcTemplate 和 JdbcClient。

对象关系映射(ORM)框架(如 Hibernate)为关系数据库提供了一个抽象层,允许开发人员使用面向对象范例与数据库交互。这使开发人员能够将 Java 对象映射到数据库表,封装并隔离 SQL 查询的细节。

在直接 SQL 执行和 ORM 框架支持之间做出选择取决于多种因素,包括应用程序的复杂性、开发人员的偏好和具体的项目要求。直接 SQL 执行因其简单性和可控性而受到青睐,而 ORM 框架则在优先考虑面向对象设计和抽象的情况下表现出色。

三、JdbcClient 与 JdbcTemplate 的区别

JdbcTemplate 是 Spring Data 中的一个核心类,可简化 JDBC 的使用并消除与传统 JDBC 使用相关的大量模板代码。它提供了执行 SQL 查询、更新和存储过程的方法。

它具有以下功能:

  • 执行 SQL 查询、更新和存储过程
  • 使用? 占位符传递查询参数
  • 通过 RowMapper 或 ResultSetExtractor 进行查询结果行映射

以下面的代码为例

java 复制代码
public int getCountOfUsers(String name) {

  String sql = "SELECT COUNT(*) FROM users WHERE name = ?";
  return jdbcTemplate.queryForObject(sql, Integer.class, name);
}```


JdbcClient 是 Spring 6.1 中引入的增强型统一 JDBC 客户端 API,为命名和位置参数语句提供了流畅的交互模型。它旨在进一步简化 JDBC 操作。

它具有以下功能:

 - 统一支持命名参数和位置参数
 - 旨在进一步简化 JDBC 操作
 - 作为不断发展的 Spring 框架的一部分引入

以下面的代码为例

```java
public int getCountOfUsers(String name) {

  return jdbcClient.sql("SELECT COUNT(*) FROM users WHERE name = ?")
    .param(name)
    .query(Integer.class)
    .single();
}

在 JdbcTemplate 和 JdbcClient 之间做出选择取决于项目的上下文、Spring 版本和开发人员的偏好。这两种工具都能简化数据库交互,各有各的优势和用例。

四、开始使用 JdbcClient

4.1. Maven

要在 Spring 应用程序中使用 JdbcClient,我们必须确保项目使用的是 Spring 6.1 或 Spring Boot 3.2 的最低版本。

xml 复制代码
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>3.2.0-M2</version>
  <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  <!-- other dependencies -->
</dependencies>

4.2. 配置和初始化

要配置 JdbcClient,请确保 Repository 类中包含了 DataSource Bean

一旦配置完成,JdbcClient 类的实例是线程安全的。一种常用的方法是将 DataSource Bean 依赖注入到 Repository/Dao 类中。JdbcClient 是在存储库构造函数中使用语句 JdbcClient.create() 创建的。Spring 框架会使用构造器注入自动注入 DataSource Bean。

java 复制代码
@Repository
public class PersonRepository {

  private final JdbcClient jdbcClient;

  public PersonRepository(DataSource dataSource) {
    this.jdbcClient = JdbcClient.create(dataSource);
  }

  //...
}

4.3. 使用 JdbClient 的简单示例

一旦构建了 JdbcClient 实例,我们就可以使用它的便捷方法来执行 SQL 查询。

java 复制代码
public Optional<Person> findById(Long id) {

  String sql = "select id, first_name, last_name, created_at from person where id = :id";

  return jdbcClient.sql(sql)
    .param("id", id)
    .query((rs, rowNum) -> new Person(
        rs.getInt("id"), 
        rs.getString("first_name"), 
        rs.getString("last_name"), 
        rs.getTimestamp("created_at").toInstant()))
    .optional();
}

五、为 SQL 语句传递参数

JdbcClient API 在接受 SQL 参数方面非常灵活。让我们来看看几种方法。

5.1. 位置参数

位置参数是查询语句中的占位符,通过其在语句中的位置顺序来识别。这些参数在 SQL 语句中用占位符?

在下面的示例中,查询参数 first_name、last_name 和 created_at 是按分配给方法 StatementSpec.param() 的顺序隐式注册的。

java 复制代码
String sql = "insert into person(first_name, last_name, created_at) values (?, ?, ?)";

jdbcClient.sql(sql)
  .param("Alex")
  .param("Dave")
  .param(Timestamp.from(Instant.now()))
  .update();

我们还可以使用 StatementSpec.params() 方法将参数作为 var-args 传入,如下所示:

java 复制代码
jdbcClient.sql(sql)
  .params("Alex", "Dave", Timestamp.from(Instant.now()))
  .update(keyHolder);

此外,还可以将参数传递为 List。

java 复制代码
jdbcClient.sql(sql)
  .params(List.of("Alex", "Dave", Timestamp.from(Instant.now())))
  .update(keyHolder);

如果我们想进一步确保按正确顺序绑定参数,我们甚至可以传递参数索引,以确保万无一失。

java 复制代码
jdbcClient.sql(sql)
  .param(1, "Alex")
  .param(2, "Dave")
  .param(3, Timestamp.from(Instant.now()))
  .update()

5.2. 命名参数

与 NamedParameterJdbcTemplate 类似,JdbcClient 也支持使用占位符 ":paramName" 格式命名 SQL 语句参数。

java 复制代码
String sql = "insert into person(first_name, last_name, created_at) values (:firstName, :lastName, :createdAt)";

jdbcClient.sql(sql)
  .param("firstName, "Alex")
  .param("lastName", "Dave")
  .param("createdAt", Timestamp.from(Instant.now()))
  .update();

也可以以 Map 的形式传递命名参数,其中键代表命名参数,值在运行时传递给查询。

java 复制代码
Map<String, ?> paramMap = Map.of(
  "firstName", "Alex",
  "lastName", "Dave",
  "createdAt", Timestamp.from(Instant.now())
);

jdbcClient.sql(sql)
  .params(paramMap)
  .update();

5.3. 对象实例参数(Parameter Source)

为了让事情变得更简单,也可以传递一个字段名与命名参数相匹配的对象(记录类、带有 bean 属性的类或普通字段持有者)。

在下面的示例中,我们使用 Person 对象中的值对数据库执行 INSERT 操作。

java 复制代码
Person person = new Person(null, "Clark", "Kent", Instant.now());

jdbcClient.sql(sql)
  .paramSource(person)
  .update();

Person 类是一种记录类型,它的字段名与命名参数相匹配。

java 复制代码
public record Person(Long id, String firstName, String lastName, Instant createdAt) {
}

同样,我们也可以使用 SimplePropertySqlParameterSourceBeanPropertySqlParameterSource 策略。

java 复制代码
SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(person);

jdbcClient.sql(sql)
  .paramSource(namedParameters)
  .update();

六、将结果集映射到对象

6.1. 使用自定义 RowMapper

从数据库中查询行时,我们可以使用结果集检索列的值,如第 3.3 节所示。但如果我们想增加灵活性和代码的简洁性,可以考虑使用 RowMapper。

下面的 PersonRowMapper 类实现了 RowMapper 接口,并覆盖了 mapRow() 方法,该方法包含将数据库行映射到 Person 实例的逻辑。

java 复制代码
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.Instant;
import com.howtodoinjava.model.Person;
import org.springframework.jdbc.core.RowMapper;

public class PersonRowMapper implements RowMapper<Person> {

  private PersonRowMapper() {}

  private static final PersonRowMapper INSTANCE = new PersonRowMapper();

  public static PersonRowMapper getInstance() {
    return INSTANCE;
  }

  @Override
  public Person mapRow(ResultSet rs, int rowNum) throws SQLException {
    return new Person(
      rs.getLong("id"),
      rs.getString("first_name"),
      rs.getString("last_name"),
      getInstantFromTimestamp(rs.getTimestamp("created_at"))
    );
  }

  private Instant getInstantFromTimestamp(Timestamp timestamp) {
    return (timestamp != null) ? timestamp.toInstant() : null;
  }
}

现在,我们可以在 query() 方法中使用 PersonRowMapper,Spring 会在内部使用映射器,并直接获取 Person 实例。

java 复制代码
String querySql = "select id, first_name, last_name, created_at from person where id = :id";

Optional<Person> personOptional = jdbcClient.sql(querySql)
  .param("id", 1)
  .query(PersonRowMapper.getInstance())
  .optional();

6.2. 使用类 Mapping

如果因为在类字段和数据库列之间有一个直接的字段映射,而创建 PersonRowMapper 显得很费力,那么可以直接传递 query() 方法的类类型,这样也可以。

java 复制代码
String querySql = "select id, first_name, last_name, created_at from person where id = :id";

Optional<Person> personOptional = jdbcClient.sql(querySql)
  .param("id", 1)
  .query(Person.class)
  .optional();

译者注:再看一下 Person 的记录类是如何定义的,看起来会自动完成下划线分隔命名和驼峰命名的转换。

java 复制代码
public record Person(Long id, String firstName, String lastName, Instant createdAt) {
}

七、SQL 查询和更新操作

JdbcClient 支持所有类型的数据库操作,如选择、创建、更新和删除记录。

  • query() 方法执行给定的 SQL查询,返回的查询结果提供了多个封装方法,如映射类(Class Mapping)、行映射器(RowMapper)、行回调处理程序(RowCallbackHandler)和结果集提取器(ResultSetExtractor)
  • update() 方法将提供的 SQL 语句作为更新执行。它总是返回一个受影响行数的 int 值。

这些方法的示例在前面的章节中已经介绍过,不再赘述。

八、批量插入和存储过程的考虑因素

JdbcClient 是一个灵活但非常简化的界面,仅用于 JDBC 查询/更新语句。如果您需要做更复杂的事情,如执行批处理操作调用存储过程,JdbcClient 可能无法提供您需要的所有功能。

在这种情况下,您可能需要使用 Spring 提供的其他工具,如 SimpleJdbcInsert 或 SimpleJdbcCall。

另外,您也可以直接使用更基本的 JdbcTemplate 来完成 JdbcClient 无法完全覆盖的任务。这就好比在工具箱中装有不同的工具,您可以根据工作需要选择最合适的工具。

九、结论

在本教程中,我们探讨了使用 Spring 6 JdbcClient 进行查询和更新操作的初始配置和基础知识。如前所述,JdbcClient 为 JDBC 交互提供了简化和统一的 API,使代码更加简洁易读。

学习愉快

Github 上的源码地址

相关推荐
TDengine (老段)6 分钟前
TDengine IDMP 可视化 —— 分享
大数据·数据库·人工智能·时序数据库·tdengine·涛思数据·时序数据
布局呆星15 分钟前
SpringBoot 基础入门
java·spring boot·spring
GottdesKrieges44 分钟前
OceanBase数据库备份配置
数据库·oceanbase
SPC的存折1 小时前
MySQL 8组复制完全指南
linux·运维·服务器·数据库·mysql
运维行者_1 小时前
OpManager MSP NetFlow Analyzer集成解决方案,应对多客户端网络流量监控挑战
大数据·运维·服务器·网络·数据库·自动化·运维开发
炸炸鱼.3 小时前
Python 操作 MySQL 数据库
android·数据库·python·adb
softshow10263 小时前
Etsy 把 1000 个 MySQL 分片迁进 Vitess
数据库·mysql
Ronaldinho Gaúch3 小时前
MySQL基础
数据库·mysql
不剪发的Tony老师4 小时前
Noir:一款键盘驱动的现代化数据库管理工具
数据库·sql
❀͜͡傀儡师5 小时前
Spring AI Alibaba vs. AgentScope:两个阿里AI框架,如何选择?
java·人工智能·spring