JdbcClient与JdbcTemplate比较

Spring 框架提供了几种不同的数据库访问方法,其中有可直接执行SQL的统一API,这种方法的关键组件包括JdbcTemplate、NamedParameterJdbcTemplate和JdbcClient。

从Spring 6.1开始提供了JdbcClient,它为 JDBC 查询和更新操作也提供了统一的客户端 API,提供了更加流畅和简化的交互模型。

都是基于SQL的JDBC执行器,两者有啥区别?

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);
}

以下是使用JdbcTemplate实现CRUD代码:

@Service
public class TemplatePostService implements PostService {

    private static final Logger log = LoggerFactory.getLogger(TemplatePostService.class);
    private final JdbcTemplate jdbcTemplate;

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

    RowMapper<Post> rowMapper = (rs, rowNum) -> new Post(
            rs.getString("id"),
            rs.getString("title"),
            rs.getString("slug"),
            rs.getDate("date").toLocalDate(),
            rs.getInt("time_to_read"),
            rs.getString("tags")
    );

    @Override
    public List<Post> findAll() {
        var sql = "SELECT id,title,slug,date,time_to_read,tags FROM post";
        return jdbcTemplate.query(sql, rowMapper);
    }

    @Override
    public Optional<Post> findById(String id) {
        var sql = "SELECT id,title,slug,date,time_to_read,tags FROM post WHERE id = ?";
        Post post = null;
        try {
            post = jdbcTemplate.queryForObject(sql,rowMapper,id);
        } catch (DataAccessException ex) {
            log.info("Post not found: " + id);
        }

        return Optional.ofNullable(post);
    }

    @Override
    public void create(Post post) {
        String sql = "INSERT INTO post(id,title,slug,date,time_to_read,tags) values(?,?,?,?,?,?)";
        int insert = jdbcTemplate.update(sql,post.id(),post.title(),post.slug(),post.date(),post.timeToRead(),post.tags());
        if(insert == 1) {
            log.info("New Post Created: " + post.title());
        }
    }

    @Override
    public void update(Post post, String id) {
        String sql = "update post set title = ?, slug = ?, date = ?, time_to_read = ?, tags = ? where id = ?";
        int update = jdbcTemplate.update(sql,post.title(),post.slug(),post.date(),post.timeToRead(),post.tags(),id);
        if(update == 1) {
            log.info("Post Updated: " + post.title());
        }
    }

    @Override
    public void delete(String id) {
        String sql = "delete from post where id = ?";
        int delete = jdbcTemplate.update(sql,id);
        if(delete == 1) {
            log.info("Post Deleted: " + id);
        }
    }
}

JdbcTemplate中的每个查询方法都需要一个行映射器RowMapper来将数据库表中的列映射到记录Post,这可能是一项相当艰巨的任务。如上述代码中的:

RowMapper<Post> rowMapper = (rs, rowNum) -> new Post(
            rs.getString("id"),
            rs.getString("title"),
            rs.getString("slug"),
            rs.getDate("date").toLocalDate(),
            rs.getInt("time_to_read"),
            rs.getString("tags")
    );

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

它具有以下特点:

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

代码:

public int getCountOfUsers(String name) {

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

与 JdbcTemplate相比,JdbcClient的接口更简洁、更易读,使代码编写更流畅、更直观。

如果要获取一个 JDBC 客户端实例。Spring Boot 3.2 的自动配置 JDBC 客户端也会交给我们,下面是CRUD代码:

@Service
public class ClientPostService implements PostService {

  private final JdbcClient jdbcClient;

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

  @Override
  public List<Post> findAll() {
    return jdbcClient.sql("SELECT id,title,slug,date,time_to_read,tags FROM post")
      .query(Post.class)
      .list();
  }

  @Override
  public Optional<Post> findById(String id) {
    return jdbcClient.sql("SELECT id,title,slug,date,time_to_read,tags FROM post WHERE id = :id")
      .param("id", id)
      .query(Post.class)
      .optional();
  }

  @Override
  public void create(Post post) {
    int update = jdbcClient.sql("INSERT INTO post(id,title,slug,date,time_to_read,tags) values(?,?,?,?,?,?)")
      .params(List.of(post.id(), post.title(), post.slug(), post.date(), post.timeToRead(), post.tags()))
      .update();

    Assert.state(update == 1, "Failed to create post " + post.title());
  }

  @Override
  public void update(Post post, String id) {
    var updated = jdbcClient.sql("update post set title = ?, slug = ?, date = ?, time_to_read = ?, tags = ? where id = ?")
      .params(List.of(post.title(), post.slug(), post.date(), post.timeToRead(), post.tags(), id))
      .update();

    Assert.state(updated == 1, "Failed to update post " + post.title());
  }

  @Override
  public void delete(String id) {
    var updated = jdbcClient.sql("delete from post where id = :id")
      .param("id", id)
      .update();

    Assert.state(updated == 1, "Failed to delete post " + id);
  }

}

与JdbcTemplate相比,JdbcClient没有RowMapper,节省了数据库表字段核对象字段之间手工编程的繁杂任务。

总之,Spring Framework 6.1 和 Spring Boot 3.2 中新引入的 JDBC 客户端提高了编程效率,节省了编程时间。