新Spring Boot 3.2 Jdbc client简介

新的Jdbc client已添加到 Spring Framework 6.1 和 Spring Boot 3.2 中,查询创建更加流畅、更易于阅读和理解。

  • 一个非常有趣的功能是自动配置:这意味着我们只需要写下应用程序的 Bean,我们就会有一个Jdbc Client的实例。就这么简单!
  • 另一个有趣的功能是可以处理不可变对象(如 Record),这是 Java 14 新增的功能,而过去使用Jdbc Template 无法处理 Record。

让我们开始编写代码!
首先,我创建了一个记录(Record)来代表我们应用程序的数据库实体。

public record Product(Long id, String description) {}

然后,我创建了一个 @ProductController 控制器来接收 REST 请求:

@RestController
@RequestMapping("/products")
public class ProductController {

  private ProductService service;

  public ProductController(ProductService service) {
    this.service = service;
  }

  @GetMapping(
"/{id}")
  public Product findBy(@PathVariable Long id) {
    var product = service.findBy(id);
    if (product.isEmpty()) {
      throw new ProductNotFoundException(id);
    }
    return product.get();
  }

  @GetMapping
  public List<Product> findAll() {
    return service.findAll();
  }

  @PostMapping
  public void create(@RequestBody Product product) {
    service.create(product);
  }

  @DeleteMapping(
"/{id}")
  public void deleteBy(@PathVariable Long id) {
    service.delete(id);
  }

  @PutMapping
  public void update(@RequestBody Product product) {
    service.update(product);
  }
}

如果你想导入所有端点,只需将 collections.json 文件加载到 Postman、Insomnia 或你最喜欢的 api 客户端。

我们的 @Service 层只是 @Repository 调用的一个抽象,因此本文将省略它。

比较
为了了解 Jdbc 客户端在多大程度上提高了代码的流畅性和可读性,让我们对两者进行一下比较。

首先,让我们看看 ProductRepository 使用以前的 JdbcTemplate模板创建方法时的样子:

@Repository
public class ProductRepository {

  private final JdbcTemplate jdbcTemplate;

  public ProductRepository(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate;
  }

  var rowMapper = (rs, rowNum) -> new Product(
    rs.getString("id"),
    rs.getString(
"description")
  );

  public void create(Product product) {
    var sql =
"INSERT INTO product(id, description) values(?,?)";
    return jdbcTemplate.update(sql, product.id(), product.description());
  }
}

如果我们以使用新的 Jdbc 客户端 API 的创建方法为例,它看起来会是这样:

@Repository
public class ProductRepository {

  private final JdbcClient jdbcClient;

  public ProductRepository(JdbcClient jdbcClient) {
    this.jdbcClient = jdbcClient;
  }

  public Integer create(Product product) {
      return jdbcClient
          .sql("INSERT INTO product (id, description) VALUES (:id, :description)")
          .param(
"id", product.id())
          .param(
"description", product.description())
          .update();
    }
}

请注意,sql 方法接收 SQL 命令和要从 SQL 中填写的参数。在 param 方法中,参数的值被传递,这些参数可以被链式调用,从而使调用更流畅、更易读。

完整案例
现在是一个使用 Jdbc 客户端对 ProductRepository 进行 CRUD 的完整示例。

@Repository
public class ProductRepository {

  private final JdbcClient jdbcClient;

  public ProductRepository(JdbcClient jdbcClient) {
    this.jdbcClient = jdbcClient;
  }

  public Optional<Product> findBy(Long id) {
    return jdbcClient
        .sql("SELECT id, description FROM product WHERE id = :id")
        .param(
"id", id)
        .query(Product.class)
        .optional();
  }

  public List<Product> findAll() {
    return this.jdbcClient.sql(
"SELECT id, description FROM product")
        .query(Product.class).list();
  }

  public Integer create(Product product) {
    return jdbcClient
        .sql(
"INSERT INTO product (id, description) VALUES (:id, :description)")
        .param(
"id", product.id())
        .param(
"description", product.description())
        .update();
  }

  public Integer delete(Long id) {
    return jdbcClient.sql(
"DELETE FROM product WHERE id = :id")
        .param(
"id", id).update();
  }

  public Integer update(Product product) {
    return jdbcClient
        .sql(
"UPDATE product SET description = :description WHERE id = :id")
        .param(
"description", product.description())
        .param(
"id", product.id())
        .update();
  }
}

结论
关于使用 Spring Framework 6.1 和 Spring Boot 3.2 中的 Jdbc Client 进行 CRUD 的介绍到此结束。Spring 带来的 api 简化了复杂的任务,并且易于在 Spring 项目中使用和配置。