• 【译】什么时候使用 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 进行查询结果行映射

    以下面的代码为例

    public int getCountOfUsers(String name) {
    
      String sql = "SELECT COUNT(*) FROM users WHERE name = ?";
      return jdbcTemplate.queryForObject(sql, Integer.class, name);
    }```
    
    
    JdbcClientSpring 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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

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

    四、开始使用 JdbcClient

    4.1. Maven

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

    <parent>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-parentartifactId>
      <version>3.2.0-M2version>
      <relativePath/> 
    parent>
    
    <dependencies>
      <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-data-jpaartifactId>
      dependency>
      
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4.2. 配置和初始化

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

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

    @Repository
    public class PersonRepository {
    
      private final JdbcClient jdbcClient;
    
      public PersonRepository(DataSource dataSource) {
        this.jdbcClient = JdbcClient.create(dataSource);
      }
    
      //...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    4.3. 使用 JdbClient 的简单示例

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

    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    五、为 SQL 语句传递参数

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

    5.1. 位置参数

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

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

    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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

    jdbcClient.sql(sql)
      .params("Alex", "Dave", Timestamp.from(Instant.now()))
      .update(keyHolder);
    
    • 1
    • 2
    • 3

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

    jdbcClient.sql(sql)
      .params(List.of("Alex", "Dave", Timestamp.from(Instant.now())))
      .update(keyHolder);
    
    • 1
    • 2
    • 3

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

    jdbcClient.sql(sql)
      .param(1, "Alex")
      .param(2, "Dave")
      .param(3, Timestamp.from(Instant.now()))
      .update()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5.2. 命名参数

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

    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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

    Map<String, ?> paramMap = Map.of(
      "firstName", "Alex",
      "lastName", "Dave",
      "createdAt", Timestamp.from(Instant.now())
    );
    
    jdbcClient.sql(sql)
      .params(paramMap)
      .update();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

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

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

    Person person = new Person(null, "Clark", "Kent", Instant.now());
    
    jdbcClient.sql(sql)
      .paramSource(person)
      .update();
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    public record Person(Long id, String firstName, String lastName, Instant createdAt) {
    }
    
    • 1
    • 2

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

    SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(person);
    
    jdbcClient.sql(sql)
      .paramSource(namedParameters)
      .update();
    
    • 1
    • 2
    • 3
    • 4
    • 5

    六、将结果集映射到对象

    6.1. 使用自定义 RowMapper

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

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

    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;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

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

    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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    6.2. 使用类 Mapping

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

    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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    public record Person(Long id, String firstName, String lastName, Instant createdAt) {
    }
    
    • 1
    • 2

    七、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 上的源码地址

  • 相关阅读:
    HttpServletResponse HttpServletRequest
    Union-Find Algorithm-并查集
    java中Collection.stream()流用法详解
    UART、RS232、RS485、I2C和SPI的介绍
    【Golang星辰图】图像和多媒体处理的创新之路:Go语言的无限潜能
    针对KF状态估计的电力系统虚假数据注入攻击研究(Matlab代码实现)
    基于OpenHarmony的智能喝水提醒器
    对极几何(Epipolar Geometry)
    Leetcode899 有序队列Orderly Queue
    一、nginx安装第三方模块[apt安装、源码安装]
  • 原文地址:https://blog.csdn.net/xieshaohu/article/details/134544734