Spring Boot 集成 JdbcTemplate(盘它!)

打印 上一主题 下一主题

主题 937|帖子 937|积分 2811

Spring Boot 集成 JdbcTemplate

基本介绍

JdbcTemplate 概念

JdbcTemplate 是 Spring Framework 提供的一个用于简化 JDBC(Java Database Connectivity)操纵的工具类。它封装了复杂的 JDBC API,使开发者能够更方便、快捷地执行数据库操纵,如查询、插入、更新和删除。通过利用 JdbcTemplate,我们可以制止大量的样板代码,如创建连接、处理 SQL 非常和关闭资源等。这不仅提高了开发效率,还使代码更加简便、可读。
JdbcTemplate 优势

在企业级应用开发中,数据访问层(Data Access Layer,DAL)是核心组成部门。JdbcTemplate 的优势在于:


  • 简化开发:通过封装底层的 JDBC API,减少了代码的重复性,开发者只需专注于业务逻辑的实现。
  • 增强可读性:JdbcTemplate 提供了清晰简便的 API,使得数据库操纵更加直观。
  • 非常处理:JdbcTemplate 同一了 SQL 非常处理,将 SQL 非常转换为 Spring 的 DataAccessException,使得非常处理更加同等。
  • 性能:相比于 JPA 等 ORM 框架,JdbcTemplate 直接基于 SQL 操纵,性能开销更小,得当对性能要求较高的场景。
  • 灵活性:JdbcTemplate 保留了对原始 SQL 的控制权,得当需要手动优化 SQL 的场景。
JdbcTemplate 应用场景

JdbcTemplate 实用于以下几种场景:


  • 轻量级应用:在不需要复杂 ORM 框架的轻量级应用中,利用 JdbcTemplate 可以实现更高的开发效率和性能。
  • 性能关键型应用:在对数据库访问性能要求较高的场景下,JdbcTemplate 由于其直接基于 JDBC 操纵的特性,能够提供更好的性能表现。
  • 复杂查询场景:在需要执行复杂 SQL 查询并需要手动优化 SQL 性能的场景下,JdbcTemplate 使得开发者能够完全控制 SQL 的执行过程。
  • 数据迁移与批量操纵:在大数据量的数据迁移或批量处理任务中,JdbcTemplate 的批量操纵支持能够明显提高效率。
NamedParameterJdbcTemplate 概念

NamedParameterJdbcTemplate 是 JdbcTemplate 的一个扩展版本,它引入了命名参数的概念,使得 SQL 语句更加易读和维护。利用 NamedParameterJdbcTemplate,可以通过参数名称而不是位置来绑定参数,制止了 SQL 中的位置参数(?)带来的混淆和维护本钱。对于复杂 SQL 操纵和需要动态构建 SQL 的场景,NamedParameterJdbcTemplate 提供了更大的灵活性。
准备工作

在利用 JdbcTemplate 和 NamedParameterJdbcTemplate 之前,首先需要进行一些基础的准备工作。这部门内容包括项目情况的配置、数据库的设置以及实体类与数据库表的映射。这些准备工作是整个项目开发的基础,确保我们在接下来的实践中能够顺利地实现各种数据库操纵。
项目情况配置

Spring Boot版本选择

Spring Boot 提供了开箱即用的功能,极大地简化了 Spring 应用的开发。在选择 Spring Boot 版本时,发起利用最新版的稳固版本,以得到最新的功能和性能优化,同时确保兼容性和安全性。
推荐版本:


  • Spring Boot 2.x:如果项目已有一段时间,且对新特性需求不高,2.x 系列是一个成熟稳固的选择。
  • Spring Boot 3.x:对于新项目或需要利用 Java 17 及以上版本的新特性,3.x 系列更为符合,提供了更多优化和现代化的开发体验。
Maven 依赖配置

在 Spring Boot 项目中,JdbcTemplate 和 NamedParameterJdbcTemplate 默认包罗在 spring-boot-starter-jdbc 依赖中,因此只需在项目的构建文件中添加相关依赖即可。
Maven 配置:
  1. <dependency>
  2.     <groupId>org.springframework.boot</groupId>
  3.     <artifactId>spring-boot-starter-jdbc</artifactId>
  4. </dependency>
  5. <dependency>
  6.     <groupId>mysql</groupId> <!-- 这里以MySQL为例,其他数据库请根据需求配置 -->
  7.     <artifactId>mysql-connector-java</artifactId>
  8.     <scope>runtime</scope>
  9. </dependency>
复制代码
这些依赖将会自动下载并配置所有必要的库,确保项目中可以直接利用 JdbcTemplate 和 NamedParameterJdbcTemplate。
数据库配置

Spring Boot 提供了多种方式来配置数据库连接信息。推荐利用 application.yml 文件进行配置,由于它结构清晰,便于阅读和维护。
application.yml 配置示例:
  1. spring:
  2.   datasource:
  3.     url: jdbc:mysql://localhost:3306/db_boot?useSSL=false&serverTimezone=UTC
  4.     username: root
  5.     password: root
  6.     driver-class-name: com.mysql.cj.jdbc.Driver
复制代码
实体类与数据库表映射

创建实体类

假设我们要管理一个简单的用户信息表 users,每个用户具有 id、name、email 和 age 属性。我们可以创建一个相应的实体类 User:
  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. @RequiredArgsConstructor
  5. public class User {
  6.     private Long id;
  7.     private String name;
  8.     private String email;
  9.     private Integer age;
  10. }
复制代码
数据库表的设计与创建

在 MySQL 中,我们可以通过以下 SQL 语句来创建与 User 实体类对应的 users 表:
  1. CREATE TABLE users (
  2.     id BIGINT AUTO_INCREMENT PRIMARY KEY,
  3.     name VARCHAR(100) NOT NULL,
  4.     email VARCHAR(100) NOT NULL,
  5.     age INT
  6. );
复制代码
JdbcTemplate 基础用法

JdbcTemplate 注入方式

在 Spring Boot 中,JdbcTemplate 通常通过依赖注入的方式来利用。我们可以通过构造器注入或字段注入的方式将 JdbcTemplate 注入到服务类中。
通过构造器注入

构造器注入是推荐的依赖注入方式,由于它可以确保依赖项在对象创建时即被初始化。下面是一个示例,展示了怎样通过构造器注入 JdbcTemplate:
  1. @Service
  2. public class UserService {
  3.     private final JdbcTemplate jdbcTemplate;
  4.     public UserService(JdbcTemplate jdbcTemplate) {
  5.         this.jdbcTemplate = jdbcTemplate;
  6.     }
  7. }
复制代码
通过字段注入

固然构造器注入是推荐的方式,但在某些情况下,字段注入也被广泛利用,尤其是在较小的项目中。字段注入利用 @Autowired 注解来自动注入依赖项:
  1. @Service
  2. public class UserService {
  3.     @Autowired
  4.     private JdbcTemplate jdbcTemplate;
  5. }
复制代码
这种方式固然利用方便,但缺点是无法通过构造函数确保依赖项的注入。
数据库查询

JdbcTemplate 提供了丰富的方法用于执行各种类型的数据库查询操纵,包括查询单条记录、多条记录以及返回 Map 或 List 效果。
查询单条记录(queryForObject)

在 JdbcTemplate 中,queryForObject 方法用于查询单条记录并将其映射为对象。随着 Spring Framework 的发展,一些旧方法被标志为过时,并引入了新的方法以提高灵活性和易用性。
方法签名分析:
  1. @Nullable
  2. public <T> T queryForObject(String sql, Object[] args, int[] argTypes, RowMapper<T> rowMapper) throws DataAccessException {
  3.     List<T> results = (List)this.query(sql, args, argTypes, (ResultSetExtractor)(new RowMapperResultSetExtractor(rowMapper, 1)));
  4.     return DataAccessUtils.nullableSingleResult(results);
  5. }
复制代码
参数寄义:


  • String sql: SQL 查询语句,通常包罗一个 SELECT 语句,大概包罗参数占位符。
  • Object[] args: 参数数组,用于更换 SQL 语句中的占位符(?)。
  • int[] argTypes: 参数类型数组,对应 args 中每个参数的 JDBC 类型(如 Types.INTEGER, Types.VARCHAR 等)。
  • RowMapper<T> rowMapper: RowMapper 是一个接口,用于将 ResultSet 中的一行数据映射为 Java 对象。
处理逻辑如下:


  • 该方法首先利用 query 方法执行 SQL 查询,传入参数和对应的参数类型,并将 ResultSet 的处理委托给 RowMapperResultSetExtractor,该类内部利用 RowMapper 来处理每一行的数据映射。
  • 然后,利用 DataAccessUtils.nullableSingleResult(results) 从效果会合获取单条记录。这个方法会确保返回效果为单条记录,如果效果集为空,返回 null,如果有多条记录则抛出非常。
⚠ 过时的 queryForObject 方法:
  1. @Deprecated
  2. @Nullable
  3. public <T> T queryForObject(String sql, @Nullable Object[] args, RowMapper<T> rowMapper) throws DataAccessException {
  4.     List<T> results = (List)this.query((String)sql, (Object[])args, (ResultSetExtractor)(new RowMapperResultSetExtractor(rowMapper, 1)));
  5.     return DataAccessUtils.nullableSingleResult(results);
  6. }
复制代码
这个方法与前一个方法的告急区别在于它没有 argTypes 参数,实用于不需要明确指定参数类型的情况。由于 JDBC 参数类型不明确大概导致一些潜在问题,因此该方法已被标志为过时。
推荐的 queryForObject 方法:
  1. @Nullable
  2. public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args) throws DataAccessException {
  3.     List<T> results = (List)this.query((String)sql, (Object[])args, (ResultSetExtractor)(new RowMapperResultSetExtractor(rowMapper, 1)));
  4.     return DataAccessUtils.nullableSingleResult(results);
  5. }
复制代码
这个方法是推荐的利用方式,通过变长参数 Object... args 提供参数,并且结合了 RowMapper 以灵活地处理查询效果。
处理逻辑如下:


  • 与前面的方法类似,queryForObject 调用了 query 方法来执行 SQL 查询,参数利用 Object... args 来灵活传递。
  • 通过 RowMapper 将 ResultSet 中的数据映射为对象,然后利用 DataAccessUtils.nullableSingleResult(results) 提取效果。
因此,当我们希望查询数据库中的单条记录时,可以利用上面这个 queryForObject 方法。这个方法要求 SQL 查询返回一行数据,并将效果映射为指定类型的对象。
  1. /**
  2. * 根据用户ID查找用户
  3. *
  4. * @param id 用户ID,作为查询条件
  5. * @return 匹配的用户对象,若不存在则返回null
  6. */
  7. public User findUserById(Long id) {
  8.     // 定义查询语句,使用 ? 作为占位符,防止 SQL 注入
  9.     String sql = "SELECT * FROM users WHERE id = ?";
  10.     // 使用 jdbcTemplate 的 queryForObject 方法执行查询,并将结果映射为 User 对象
  11.     // 第一个参数为 SQL 语句,第二个参数为参数对象数组,第三个参数为 RowMapper 接口的实现
  12.     // RowMapper 用于将 ResultSet 的每一行映射为一个 User 对象
  13.     return jdbcTemplate.queryForObject(sql, (rs, rowNum) ->
  14.         new User(
  15.             rs.getLong("id"),
  16.             rs.getString("name"),
  17.             rs.getString("email"),
  18.             rs.getInt("age")
  19.         ), id
  20.     );
  21. }
复制代码
查询多条记录(query)

如果查询的效果大概包罗多条记录,可以利用 query 方法。这个方法返回一个包罗所有效果的 List,每个效果可以映射为一个对象。
JdbcTemplate 的 query 方法用于执行 SQL 查询,并将效果集通过不同的方式进行处理。根据不同的需求,query 方法有多个重载版本,可以返回单个对象、列表或通过回调函数逐行处理效果集。
先来看看 query 方法(带 ResultSetExtractor)的源码:
  1. @Nullable
  2. public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
  3.     // 参数检查,确保 SQL 语句和 `ResultSetExtractor` 非空
  4.     Assert.notNull(sql, "SQL must not be null");
  5.     Assert.notNull(rse, "ResultSetExtractor must not be null");
  6.     if (this.logger.isDebugEnabled()) {
  7.         this.logger.debug("Executing SQL query [" + sql + "]");
  8.     }
  9.     class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
  10.         QueryStatementCallback() {
  11.         }
  12.         @Nullable
  13.         public T doInStatement(Statement stmt) throws SQLException {
  14.             ResultSet rs = null;
  15.             Object var3;
  16.             try {
  17.                 // 执行 SQL 查询,获取 `ResultSet`,
  18.                 rs = stmt.executeQuery(sql);
  19.                 // 通过 `ResultSetExtractor` 的 `extractData` 方法处理 `ResultSet`
  20.                 var3 = rse.extractData(rs);
  21.             } finally {
  22.                 // 闭 `ResultSet` 以释放资源
  23.                 JdbcUtils.closeResultSet(rs);
  24.             }
  25.             return var3;
  26.         }
  27.         public String getSql() {
  28.             return sql;
  29.         }
  30.     }
  31.     // 执行查询, 传入 `QueryStatementCallback` 作为回调处理逻辑,最后返回结果
  32.     return this.execute(new QueryStatementCallback(), true);
  33. }
复制代码
参数寄义:


  • String sql: SQL 查询语句。
  • ResultSetExtractor<T> rse: ResultSetExtractor 是一个回调接口,用于处理 ResultSet 并返回一个效果对象。
处理逻辑:


  • 参数检查:首先,利用 Assert.notNull 确保 SQL 语句和 ResultSetExtractor 非空。如果 sql 或 rse 为 null,会抛出 IllegalArgumentException。
  • 日志记录:如果调试模式开启,记录 SQL 查询语句。
  • 内部类 QueryStatementCallback

    • 实现了 StatementCallback<T> 和 SqlProvider 接口。
    • doInStatement 方法中执行 SQL 查询,获取 ResultSet,并通过 ResultSetExtractor 的 extractData 方法处理 ResultSet。
    • 处理完成后,关闭 ResultSet 以开释资源。

  • 执行查询:调用 execute 方法执行查询,传入 QueryStatementCallback 作为回调处理逻辑,最后返回效果。
这个版本的 query 方法非常灵活,实用于自界说 ResultSet 处理逻辑的场景。ResultSetExtractor 接口答应用户界说如那边理整个效果集。

再来看看基于上述方法的别的一个版本 query 方法(带 RowCallbackHandler):
  1. public void query(String sql, RowCallbackHandler rch) throws DataAccessException {
  2.     this.query((String)sql, (ResultSetExtractor)(new RowCallbackHandlerResultSetExtractor(rch)));
  3. }
复制代码
参数寄义:


  • String sql: SQL 查询语句。
  • RowCallbackHandler rch: RowCallbackHandler 是一个回调接口,用于逐行处理 ResultSet 中的每一行数据。
处理逻辑:


  • 该方法内部调用了带 ResultSetExtractor 的 query 方法。
  • 通过 RowCallbackHandlerResultSetExtractor,将每一行的 ResultSet 数据传递给 RowCallbackHandler 处理。
此方法实用于需要逐行处理效果集的场景,而不是将整个效果集映射为一个对象或列表。例如,在需要在处理数据的同时执行某些操纵(如记录日志或更新状态)时,可以利用这种方式。

最后看看 query 方法(带 RowMapper):
  1. public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
  2.     return (List)result((List)this.query((String)sql, (ResultSetExtractor)(new RowMapperResultSetExtractor(rowMapper))));
  3. }
复制代码
参数寄义:


  • String sql: SQL 查询语句。
  • RowMapper<T> rowMapper: RowMapper 是一个接口,用于将 ResultSet 中的每一行数据映射为指定类型的对象。
处理逻辑:


  • 该方法内部调用了 query 方法,通过 RowMapperResultSetExtractor 来处理 ResultSet。
  • RowMapperResultSetExtractor 利用提供的 RowMapper 对象逐行处理效果集,将每一行映射为对象。
  • 返回的效果是一个包罗映射对象的 List<T>。
这个版本的 query 方法实用于需要将查询效果集映射为 Java 对象的场景。通过 RowMapper 接口,开发者可以自界说怎样将数据库表的每一行映射为 Java 对象,得当于 ORM 的需求。
   Note:JdbcTemplate 的 query 方法提供了多种灵活的接口来处理 SQL 查询的效果集:
  

  • 带 ResultSetExtractor 的 query 方法:实用于需要自界说处理整个 ResultSet 的场景。ResultSetExtractor 接口答应开发者在查询完成后一次性处理整个效果集,并将其转换为所需的对象。
  • 带 RowCallbackHandler 的 query 方法:实用于逐行处理 ResultSet 的场景。每一行数据将被传递给 RowCallbackHandler 进行处理,而不是将整个效果集一次性映射为对象或集合。
  • 带 RowMapper 的 query 方法:实用于将效果集的每一行映射为 Java 对象的场景。最终的效果是一个包罗映射对象的 List<T>,得当用作数据访问层的一部门。
  对于查询所有用户的需求,属于 ORM 的场景,不需要额外的特殊处理,可以选用带 RowMapper 的 query 方法:
  1. public List<User> findAllUsers() {
  2.     String sql = "SELECT * FROM users";
  3.     return jdbcTemplate.query(sql, (rs, rowNum) ->
  4.         new User(
  5.                 rs.getLong("id"),
  6.                 rs.getString("name"),
  7.                 rs.getString("email"),
  8.                 rs.getInt("age")
  9.         )
  10.     );
  11. }
复制代码
查询返回 Map

有时候,我们需要直接将查询效果作为 Map 或 List 返回,JdbcTemplate 提供了多种方法来实现这一需求。
queryForMap 方法用于执行 SQL 查询,并将单行效果映射为 Map<String, Object>,其中键为列名,值为相应的字段值。
  1. public Map<String, Object> findUserAsMap(Long id)  {
  2.     String sql = "SELECT * FROM users WHERE id = ?";
  3.     return jdbcTemplate.queryForMap(sql, id);
  4. }
复制代码
方法签名分析:
  1. public Map<String, Object> queryForMap(String sql, Object[] args, int[] argTypes) throws DataAccessException {
  2.     return (Map)result((Map)this.queryForObject(sql, args, argTypes, this.getColumnMapRowMapper()));
  3. }
复制代码
参数寄义:


  • String sql: SQL 查询语句。
  • Object[] args: 参数数组。
  • int[] argTypes: 参数类型数组。
处理逻辑:


  • 该方法调用了 queryForObject 方法,利用 getColumnMapRowMapper() 将查询效果转换为 Map<String, Object>。
  • 返回的效果通过 result() 方法进行处理并返回。
再看看无 argTypes 参数的 queryForMap 方法:
  1. public Map<String, Object> queryForMap(String sql, @Nullable Object... args) throws DataAccessException {
  2.     return (Map)result((Map)this.queryForObject(sql, args, this.getColumnMapRowMapper()));
  3. }
复制代码
这个方法与前者类似,但不需要显式指定参数类型,更加简便,得当不需要明确类型控制的场景。
处理逻辑:


  • 直接利用 Object... args 传递参数,调用 queryForObject 进行查询。
  • 效果映射为 Map<String, Object> 并返回。
查询返回 List

queryForList 方法用于执行查询并返回一个 List,每个元素是查询效果的一行数据,可以是简单类型的列表或 Map 的列表。
方法签名分析:
  1. public <T> List<T> queryForList(String sql, Class<T> elementType) throws DataAccessException {
  2.     return this.query(sql, this.getSingleColumnRowMapper(elementType));
  3. }
复制代码
参数寄义:


  • String sql: SQL 查询语句。
  • Class<T> elementType: 列类型,用于将查询效果中的单列映射为该类型的对象。
该方法利用 getSingleColumnRowMapper(elementType) 来创建一个单列映射器,将效果会合单列数据映射为指定类型的对象,并返回包罗这些对象的列表。
再来看看查询多行数据的 queryForList 方法:
  1. public List<Map<String, Object>> queryForList(String sql) throws DataAccessException {
  2.     return this.query(sql, this.getColumnMapRowMapper());
  3. }
复制代码
该方法利用 getColumnMapRowMapper() 将每行效果映射为 Map<String, Object>,然后返回 List<Map<String, Object>>,每个 Map 代表一行数据。
利用示例:
  1. public List<Map<String, Object>> findAllUserAsMap() {
  2.     String sql = "SELECT * FROM users";
  3.     return jdbcTemplate.queryForList(sql);
  4. }
复制代码
数据库更新与删除

在 JdbcTemplate 中,update 方法用于执行 SQL 的更新、删除和插入操纵。它是对数据库进行写操纵的核心方法之一。该方法有多个重载版本,可以处理不同的场景,包括带参数的 SQL 语句、返回天生的键等。
update 方法(核心实现):
  1. protected int update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss) throws DataAccessException {
  2.     // 日志记录
  3.     this.logger.debug("Executing prepared SQL update");
  4.     // 调用 `execute` 方法,传入 `PreparedStatementCreator` 和 `PreparedStatement` 的回调处理逻辑
  5.     // 通过 updateCount 方法返回受影响的行数。
  6.     return updateCount((Integer)this.execute(psc, (ps) -> {
  7.         boolean var9 = false;
  8.         Integer var4;
  9.         try {
  10.             var9 = true;
  11.             
  12.             // 如果 PreparedStatementSetter (pss) 不为空,则调用 pss.setValues(ps) 方法设置 SQL 语句的参数。
  13.             if (pss != null) {
  14.                 pss.setValues(ps);
  15.             }
  16.             // 执行 ps.executeUpdate() 方法,执行 SQL 更新操作,并返回受影响的行数。
  17.             int rows = ps.executeUpdate();
  18.             if (this.logger.isTraceEnabled()) {
  19.                 this.logger.trace("SQL update affected " + rows + " rows");
  20.             }
  21.             var4 = rows;
  22.             var9 = false;
  23.         } finally {
  24.             if (var9) {
  25.                 // 如果 pss 实现了 ParameterDisposer 接口,则调用 cleanupParameters 方法进行参数清理,确保资源正确释放。
  26.                 if (pss instanceof ParameterDisposer parameterDisposer) {
  27.                     parameterDisposer.cleanupParameters();
  28.                 }
  29.             }
  30.         }
  31.         if (pss instanceof ParameterDisposer parameterDisposerx) {
  32.             parameterDisposerx.cleanupParameters();
  33.         }
  34.         return var4;
  35.     }, true));
  36. }
复制代码
参数寄义:


  • PreparedStatementCreator psc: 用于创建 PreparedStatement 的回调接口,通常用于处理带有参数的 SQL 语句。
  • PreparedStatementSetter pss: 可选的回调接口,用于设置 PreparedStatement 的参数。
处理逻辑:

  • 日志记录:如果调试模式开启,记录 SQL 更新操纵的执行情况。
  • 执行 SQL 语句

    • 调用 execute 方法,传入 PreparedStatementCreator 和 PreparedStatement 的回调处理逻辑。
    • 如果 PreparedStatementSetter (pss) 不为空,则调用 pss.setValues(ps) 方法设置 SQL 语句的参数。
    • 执行 ps.executeUpdate() 方法,执行 SQL 更新操纵,并返回受影响的行数。

  • 处理参数清理:如果 pss 实现了 ParameterDisposer 接口,则调用 cleanupParameters 方法进行参数清理,确保资源正确开释。
  • 返回效果:通过 updateCount 方法返回受影响的行数。

update 方法(简化版本):
  1. public int update(PreparedStatementCreator psc) throws DataAccessException {
  2.     return this.update(psc, (PreparedStatementSetter)null);
  3. }
复制代码
这是 update 方法的简化版本,不利用 PreparedStatementSetter。直接调用核心 update 方法,pss 参数为 null。实用于不需要设置参数的简单 SQL 语句。

update 方法(处理天生的键):
  1. public int update(final PreparedStatementCreator psc, final KeyHolder generatedKeyHolder) throws DataAccessException {
  2.     // 检查 KeyHolder:确保 KeyHolder 非空。
  3.     Assert.notNull(generatedKeyHolder, "KeyHolder must not be null");
  4.     // 日志记录:记录 SQL 更新操作及生成的键。
  5.     this.logger.debug("Executing SQL update and returning generated keys");
  6.    
  7.     // 执行 SQL
  8.     return updateCount((Integer)this.execute(psc, (ps) -> {
  9.         // 执行 ps.executeUpdate() 方法,执行 SQL 更新操作,获取受影响的行数。
  10.         int rows = ps.executeUpdate();
  11.         
  12.         // 清空 KeyHolder 中的键列表,然后通过 storeGeneratedKeys 方法将生成的键存储到 KeyHolder 中。
  13.         generatedKeyHolder.getKeyList().clear();
  14.         this.storeGeneratedKeys(generatedKeyHolder, ps, 1);
  15.         if (this.logger.isTraceEnabled()) {
  16.             this.logger.trace("SQL update affected " + rows + " rows and returned " + generatedKeyHolder.getKeyList().size() + " keys");
  17.         }
  18.         // 返回结果:返回受影响的行数。
  19.         return rows;
  20.     }, true));
  21. }
复制代码
参数寄义:


  • KeyHolder generatedKeyHolder: 用于存储天生的键(如自增主键)的对象。
处理逻辑:

  • 检查 KeyHolder:确保 KeyHolder 非空。
  • 日志记录:记录 SQL 更新操纵及天生的键。
  • 执行 SQL 语句

    • 执行 ps.executeUpdate() 方法,执行 SQL 更新操纵,获取受影响的行数。
    • 清空 KeyHolder 中的键列表,然后通过 storeGeneratedKeys 方法将天生的键存储到 KeyHolder 中。

  • 返回效果:返回受影响的行数。
   TIP:此方法实用于需要在执行插入操纵后获取天生键(如自增 ID)的场景。
  
update 方法(带 PreparedStatementSetter):
  1. public int update(String sql, @Nullable PreparedStatementSetter pss) throws DataAccessException {
  2.     return this.update((PreparedStatementCreator)(new SimplePreparedStatementCreator(sql)), (PreparedStatementSetter)pss);
  3. }
复制代码
处理逻辑:该方法担当一个 SQL 语句和可选的 PreparedStatementSetter,通过 SimplePreparedStatementCreator 创建 PreparedStatement,然后调用核心 update 方法执行 SQL 操纵。
   TIP:实用于简单的 SQL 更新、删除或插入操纵。
  
update 方法(带参数和参数类型):
  1. public int update(String sql, Object[] args, int[] argTypes) throws DataAccessException {
  2.     return this.update(sql, this.newArgTypePreparedStatementSetter(args, argTypes));
  3. }
复制代码
参数寄义:


  • Object[] args: 参数数组,用于更换 SQL 语句中的占位符。
  • int[] argTypes: 参数类型数组,对应 args 中每个参数的 JDBC 类型(如 Types.INTEGER, Types.VARCHAR 等)。
处理逻辑:通过 newArgTypePreparedStatementSetter 方法创建 PreparedStatementSetter,设置参数及其类型,然后调用 update 方法执行 SQL 操纵。
   TIP:实用于需要明确指定参数类型的 SQL 操纵。
  
update 方法(带参数):
  1. public int update(String sql, @Nullable Object... args) throws DataAccessException {
  2.     return this.update(sql, this.newArgPreparedStatementSetter(args));
  3. }
复制代码
参数寄义:


  • Object... args: 参数列表,用于更换 SQL 语句中的占位符。
处理逻辑:通过 newArgPreparedStatementSetter 方法创建 PreparedStatementSetter,仅设置参数(不指定类型),然后调用 update 方法执行 SQL 操纵。
   TIP:实用于无需明确指定参数类型的简单 SQL 操纵。
    Note:
  JdbcTemplate 的 update 方法提供了多种情势,以处理各种更新、删除和插入操纵:
  

  • 核心 update 方法:担当 PreparedStatementCreator 和 PreparedStatementSetter,实用于复杂的场景,需要灵活地设置 SQL 参数。
  • 简化 update 方法:不利用 PreparedStatementSetter,实用于不需要设置参数的简单 SQL 操纵。
  • 处理天生的键的 update 方法:用于插入操纵后获取天生键,如自增主键。
  • 带 PreparedStatementSetter 的 update 方法:利用 PreparedStatementSetter 设置参数,实用于大多数简单的 SQL 操纵。
  • 带参数和参数类型的 update 方法:答应明确指定参数及其类型,实用于需要准确控制 SQL 参数的场景。
  • 带参数的 update 方法:简化参数设置,不指定类型,实用于简单的 SQL 操纵。
  执行插入操纵(update)

下面的示例展示了怎样利用 update 方法插入一条新记录:
  1. public void insertUser(User user) {
  2.     String sql = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
  3.     jdbcTemplate.update(sql, user.getName(), user.getEmail(), user.getAge());
  4. }
复制代码
该方法返回插入的行数,通常为 1。
执行更新操纵(update)

类似地,我们可以利用 update 方法更新现有的记录:
  1. public void updateUser(User user) {
  2.     String sql = "UPDATE users SET name = ?, email = ?, age = ? WHERE id = ?";
  3.     jdbcTemplate.update(sql, user.getName(), user.getEmail(), user.getAge(), user.getId());
  4. }
复制代码
该方法返回被更新的行数。
执行删除操纵(update)

删除操纵同样可以通过 update 方法来实现:
  1. public int deleteUser(Long id) {
  2.     String sql = "DELETE FROM users WHERE id = ?";
  3.     return jdbcTemplate.update(sql, id);
  4. }
复制代码
该方法返回被删除的行数。
批量操纵

在 JdbcTemplate 中,batchUpdate 方法用于执行批量更新、删除和插入操纵。通过批量操纵可以明显提高数据库操纵的效率,特殊是在处理大量数据时。
batchUpdate 方法(带 PreparedStatementCreator 和 BatchPreparedStatementSetter):
  1. public int[] batchUpdate(final PreparedStatementCreator psc, final BatchPreparedStatementSetter pss, final KeyHolder generatedKeyHolder) throws DataAccessException {
  2.     // 调用 execute 方法,传入 PreparedStatementCreator 和 PreparedStatementCallback 来执行批量操作。
  3.     // 由 BatchPreparedStatementSetter 设置批次中的每个 PreparedStatement 参数。
  4.     int[] result = (int[])this.execute(psc, this.getPreparedStatementCallback(pss, generatedKeyHolder));
  5.     Assert.state(result != null, "No result array");
  6.     return result;
  7. }
复制代码
参数寄义:


  • PreparedStatementCreator psc: 用于创建 PreparedStatement 的回调接口。
  • BatchPreparedStatementSetter pss: 用于设置批量操纵中每个 PreparedStatement 参数的回调接口。
  • KeyHolder generatedKeyHolder: 用于存储天生的键的对象,通常用于插入操纵后获取自增主键。
处理逻辑:

  • 执行批量操纵:调用 execute 方法,传入 PreparedStatementCreator 和 PreparedStatementCallback 来执行批量操纵。由 BatchPreparedStatementSetter 设置批次中的每个 PreparedStatement 参数。
  • 返回效果:效果是一个 int[] 数组,每个元素表示执行每批次更新操纵所影响的行数。
   TIP:该方法实用于需要获取天生键的批量插入操纵。
  
batchUpdate 方法(带 SQL 和 BatchPreparedStatementSetter):
  1. public int[] batchUpdate(String sql, final BatchPreparedStatementSetter pss) throws DataAccessException {
  2.     // 在调试模式下,记录 SQL 批量操作的执行情况。
  3.     if (this.logger.isDebugEnabled()) {
  4.         this.logger.debug("Executing SQL batch update [" + sql + "]");
  5.     }
  6.     // 通过 `BatchPreparedStatementSetter` 获取批次大小,如果批次大小为 0,则直接返回空的结果数组
  7.     int batchSize = pss.getBatchSize();
  8.     if (batchSize == 0) {
  9.         return new int[0];
  10.     } else {
  11.         // 调用 execute 方法,通过内部的 PreparedStatementCallback 进行批量操作。
  12.         int[] result = (int[])this.execute(sql, this.getPreparedStatementCallback(pss, (KeyHolder)null));
  13.         Assert.state(result != null, "No result array");
  14.         
  15.         // 返回受影响的行数数组,每个元素表示每批次执行的结果。
  16.         return result;
  17.     }
  18. }
复制代码
参数寄义:


  • String sql: 要执行的 SQL 语句。
  • BatchPreparedStatementSetter pss: 用于设置批量操纵中每个 PreparedStatement 参数的回调接口。
处理逻辑:

  • 日志记录:在调试模式下,记录 SQL 批量操纵的执行情况。
  • 检查批次巨细:通过 BatchPreparedStatementSetter 获取批次巨细,如果批次巨细为 0,则直接返回空的效果数组。
  • 执行批量操纵:调用 execute 方法,通过内部的 PreparedStatementCallback 进行批量操纵。
  • 返回效果:返回受影响的行数数组,每个元素表示每批次执行的效果。
   TIP:该方法实用于简单的批量更新、删除操纵,不涉及天生键的处理。
  
batchUpdate 方法(带 SQL 和参数列表):
  1. public int[] batchUpdate(String sql, List<Object[]> batchArgs) throws DataAccessException {
  2.     return this.batchUpdate(sql, batchArgs, new int[0]);
  3. }
复制代码
参数寄义:


  • String sql: 要执行的 SQL 语句。
  • List<Object[]> batchArgs: 参数列表,每个 Object[] 表示一批操纵的参数。
处理逻辑:这是一个简化的 batchUpdate 方法,它调用了带有参数类型的 batchUpdate 方法,并传入空的 int[] 数组作为参数类型。
   TIP:实用于简单的批量操纵,不涉及参数类型控制。
  
batchUpdate 方法(带 SQL、参数列表和参数类型):
  1. public int[] batchUpdate(String sql, final List<Object[]> batchArgs, final int[] argTypes) throws DataAccessException {
  2.     // 如果参数列表为空,则返回空的结果数组。
  3.     // 执行批量操作,并返回受影响的行数数组。
  4.     return batchArgs.isEmpty() ? new int[0] : this.batchUpdate(sql, new BatchPreparedStatementSetter() {
  5.         // 在 `BatchPreparedStatementSetter` 的 `setValues` 方法中,根据参数和对应的类型设置 `PreparedStatement` 的参数值
  6.         public void setValues(PreparedStatement ps, int i) throws SQLException {
  7.             Object[] values = (Object[])batchArgs.get(i);
  8.             int colIndex = 0;
  9.             Object[] var5 = values;
  10.             int var6 = values.length;
  11.             for(int var7 = 0; var7 < var6; ++var7) {
  12.                 Object value = var5[var7];
  13.                 ++colIndex;
  14.                
  15.                 // 使用 `StatementCreatorUtils.setParameterValue` 方法处理不同类型的参数值
  16.                 if (value instanceof SqlParameterValue paramValue) {
  17.                     StatementCreatorUtils.setParameterValue(ps, colIndex, paramValue, paramValue.getValue());
  18.                 } else {
  19.                     int colType;
  20.                     if (argTypes.length < colIndex) {
  21.                         colType = Integer.MIN_VALUE;
  22.                     } else {
  23.                         colType = argTypes[colIndex - 1];
  24.                     }
  25.                     StatementCreatorUtils.setParameterValue(ps, colIndex, colType, value);
  26.                 }
  27.             }
  28.         }
  29.         public int getBatchSize() {
  30.             return batchArgs.size();
  31.         }
  32.     });
  33. }
复制代码
参数寄义:


  • String sql: 要执行的 SQL 语句。
  • List<Object[]> batchArgs: 参数列表,每个 Object[] 表示一批操纵的参数。
  • int[] argTypes: 参数类型数组,对应 batchArgs 中每个参数的 JDBC 类型。
处理逻辑:

  • 检查参数列表是否为空:如果参数列表为空,则返回空的效果数组。
  • 设置参数值

    • 在 BatchPreparedStatementSetter 的 setValues 方法中,根据参数和对应的类型设置 PreparedStatement 的参数值。
    • 利用 StatementCreatorUtils.setParameterValue 方法处理不同类型的参数值。

  • 返回效果:执行批量操纵,并返回受影响的行数数组。
   TIP:该方法实用于需要明确指定参数类型的批量操纵场景。
  
batchUpdate 方法(带自界说参数设置和批次巨细):
  1. public <T> int[][] batchUpdate(String sql, final Collection<T> batchArgs, final int batchSize, final ParameterizedPreparedStatementSetter<T> pss) throws DataAccessException {
  2.     // 在调试模式下,记录 SQL 批量操作的执行情况和批次大小。
  3.     if (this.logger.isDebugEnabled()) {
  4.         this.logger.debug("Executing SQL batch update [" + sql + "] with a batch size of " + batchSize);
  5.     }
  6.     int[][] result = (int[][])this.execute(sql, (ps) -> {
  7.         ArrayList<int[]> rowsAffected = new ArrayList<>();
  8.         boolean var15 = false;
  9.         // 结果是一个二维 `int[][]` 数组,每个子数组表示一个批次的执行结果。
  10.         int[][] var19;
  11.         try {
  12.             var15 = true;
  13.             
  14.             // 通过 `JdbcUtils.supportsBatchUpdates` 检查数据库是否支持批量更新。
  15.             boolean batchSupported = JdbcUtils.supportsBatchUpdates(ps.getConnection());
  16.             int n = 0;
  17.             Iterator<T> var8 = batchArgs.iterator();
  18.             while(true) {
  19.                 if (!var8.hasNext()) {
  20.                     int[][] result1 = new int[rowsAffected.size()][];
  21.                     for(int i = 0; i < result1.length; ++i) {
  22.                         result1[i] = rowsAffected.get(i);
  23.                     }
  24.                     var19 = result1;
  25.                     var15 = false;
  26.                     break;
  27.                 }
  28.                 T obj = var8.next();
  29.                
  30.                 // 逐个设置 `PreparedStatement` 的参数。
  31.                 pss.setValues(ps, obj);
  32.                 ++n;
  33.                
  34.                 // 如果数据库支持批量操作,则累积操作并按批次大小执行 `ps.executeBatch()`。
  35.                 if (batchSupported) {
  36.                     ps.addBatch();
  37.                     if (n % batchSize == 0 || n == batchArgs.size()) {
  38.                         if (this.logger.isTraceEnabled()) {
  39.                             int batchIdx = n % batchSize == 0 ? n / batchSize : n / batchSize + 1;
  40.                             int items = n - (n % batchSize == 0 ? n / batchSize - 1 : n / batchSize) * batchSize;
  41.                             this.logger.trace("Sending SQL batch update #" + batchIdx + " with " + items + " items");
  42.                         }
  43.                         rowsAffected.add(ps.executeBatch());
  44.                     }
  45.                 } else { // 如果不支持批量操作,则逐条执行 `ps.executeUpdate()`。
  46.                     int updateCount = ps.executeUpdate();
  47.                     rowsAffected.add(new int[]{updateCount});
  48.                 }
  49.             }
  50.         } finally {
  51.             if (var15) {
  52.                 if (pss instanceof ParameterDisposer parameterDisposer) {
  53.                     parameterDisposer.cleanupParameters();
  54.                 }
  55.             }
  56.         }
  57.         if (pss instanceof ParameterDisposer parameterDisposerx) {
  58.             parameterDisposerx.cleanupParameters();
  59.         }
  60.         return var19;
  61.     });
  62.     Assert.state(result != null, "No result array");
  63.     return result;
  64. }
复制代码
参数寄义:


  • String sql:要执行的 SQL 语句。
  • Collection<T> batchArgs:批量操纵的参数集合,每个元素表示一批操纵的参数。
  • int batchSize:每次批量操纵的巨细。
  • ParameterizedPreparedStatementSetter<T> pss:用于设置 PreparedStatement 参数的回调接口。
处理逻辑:

  • 日志记录:在调试模式下,记录 SQL 批量操纵的执行情况和批次巨细。
  • 批量操纵支持检查:通过 JdbcUtils.supportsBatchUpdates 检查数据库是否支持批量更新。
  • 执行批量操纵

    • 逐个设置 PreparedStatement 的参数。
    • 如果数据库支持批量操纵,则累积操纵并按批次巨细执行 ps.executeBatch()。
    • 如果不支持批量操纵,则逐条执行 ps.executeUpdate()。

  • 返回效果:效果是一个二维 int[][] 数组,每个子数组表示一个批次的执行效果。
   TIP:该方法实用于自界说批次巨细和参数设置的复杂批量操纵场景。
  批量插入(batchUpdate)

以下示例展示了怎样批量插入多个用户:
  1. public int[] batchAddUsers(List<User> users) {
  2.     String sql = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
  3.     return jdbcTemplate.batchUpdate(sql, users, users.size(), (ps, user) -> {
  4.         ps.setString(1, user.getName());
  5.         ps.setString(2, user.getEmail());
  6.         ps.setInt(3, user.getAge());
  7.     });
  8. }
复制代码
该方法返回一个 int[] 数组,每个元素代表对应批次的执行效果。
批量更新与删除(batchUpdate)

批量更新和删除操纵与批量插入类似,也利用 batchUpdate 方法来实现:
  1. public int[] batchUpdateUsers(List<User> users) {
  2.     String sql = "UPDATE users SET name = ?, email = ?, age = ? WHERE id = ?";
  3.     return jdbcTemplate.batchUpdate(sql, users, users.size(), (ps, user) -> {
  4.         ps.setString(1, user.getName());
  5.         ps.setString(2, user.getEmail());
  6.         ps.setInt(3, user.getAge());
  7.         ps.setLong(4, user.getId());
  8.     });
  9. }
  10. public int[] batchDeleteUsers(List<Long> ids) {
  11.     String sql = "DELETE FROM users WHERE id = ?";
  12.     return jdbcTemplate.batchUpdate(sql, ids, ids.size(), (ps, id) -> ps.setLong(1, id));
  13. }
复制代码
这两个方法分别执行批量更新和删除操纵,返回 int[] 数组,表示每个批次的执行效果。
JdbcTemplate 进阶用法

利用 RowMapper 自界说效果映射

RowMapper 是 JdbcTemplate 中用于将 ResultSet 的每一行映射为 Java 对象的接口。通过实现 RowMapper 接口,开发者可以根据需要自界说效果集的映射逻辑,从而将数据库查询效果转换为更得当应用逻辑的对象情势。
什么是 RowMapper

RowMapper 接口位于 org.springframework.jdbc.core 包中,它只有一个方法:
  1. @FunctionalInterface
  2. public interface RowMapper<T> {
  3.     @Nullable
  4.     T mapRow(ResultSet rs, int rowNum) throws SQLException;
  5. }
复制代码
参数说明:


  • ResultSet rs: 表示数据库返回的效果集。
  • int rowNum: 表示当前处理的是效果集的第几行(从 0 开始)。
返回值:


  • 返回的是一个泛型类型 T,表示将 ResultSet 中的一行数据映射为的对象类型。
通过实现这个接口,你可以在 mapRow 方法中界说怎样将 ResultSet 的一行数据映射到你界说的对象中。
自界说 RowMapper

还是之前的用户表 users,包罗以下字段:id、name、email、age。我们需要将查询效果映射为一个 User 对象。
  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. @RequiredArgsConstructor
  5. public class User {
  6.     private Long id;
  7.     private String name;
  8.     private String email;
  9.     private Integer age;
  10. }
复制代码
我们实现 RowMapper 接口,将 ResultSet 中的每一行数据映射为 User 对象:
  1. public class UserRowMapper implements RowMapper {
  2.     @Override
  3.     public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
  4.         User user = new User();
  5.         user.setId(rs.getLong("id"));
  6.         user.setName(rs.getString("name"));
  7.         user.setEmail(rs.getString("email"));
  8.         user.setAge(rs.getInt("age"));
  9.         return user;
  10.     }
  11. }
复制代码
在 JdbcTemplate 中利用自界说 RowMapper

一旦 RowMapper 实现完成,我们可以在 JdbcTemplate 中利用它来执行查询并映射效果。例如:
  1. public List<User> findAllUsers() {
  2.     String sql = "SELECT * FROM users";
  3.     return jdbcTemplate.query(sql, new UserRowMapper());
  4. }
复制代码
在上面的代码中,jdbcTemplate.query 方法将执行给定的 SQL 查询,并利用 UserRowMapper 将效果会合的每一行数据映射为 User 对象,最终返回 User 对象的 List。
利用 Lambda 表达式简化 RowMapper

如果映射逻辑非常简单,例如只是将数据库字段直接映射到对象的字段上,可以利用 Lambda 表达式简化 RowMapper 的实现:
  1. public List<User> findAllUsers() {
  2.     String sql = "SELECT * FROM users";
  3.     // return jdbcTemplate.query(sql, new UserRowMapper());
  4.     return jdbcTemplate.query(sql, (rs, rowNum) -> {
  5.         User user = new User();
  6.         user.setId(rs.getLong("id"));
  7.         user.setName(rs.getString("name"));
  8.         user.setEmail(rs.getString("email"));
  9.         user.setAge(rs.getInt("age"));
  10.         return user;
  11.     });
  12. }
复制代码
这种写法更加简便,但功能上与单独界说的 UserRowMapper 类似。它更得当于映射逻辑简单、不需要在多个地方复用的场景。
扩展 RowMapper 的功能

在某些复杂场景下,大概需要扩展 RowMapper 的功能。例如,当效果集包罗关联表的数据时,可以在 RowMapper 中处理这些关联关系,并将其映射为复杂对象。
假设 User 对象中还有一个 List<Order> 字段,表示用户的订单列表,Order 是一个单独的类,代表订单信息。在 UserRowMapper 中,你可以添加额外的逻辑来查询并填充用户的订单信息:
  1. public class UserRowMapper implements RowMapper<User> {
  2.     @Override
  3.     public User mapRow(ResultSet rs, int rowNum) throws SQLException {
  4.         User user = new User();
  5.         user.setId(rs.getLong("id"));
  6.         user.setName(rs.getString("name"));
  7.         user.setEmail(rs.getString("email"));
  8.         user.setAge(rs.getInt("age"));
  9.         
  10.         // 获取用户的订单列表(假设订单数据已经加入到 ResultSet 中)
  11.         List<Order> orders = new ArrayList<>();
  12.         do {
  13.             Order order = new Order();
  14.             order.setOrderId(rs.getLong("order_id"));
  15.             order.setProduct(rs.getString("product"));
  16.             order.setPrice(rs.getBigDecimal("price"));
  17.             orders.add(order);
  18.         } while (rs.next() && rs.getLong("id") == user.getId()); // 关联逻辑,类似于 ON 条件
  19.         
  20.         user.setOrders(orders);
  21.         return user;
  22.     }
  23. }
复制代码
这种处理方式可以将多个表的数据映射为复杂的对象结构,但要留意控制查询的效率和内存斲丧。
利用 ResultSetExtractor 处理复杂查询效果

ResultSetExtractor 是 JdbcTemplate 提供的另一种用于处理查询效果的接口,与 RowMapper 不同的是,ResultSetExtractor 答应你处理整个 ResultSet,而不仅仅是逐行映射。这使得它特殊得当处理复杂的查询效果,比如需要跨多行数据进行聚合或复杂转换的情况。
什么是 ResultSetExtractor

ResultSetExtractor 是一个函数式接口,其界说如下:
  1. public interface ResultSetExtractor<T> {
  2.     @Nullable
  3.     T extractData(ResultSet rs) throws SQLException, DataAccessException;
  4. }
复制代码


  • 参数说明:ResultSet rs: 表示数据库返回的效果集,大概包罗多行数据。
  • 返回值:返回值是泛型类型 T,可以是任何类型,通常是一个处理后的对象或对象集合。
ResultSetExtractor 答应你一次性处理整个 ResultSet,这在需要处理复杂的、跨多行的数据时特殊有用。
自界说 ResultSetExtractor

假设我们需要查询用户及其订单信息,并将这些信息构造成一个包罗用户和其订单列表的复杂对象。我们可以利用 ResultSetExtractor 实现这一需求。
首先,假设我们有如下的 User 和 Order 类:
  1. public class User {
  2.     private Long id;
  3.     private String name;
  4.     private String email;
  5.     private Integer age;
  6.     private List<Order> orders;
  7.     // 构造方法、getter 和 setter 方法省略
  8. }
  9. public class Order {
  10.     private Long orderId;
  11.     private String product;
  12.     private BigDecimal price;
  13.     // 构造方法、getter 和 setter 方法省略
  14. }
复制代码
然后,我们可以利用 ResultSetExtractor 将查询效果映射为一个 User 对象及其关联的 Order 列表:
  1. public class UserWithOrdersExtractor implements ResultSetExtractor {
  2.     @Override
  3.     public User extractData(ResultSet rs) throws SQLException, DataAccessException {
  4.         User user = null;
  5.         List<Order> orders = new ArrayList<>();
  6.         while (rs.next()) {
  7.             if (user == null) {
  8.                 user = new User();
  9.                 user.setId(rs.getLong("id"));
  10.                 user.setName(rs.getString("name"));
  11.                 user.setEmail(rs.getString("email"));
  12.                 user.setAge(rs.getInt("age"));
  13.             }
  14.             Order order = new Order();
  15.             order.setOrderId(rs.getLong("order_id"));
  16.             order.setProduct(rs.getString("product"));
  17.             order.setPrice(rs.getBigDecimal("price"));
  18.             orders.add(order);
  19.         }
  20.         if (user != null) {
  21.             user.setOrders(orders);
  22.         }
  23.         return user;
  24.     }
  25. }
复制代码
在这个实现中,我们利用 while 循环遍历 ResultSet,每次读取一行数据。在第一次循环时,我们初始化 User 对象,并在后续的每一行中将订单信息添加到用户的订单列表中。循环竣事后,返回包罗所有订单信息的用户对象。
利用匿名类或 Lambda 表达式简化 ResultSetExtractor

如果你只需要一次性的利用 ResultSetExtractor,可以利用匿名类或 Lambda 表达式来简化代码。例如:
  1. public User findUserWithOrders(Long userId) {
  2.     String sql = "SELECT u.id, u.name, u.email, u.age, o.order_id, o.product, o.price " +
  3.             "FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE u.id = ?";
  4.     return  jdbcTemplate.query(sql, new Object[]{userId}, rs -> {
  5.         User user = null;
  6.         List<Order> orders = new ArrayList<>();
  7.         while (rs.next()) {
  8.             if (user == null) {
  9.                 user = new User();
  10.                 user.setId(rs.getLong("id"));
  11.                 user.setName(rs.getString("name"));
  12.                 user.setEmail(rs.getString("email"));
  13.                 user.setAge(rs.getInt("age"));
  14.             }
  15.             Order order = new Order();
  16.             order.setOrderId(rs.getLong("order_id"));
  17.             order.setProduct(rs.getString("product"));
  18.             order.setPrice(rs.getBigDecimal("price"));
  19.             orders.add(order);
  20.         }
  21.         if (user != null) {
  22.             user.setOrders(orders);
  23.         }
  24.         return user;
  25.     });
  26. }
复制代码
这种写法固然简便,但也仅实用于简单的场景。如果查询逻辑复杂,发起还是单独界说一个 ResultSetExtractor 类,以便代码结构更加清晰。
ResultSetExtractor 的扩展

ResultSetExtractor 还可以用来实现更复杂的数据聚合和处理逻辑。例如,你可以在 ResultSetExtractor 中处理多个子查询效果,将数据聚合成复杂的对象结构,或将多个查询效果组合成一个复合对象。这使得它在处理复杂业务逻辑时非常有用。
举个例子,如果你需要将查询效果中的订单按日期进行分组,并盘算每组订单的总金额,你可以在 extractData 方法中实现这些逻辑,并将效果返回为一个包罗这些分组信息的对象。
利用 PreparedStatementSetter 动态设置参数

PreparedStatementSetter 是 JdbcTemplate 中用于动态设置 PreparedStatement 参数的接口。它答应开发者在执行 SQL 语句之前,通过编程的方式动态地为 SQL 语句的参数赋值。利用 PreparedStatementSetter,可以更灵活地处理不同的 SQL 参数设置场景,例如复杂的查询条件、批量操纵等。
什么是 PreparedStatementSetter

PreparedStatementSetter 接口位于 org.springframework.jdbc.core 包中,它只有一个方法:
  1. public interface PreparedStatementSetter {
  2.     void setValues(PreparedStatement ps) throws SQLException;
  3. }
复制代码
参数说明:


  • PreparedStatement ps: 代表 JDBC 中的 PreparedStatement 对象,答应在 SQL 语句执行之前为其设置参数。
PreparedStatementSetter 的核心功能就是在 SQL 执行之前,为 PreparedStatement 的参数占位符(即 ?)赋值。这使得 SQL 语句在执行时能够更换掉占位符并传递现实的参数值。
利用 PreparedStatementSetter 的基本示例

假设我们有一个 SQL 查询,需要根据用户输入的条件动态设置参数:
  1. public List<User> findUsersByCriteria(String name, Integer age) {
  2.     String sql = "SELECT id, name, email, age FROM users WHERE name = ? AND age = ?";
  3.     return jdbcTemplate.query(sql, new PreparedStatementSetter() {
  4.         @Override
  5.         public void setValues(PreparedStatement ps) throws SQLException {
  6.             ps.setString(1, name);
  7.             ps.setInt(2, age);
  8.         }
  9.     }, new UserRowMapper());
  10. }
复制代码
在这个例子中,我们界说了一个匿名的 PreparedStatementSetter 实现,通过 ps.setString 和 ps.setInt 方法为 SQL 语句中的 ? 占位符设置参数。然后,利用 UserRowMapper 来将查询效果映射为 User 对象列表。
利用 Lambda 表达式简化代码

由于 PreparedStatementSetter 是一个函数式接口,可以用 Lambda 表达式来简化代码。上面的代码可以重写为:
  1. public List<User> findUsersByCriteria(String name, Integer age) {
  2.     String sql = "SELECT id, name, email, age FROM users WHERE name = ? AND age = ?";
  3.     return jdbcTemplate.query(sql, ps -> {
  4.         ps.setString(1, name);
  5.         ps.setInt(2, age);
  6.     }, new UserRowMapper());
  7. }
复制代码
这种写法不仅简便,而且在处理简单的参数设置场景时,更容易阅读和维护。
扩展 PreparedStatementSetter:处理复杂参数设置

在现实应用中,你大概需要处理更复杂的参数设置逻辑,比如根据某些条件动态设置参数值,或者处理可选参数。下面是一个更复杂的示例:
  1. public List<User> findUsersByCriteria(String name, Integer age, String email) {
  2.     String sql = "SELECT id, name, email, age FROM users WHERE 1=1";
  3.     List<Object> params = new ArrayList<>();
  4.     StringBuilder query = new StringBuilder(sql);
  5.     if (name != null) {
  6.         query.append(" AND name = ?");
  7.         params.add(name);
  8.     }
  9.     if (age != null) {
  10.         query.append(" AND age = ?");
  11.         params.add(age);
  12.     }
  13.     if (email != null) {
  14.         query.append(" AND email = ?");
  15.         params.add(email);
  16.     }
  17.     return jdbcTemplate.query(query.toString(), ps -> {
  18.         for (int i = 0; i < params.size(); i++) {
  19.             ps.setObject(i + 1, params.get(i));
  20.         }
  21.     }, new UserRowMapper());
  22. }
复制代码
在这个例子中,我们根据传入的参数条件动态构建了 SQL 查询,并将参数添加到一个列表 params 中。然后在 PreparedStatementSetter 中,我们遍历参数列表,并将参数依次设置到 PreparedStatement 中。这样可以处理不同的查询条件而无需重复编写 SQL 语句。
批量操纵中的 PreparedStatementSetter

PreparedStatementSetter 也常用于批量操纵中,特殊是共同 BatchPreparedStatementSetter 接口利用时。它答应你为每一个批次的 SQL 语句动态设置参数。
  1. public void batchUpdateUserEmails(List<User> users) {
  2.     String sql = "UPDATE users SET email = ? WHERE id = ?";
  3.     jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
  4.         @Override
  5.         public void setValues(PreparedStatement ps, int i) throws SQLException {
  6.             User user = users.get(i);
  7.             ps.setString(1, user.getEmail());
  8.             ps.setLong(2, user.getId());
  9.         }
  10.         @Override
  11.         public int getBatchSize() {
  12.             return users.size();
  13.         }
  14.     });
  15. }
复制代码
在这个示例中,我们利用 BatchPreparedStatementSetter 来处理批量更新操纵,每个批次的参数都根据当前的用户对象动态设置。
与其他 JdbcTemplate 方法的结合

PreparedStatementSetter 不仅用于 query 方法,还可以用于 update 和 batchUpdate 等方法。这使得它在动态参数设置的场景中非常通用。例如,下面是一个通过 update 方法执行插入操纵的示例:
  1. public int insertUser(String name, String email, Integer age) {
  2.     String sql = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
  3.    
  4.     return jdbcTemplate.update(sql, ps -> {
  5.         ps.setString(1, name);
  6.         ps.setString(2, email);
  7.         ps.setInt(3, age);
  8.     });
  9. }
复制代码
利用 CallableStatementCallback 执行存储过程

在企业级应用中,存储过程(Stored Procedures)被广泛用于封装复杂的数据库逻辑。CallableStatementCallback 是 JdbcTemplate 提供的一个接口,用于执行存储过程或数据库函数,并处理效果集或输出参数。通过 CallableStatementCallback,我们可以灵活地调用数据库中的存储过程,处理复杂的输入和输出参数。
什么是 CallableStatementCallback

CallableStatementCallback 接口位于 org.springframework.jdbc.core 包中,它答应你通过回调的方式执行存储过程。接口界说如下:
  1. public interface CallableStatementCallback<T> {
  2.     @Nullable
  3.     T doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException;
  4. }
复制代码


  • 参数:CallableStatement cs: 表示 CallableStatement 对象,用于执行数据库中的存储过程或函数。
  • 返回值:泛型类型 T,表示执行存储过程后的效果,可以是单个对象、集合或任何复杂的类型。
通过实现 CallableStatementCallback,你可以自界说存储过程的执行逻辑,并处理存储过程返回的效果或输出参数。
利用 CallableStatementCallback 调用简单存储过程

假设我们在数据库中有一个简单的存储过程,用于获取用户的具体信息:
  1. CREATE PROCEDURE GetUserDetails(IN userId BIGINT, OUT userName VARCHAR(100), OUT userEmail VARCHAR(100))
  2. BEGIN
  3.     SELECT name, email INTO userName, userEmail FROM users WHERE id = userId;
  4. END;
复制代码
我们可以通过 CallableStatementCallback 调用这个存储过程,并处理输出参数:
  1. public Map<String, String> getUserDetails(Long userId) {
  2.     String sql = "{call GetUserDetails(?, ?, ?)}";
  3.     return jdbcTemplate.execute(sql, new CallableStatementCallback<Map<String, String>>() {
  4.         @Override
  5.         public Map<String, String> doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException {
  6.             // 存储过程入参
  7.             cs.setLong(1, userId);
  8.             // 存储过程出参
  9.             cs.registerOutParameter(2, Types.VARCHAR);
  10.             cs.registerOutParameter(3, Types.VARCHAR);
  11.             // 执行存储过程
  12.             cs.execute();
  13.             // 返回结果
  14.             Map<String, String> result = new HashMap<>();
  15.             result.put("userName", cs.getString(2));
  16.             result.put("userEmail", cs.getString(3));
  17.             return result;
  18.         }
  19.     });
  20. }
复制代码
在这个例子中:


  • cs.setLong(1, userId) 设置输入参数 userId。
  • cs.registerOutParameter(2, Types.VARCHAR) 和 cs.registerOutParameter(3, Types.VARCHAR) 注册两个输出参数,用于接收存储过程的返回值。
  • cs.execute() 执行存储过程。
  • 然后我们将输出参数的效果存储到 Map<String, String> 中并返回。
处理复杂的输入和输出参数

有时候,存储过程大概需要处理更复杂的输入和输出参数,比如多个输入参数,或返回一个效果集。我们可以在 CallableStatementCallback 中处理这些复杂情况。
例如,假设我们有一个存储过程,它接收多个输入参数,并返回一个效果集:
  1. CREATE PROCEDURE GetUserOrders(IN userId BIGINT)
  2. BEGIN
  3.     SELECT order_id, product, price FROM orders WHERE user_id = userId;
  4. END;
复制代码
我们可以这样处理:
  1. public List<Order> getUserOrders(Long userId) {
  2.     String sql = "{call GetUserOrders(?)}";
  3.     return jdbcTemplate.execute(sql, new CallableStatementCallback<List<Order>>() {
  4.         @Override
  5.         public List<Order> doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException {
  6.             // 存储过程入参
  7.             cs.setLong(1, userId);
  8.             // 执行存储过程,记录是否有返回结果集
  9.             boolean hasResultSet = cs.execute();
  10.             List<Order> orders = new ArrayList<>();
  11.             if (hasResultSet) {
  12.                 ResultSet rs = cs.getResultSet();
  13.                 while (rs.next()) {
  14.                     Order order = new Order();
  15.                     order.setOrderId(rs.getLong("order_id"));
  16.                     order.setProduct(rs.getString("product"));
  17.                     order.setPrice(rs.getBigDecimal("price"));
  18.                     orders.add(order);
  19.                 }
  20.             }
  21.             return orders;
  22.         }
  23.     });
  24. }
复制代码
在这个示例中:


  • cs.setLong(1, userId) 设置输入参数。
  • cs.execute() 执行存储过程并检查是否有效果集返回。
  • 如果有效果集返回,我们遍历 ResultSet 并将每一行数据映射为 Order 对象,然后添加到 orders 列表中。
处理 OUT 和 INOUT 参数

在存储过程中,OUT 参数和 INOUT 参数是比较常见的。CallableStatementCallback 提供了直接的方法来处理这些参数。
例如,一个存储过程大概会利用 INOUT 参数来接收和返回值:
  1. CREATE PROCEDURE UpdateUserAge(INOUT userAge INT)
  2. BEGIN
  3.     SET userAge = userAge + 1;
  4. END;
复制代码
调用这个存储过程并处理 INOUT 参数的示例如下:
  1. public int updateUserAge(int age) {
  2.     String sql = "{call UpdateUserAge(?)}";
  3.     return jdbcTemplate.execute(sql, new CallableStatementCallback<Integer>() {
  4.         @Override
  5.         public Integer doInCallableStatement(CallableStatement cs) throws SQLException {
  6.             cs.setInt(1, age);
  7.             cs.registerOutParameter(1, Types.INTEGER);
  8.             cs.execute();
  9.             return cs.getInt(1);
  10.         }
  11.     });
  12. }
复制代码
在这个示例中:


  • cs.setInt(1, age) 设置 INOUT 参数的初始值。
  • cs.registerOutParameter(1, Types.INTEGER) 注册输出参数。
  • 执行存储过程后,通过 cs.getInt(1) 获取更新后的参数值。
事务管理与 JdbcTemplate

在现实开发中,事务管理至关告急,特殊是在涉及多个数据库操纵时。Spring 提供了强盛的事务管理机制,答应开发者在利用 JdbcTemplate 进行数据库操纵时轻松管理事务。事务可以确保一组数据库操纵要么全部成功,要么全部回滚,从而保持数据的同等性。
在这一部门中,我们将探究 JdbcTemplate 中的事务管理,内容包括事务的基本概念、在 JdbcTemplate 中配置事务、编程式事务与声明式事务的区别,以及事务的隔离级别与传播行为。
事务的基本概念

事务(Transaction) 是一组逻辑上的操纵单元,这些操纵要么全部成功,要么全部失败回滚。
事务具有四个基本特性,通常简称为 ACID


  • 原子性(Atomicity):事务中的所有操纵要么全部成功,要么全部失败。
  • 同等性(Consistency):事务完成后,数据库必须处于同等的状态。
  • 隔离性(Isolation):事务之间相互隔离,不能互相影响。
  • 长期性(Durability):事务一旦提交,其效果将被永世保存,纵然体系发生故障也不会丢失。
Spring 的事务管理框架答应开发者在应用步伐中轻松管理这些特性,无论是通过编程式还是声明式的方式。
在 JdbcTemplate 中配置事务

利用 JdbcTemplate 时,事务管理通常通过 Spring 提供的 PlatformTransactionManager 来完成。最常见的事务管理器是 DataSourceTransactionManager,用于管理基于 JDBC 的事务。
在 Spring Boot 中,配置事务管理通常非常简单,由于它已经为你自动配置了 DataSourceTransactionManager。如果你需要手动配置,以下是一个简单的配置事务管理器示例:
  1. @Configuration
  2. @EnableTransactionManagement // 开启事务管理
  3. public class AppConfig {
  4.     @Bean
  5.     public DataSourceTransactionManager transactionManager(DataSource dataSource) {
  6.         return new DataSourceTransactionManager(dataSource);
  7.     }
  8.     @Bean
  9.     public JdbcTemplate jdbcTemplate(DataSource dataSource) {
  10.         return new JdbcTemplate(dataSource);
  11.     }
  12. }
复制代码
解释


  • @EnableTransactionManagement:启用 Spring 的注解驱动的事务管理功能。
  • DataSourceTransactionManager:Spring 提供的基于 JDBC 的事务管理器,它需要一个 DataSource 对象来管理事务的开始、提交和回滚。
  • JdbcTemplate:我们将 DataSource 注入到 JdbcTemplate 中,以便它能够利用同一个 DataSource 进行数据库操纵。
编程式事务与声明式事务

Spring 提供了两种方式来管理事务:编程式事务和声明式事务。
编程式事务答应你通过代码显式地控制事务的开始、提交和回滚。利用编程式事务通常涉及 TransactionTemplate 或 PlatformTransactionManager,它们提供了编程方式来控制事务的边界。
利用示例:
  1. @Service
  2. public class UserService {
  3.     private final JdbcTemplate jdbcTemplate;
  4.     private final PlatformTransactionManager transactionManager;
  5.     @Autowired
  6.     public UserService(JdbcTemplate jdbcTemplate, PlatformTransactionManager transactionManager) {
  7.         this.jdbcTemplate = jdbcTemplate;
  8.         this.transactionManager = transactionManager;
  9.     }
  10.     public void createUser(String name, String email) {
  11.         // 创建事务
  12.         TransactionDefinition def = new DefaultTransactionDefinition();
  13.         // 获取事务状态
  14.         TransactionStatus status = transactionManager.getTransaction(def);
  15.         try {
  16.             jdbcTemplate.update("INSERT INTO users (name, email) VALUES (?, ?)", name, email);
  17.             // 其他数据库操作...
  18.             // 操作结束,提交事务
  19.             transactionManager.commit(status);
  20.         } catch (Exception e) {
  21.             // 回滚事务
  22.             transactionManager.rollback(status);
  23.             throw e;
  24.         }
  25.     }
  26. }
复制代码
在这个示例中,我们利用 @Transactional 注解声明了 createUser 方法为一个事务方法。Spring 会自动管理事务的开始、提交和回滚。
   TIP:编程式事务优点
  

  • 简便:代码更简便,不需要手动控制事务边界。
  • 会合管理:事务管理逻辑会合在配置中,更容易维护。
  
声明式事务更为常见,它答应你利用注解(或 XML 配置)来声明事务的边界,而不需要在代码中显式控制事务。
利用示例:
  1. @Service
  2. public class UserService {
  3.     private final JdbcTemplate jdbcTemplate;
  4.     @Autowired
  5.     public UserService(JdbcTemplate jdbcTemplate) {
  6.         this.jdbcTemplate = jdbcTemplate;
  7.     }
  8.     @Transactional
  9.     public void createUser(String name, String email) {
  10.         jdbcTemplate.update("INSERT INTO users (name, email) VALUES (?, ?)", name, email);
  11.         // 其他数据库操作...
  12.     }
  13. }
复制代码
在这个示例中,我们利用 @Transactional 注解声明了 createUser 方法为一个事务方法。Spring 会自动管理事务的开始、提交和回滚。
   TIP:声明式事务优点
  

  • 简便:代码更简便,不需要手动控制事务边界。
  • 会合管理:事务管理逻辑会合在配置中,更容易维护。
  事务隔离级别与传播行为

在 Spring 事务管理中,事务的隔离级别和传播行为是两个非常告急的概念,它们直接影响到事务的执行方式和事务间的相互影响。
隔离级别决定了一个事务与另一个事务的隔离程度。Spring 提供了以下5个隔离级别:


  • DEFAULT:利用数据库默认的隔离级别。
  • READ_UNCOMMITTED:答应读取未提交的数据(大概导致脏读)。
  • READ_COMMITTED:只能读取已提交的数据(制止脏读)。
  • REPEATABLE_READ:在事务期间多次读取类似数据,效果同等(制止不可重复读)。
  • SERIALIZABLE:最高的隔离级别,完全隔离(制止幻读,但性能较差)。
利用示例:(MySQL 默认可重复读)
  1. @Transactional(isolation = Isolation.REPEATABLE_READ)
  2. public void createUser(String name, String email) {
  3.     jdbcTemplate.update("INSERT INTO users (name, email) VALUES (?, ?)", name, email);
  4. }
复制代码
在这个示例中,@Transactional 注解指定了 REPEATABLE_READ 隔离级别,确保事务在读取数据时的稳固性。

传播行为界说了当一个事务方法被另一个事务方法调用时,事务应该怎样进行。
Spring 支持的传播行为有如下7个:


  • REQUIRED(默认):如果当前存在事务,则加入该事务;如果没有,则新建一个事务。
  • REQUIRES_NEW:总是新建一个事务。如果当前存在事务,则挂起当前事务。
  • SUPPORTS:如果当前存在事务,则加入该事务;如果没有事务,则以非事务方式运行。
  • NOT_SUPPORTED:总是以非事务方式运行,如果当前存在事务,则挂起当前事务。
  • MANDATORY:必须在一个事务中运行,如果当前没有事务,则抛出非常。
  • NEVER:总是以非事务方式运行,如果当前存在事务,则抛出非常。
  • NESTED:如果当前存在事务,则在当前事务中嵌套一个子事务。
利用示例:
  1. @Transactional(propagation = Propagation.REQUIRES_NEW)
  2. public void createUser(String name, String email) {
  3.     jdbcTemplate.update("INSERT INTO users (name, email) VALUES (?, ?)", name, email);
  4. }
复制代码
在这个示例中,@Transactional 注解指定了 REQUIRES_NEW 传播行为,即每次调用 createUser 方法时,都会新建一个事务,而不依赖于外部事务。
NamedParameterJdbcTemplate 详解

NamedParameterJdbcTemplate 简介与注入方式

NamedParameterJdbcTemplate 是 JdbcTemplate 的一个扩展类,告急提供了对命名参数的支持。在利用 JdbcTemplate 时,SQL 语句中的参数通常通过位置标识符 ? 来表示,这种方式在处理复杂 SQL 时大概会导致代码难以维护。而 NamedParameterJdbcTemplate 通过利用命名参数,使得 SQL 语句更加直观和易于管理。
NamedParameterJdbcTemplate 的核心特点是支持命名参数,即你可以在 SQL 语句中利用具有名称的参数,而不是利用位置占位符 ?。这使得代码更具可读性,尤其在处理多个参数时尤为有用。
命名参数的优势:


  • 可读性:参数名称使 SQL 语句更加清晰易懂,制止了位置占位符的混淆。
  • 灵活性:可以通过 Map 或 SqlParameterSource 的实现来动态构建参数,这使得处理动态 SQL 变得更加简单。
  • 简化代码:在复杂查询中制止了参数位置错乱的风险,减少了出错的大概性。
与 JdbcTemplate 类似,NamedParameterJdbcTemplate 也依赖于 DataSource 来管理数据库连接。通常情况下,我们可以通过配置类将 NamedParameterJdbcTemplate 注入到服务类中。
如果你已经有一个配置好的 JdbcTemplate,可以直接通过构造器注入 JdbcTemplate 来创建 NamedParameterJdbcTemplate:
  1. @Configuration
  2. public class AppConfig {
  3.     @Bean
  4.     public NamedParameterJdbcTemplate namedParameterJdbcTemplate(JdbcTemplate jdbcTemplate) {
  5.         return new NamedParameterJdbcTemplate(jdbcTemplate);
  6.     }
  7.     @Bean
  8.     public JdbcTemplate jdbcTemplate(DataSource dataSource) {
  9.         return new JdbcTemplate(dataSource);
  10.     }
  11. }
复制代码
在这个配置类中,我们首先界说了一个 JdbcTemplate Bean,它依赖于 DataSource。然后,我们利用这个 JdbcTemplate 创建了一个 NamedParameterJdbcTemplate,并将其作为一个独立的 Bean 注入到 Spring 容器中。
也可以直接通过 DataSource 创建 NamedParameterJdbcTemplate,这种方式不依赖现有的 JdbcTemplate:
  1. @Configuration
  2. public class AppConfig {
  3.     @Bean
  4.     public NamedParameterJdbcTemplate namedParameterJdbcTemplate(DataSource dataSource) {
  5.         return new NamedParameterJdbcTemplate(dataSource);
  6.     }
  7. }
复制代码
在这个配置类中,我们直接通过 DataSource 创建了 NamedParameterJdbcTemplate,这种方式制止了对 JdbcTemplate 的依赖,简化了配置。
在现实项目中,你可以将 NamedParameterJdbcTemplate 注入到你的服务类中,并开始利用命名参数执行数据库操纵。
以下是一个简单的利用示例:
  1. @Service
  2. public class UserService {
  3.     private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
  4.     @Autowired
  5.     public UserService(NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
  6.         this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
  7.     }
  8.     public User findUserById(Long id) {
  9.         String sql = "SELECT id, name, email, age FROM users WHERE id = :id";
  10.         Map<String, Object> params = new HashMap<>();
  11.         params.put("id", id);
  12.         return namedParameterJdbcTemplate.queryForObject(sql, params, new BeanPropertyRowMapper<>(User.class));
  13.     }
  14. }
复制代码
在这个示例中,我们利用 NamedParameterJdbcTemplate 进行查询操纵,SQL 语句中的 :id 是一个命名参数,通过 Map<String, Object> 提供现实的参数值。queryForObject 方法用于执行查询,并返回单个 User 对象。
利用命名参数进行查询

NamedParameterJdbcTemplate 通过支持命名参数,使得 SQL 查询操纵更加直观和易于管理。我们继续探究怎样利用 NamedParameterJdbcTemplate 执行查询操纵,包括查询单条记录、多条记录,以及结合 BeanPropertySqlParameterSource 和 MapSqlParameterSource 进行查询。
查询单条记录

利用 NamedParameterJdbcTemplate 查询单条记录时,可以通过 queryForObject 方法实现。这个方法类似于 JdbcTemplate 中的 queryForObject,但它利用命名参数,使得代码更加可读。
我们希望根据用户的 id 查询单个用户的信息。可以利用如下代码实现:
  1. public User findUserById(Long id) {
  2.     String sql = "SELECT id, name, email, age FROM users WHERE id = :id";
  3.    
  4.     Map<String, Object> params = new HashMap<>();
  5.     params.put("id", id);
  6.     return namedParameterJdbcTemplate.queryForObject(sql, params, new BeanPropertyRowMapper<>(User.class));
  7. }
复制代码
解释:


  • sql 语句中的 :id 是命名参数。
  • params 是一个 Map,用于将参数名称与现实值进行绑定。
  • queryForObject 方法用于执行查询,并将效果映射为 User 对象。BeanPropertyRowMapper 会自动将数据库中的列映射到 User 类的属性上。
在现实利用中,查询大概不会返回任何效果,此时 queryForObject 方法会抛出 EmptyResultDataAccessException。可以通过捕获该非常并返回 null 来处理这种情况:
  1. public User findUserById(Long id) {
  2.     String sql = "SELECT id, name, email, age FROM users WHERE id = :id";
  3.    
  4.     Map<String, Object> params = new HashMap<>();
  5.     params.put("id", id);
  6.     try {
  7.         return namedParameterJdbcTemplate.queryForObject(sql, params, new BeanPropertyRowMapper<>(User.class));
  8.     } catch (EmptyResultDataAccessException e) {
  9.         return null;
  10.     }
  11. }
复制代码
当查询效果为空时,捕获 EmptyResultDataAccessException 并返回 null,以制止步伐崩溃。
利用 BeanPropertySqlParameterSource

除了利用 Map 作为参数容器外,还可以利用 BeanPropertySqlParameterSource,它答应你直接将对象的属性作为 SQL 参数绑定。这种方式在对象属性较多时非常有用。
我们可以利用 BeanPropertySqlParameterSource 来简化参数绑定:
  1. public User findUserById(Long id) {
  2.     String sql = "SELECT id, name, email, age FROM users WHERE id = :id";
  3.    
  4.     User user = new User();
  5.     user.setId(id);
  6.    
  7.     SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(user);
  8.     return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, new BeanPropertyRowMapper<>(User.class));
  9. }
复制代码
BeanPropertySqlParameterSource 自动将 User 对象中的属性绑定到 SQL 语句中的命名参数上。这种方式减少了手动创建参数 Map 的步骤,代码更为简便。
查询多条记录

当查询的效果大概包罗多条记录时,NamedParameterJdbcTemplate 提供了 query 方法,可以返回一个包罗所有效果的 List。每个效果可以映射为一个对象。
假设我们希望查询所有年龄大于某个值的用户,可以利用如下代码:
  1. public List<User> findUsersByAge(int age) {
  2.     String sql = "SELECT id, name, email, age FROM users WHERE age > :age";
  3.    
  4.     Map<String, Object> params = new HashMap<>();
  5.     params.put("age", age);
  6.     return namedParameterJdbcTemplate.query(sql, params, new BeanPropertyRowMapper<>(User.class));
  7. }
复制代码
解释


  • sql 语句中的 :age 是命名参数。
  • params 是一个 Map,用于将参数名称与现实值进行绑定。
  • query 方法执行查询,并返回一个包罗所有 User 对象的 List。
利用 MapSqlParameterSource

MapSqlParameterSource 是另一个常用的 SqlParameterSource 实现,它答应你利用链式调用来添加参数,代码更加简便:
  1. public List<User> findUsersByAge(int age) {
  2.     String sql = "SELECT id, name, email, age FROM users WHERE age > :age";
  3.    
  4.     SqlParameterSource namedParameters = new MapSqlParameterSource("age", age);
  5.     return namedParameterJdbcTemplate.query(sql, namedParameters, new BeanPropertyRowMapper<>(User.class));
  6. }
复制代码
MapSqlParameterSource 提供了更加简便的参数设置方式,得当参数较少的情况。
动态 SQL 查询

在现实开发中,查询条件常常是动态的。你可以利用 NamedParameterJdbcTemplate 来构建动态 SQL 查询。
假设我们需要根据多个条件动态查询用户列表:
  1. public List<User> findUsersByCriteria(String name, Integer age) {
  2.     String sql = "SELECT id, name, email, age FROM users WHERE 1=1";
  3.    
  4.     MapSqlParameterSource params = new MapSqlParameterSource();
  5.    
  6.     if (name != null) {
  7.         sql += " AND name = :name";
  8.         params.addValue("name", name);
  9.     }
  10.     if (age != null) {
  11.         sql += " AND age = :age";
  12.         params.addValue("age", age);
  13.     }
  14.     return namedParameterJdbcTemplate.query(sql, params, new BeanPropertyRowMapper<>(User.class));
  15. }
复制代码
解释


  • sql 语句利用了动态拼接的方式,根据传入的参数添加不同的条件。
  • MapSqlParameterSource 提供了动态添加参数的方法,使得代码更具灵活性。
数据库更新与删除

NamedParameterJdbcTemplate 不仅可以用来执行查询操纵,还可以用于执行更新和删除操纵。通过命名参数的支持,更新和删除操纵的代码变得更加简便和易于维护。
利用命名参数进行更新

NamedParameterJdbcTemplate 提供了 update 方法,用于执行 INSERT、UPDATE 和 DELETE 等 DML(数据操纵语言)语句。这个方法支持利用命名参数来绑定 SQL 语句中的参数,使得代码更加清晰。
假设我们需要更新用户的电子邮件地址,可以利用如下代码:
  1. public int updateUserEmail(Long userId, String newEmail) {
  2.     String sql = "UPDATE users SET email = :email WHERE id = :id";
  3.    
  4.     Map<String, Object> params = new HashMap<>();
  5.     params.put("email", newEmail);
  6.     params.put("id", userId);
  7.     return namedParameterJdbcTemplate.update(sql, params);
  8. }
复制代码
解释


  • sql 语句中的 :email 和 :id 是命名参数。
  • params 是一个 Map,用于将参数名称与现实值进行绑定。
  • update 方法执行更新操纵,并返回受影响的行数。
如果你需要更新多个字段,BeanPropertySqlParameterSource 可以简化代码,将对象属性直接绑定到 SQL 参数中。
假设我们需要更新用户的名称和电子邮件地址,可以利用如下代码:
  1. public int updateUserDetails(User user) {
  2.     String sql = "UPDATE users SET name = :name, email = :email WHERE id = :id";
  3.    
  4.     SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(user);
  5.     return namedParameterJdbcTemplate.update(sql, namedParameters);
  6. }
复制代码
解释


  • BeanPropertySqlParameterSource 自动将 User 对象中的属性绑定到 SQL 语句中的命名参数上。
  • update 方法执行更新操纵,并返回受影响的行数。
在现实开发中,你大概需要根据条件动态更新字段。可以结合 MapSqlParameterSource 来实现动态更新。
假设我们需要动态更新用户的名称和电子邮件地址,可以利用如下代码:
  1. public int updateUserDetailsDynamically(Long userId, String name, String email) {
  2.     String sql = "UPDATE users SET ";
  3.     MapSqlParameterSource params = new MapSqlParameterSource("id", userId);
  4.    
  5.     if (name != null) {
  6.         sql += "name = :name, ";
  7.         params.addValue("name", name);
  8.     }
  9.     if (email != null) {
  10.         sql += "email = :email, ";
  11.         params.addValue("email", email);
  12.     }
  13.     // 移除最后的逗号和空格
  14.     sql = sql.substring(0, sql.length() - 2);
  15.     sql += " WHERE id = :id";
  16.    
  17.     return namedParameterJdbcTemplate.update(sql, params);
  18. }
复制代码
解释


  • 动态拼接 SQL 语句,根据传入的参数决定哪些字段需要更新。
  • 利用 MapSqlParameterSource 动态添加参数,制止了冗余代码。
利用命名参数进行删除

NamedParameterJdbcTemplate 的 update 方法同样实用于删除操纵。通过命名参数,可以更灵活地控制删除的条件。
假设我们需要根据用户的 ID 删除用户记录,可以利用如下代码:
  1. public int deleteUserById(Long userId) {
  2.     String sql = "DELETE FROM users WHERE id = :id";
  3.    
  4.     Map<String, Object> params = new HashMap<>();
  5.     params.put("id", userId);
  6.     return namedParameterJdbcTemplate.update(sql, params);
  7. }
复制代码
解释


  • sql 语句中的 :id 是命名参数。
  • params 是一个 Map,用于将参数名称与现实值进行绑定。
  • update 方法执行删除操纵,并返回受影响的行数。
有时删除操纵大概会涉及多个条件,可以利用 MapSqlParameterSource 来处理这些条件。假设我们需要根据用户的年龄和电子邮件删除用户记录,可以利用如下代码:
  1. public int deleteUsersByAgeAndEmail(int age, String email) {
  2.     String sql = "DELETE FROM users WHERE age = :age AND email = :email";
  3.    
  4.     MapSqlParameterSource params = new MapSqlParameterSource();
  5.     params.addValue("age", age);
  6.     params.addValue("email", email);
  7.     return namedParameterJdbcTemplate.update(sql, params);
  8. }
复制代码
解释


  • sql 语句中利用了多个命名参数 :age 和 :email。
  • 利用 MapSqlParameterSource 来绑定多个参数,并传递给 update 方法进行删除操纵。
在处理大批量数据更新或删除时,NamedParameterJdbcTemplate 的 batchUpdate 方法可以明显提高性能。这个方法支持批量处理,并且可以结合命名参数利用。
假设我们需要批量更新用户的电子邮件地址,可以利用如下代码:
  1. public int[] batchUpdateEmails(List<User> users) {
  2.     String sql = "UPDATE users SET email = :email WHERE id = :id";
  3.    
  4.     SqlParameterSource[] batchParams = SqlParameterSourceUtils.createBatch(users.toArray());
  5.    
  6.     return namedParameterJdbcTemplate.batchUpdate(sql, batchParams);
  7. }
复制代码
解释


  • SqlParameterSourceUtils.createBatch 可以将用户列表转换为 SqlParameterSource 数组,每个元素表示一批次操纵的参数。
  • batchUpdate 方法执行批量更新操纵,返回一个 int[],每个元素表示对应批次受影响的行数。
同样的,你也可以利用 batchUpdate 方法来批量删除用户:
  1. public int[] batchDeleteUsers(List<Long> userIds) {
  2.     String sql = "DELETE FROM users WHERE id = :id";
  3.    
  4.     SqlParameterSource[] batchParams = SqlParameterSourceUtils.createBatch(userIds.toArray());
  5.    
  6.     return namedParameterJdbcTemplate.batchUpdate(sql, batchParams);
  7. }
复制代码
将用户 ID 列表转换为 SqlParameterSource 数组,然后利用 batchUpdate 方法批量执行删除操纵。
批量操纵与命名参数

NamedParameterJdbcTemplate 提供了强盛的批量操纵支持,答应你利用命名参数进行大批量的数据插入、更新和删除操纵。通过批量操纵,你可以明显提高性能,尤其是在处理大量数据时。
批量插入

在批量插入操纵中,NamedParameterJdbcTemplate 的 batchUpdate 方法答应你一次性插入多条记录,而不需要为每条记录单独执行插入操纵。这样可以减少数据库的交互次数,从而提高插入效率。
假设我们有一个 User 类,并需要批量插入多个用户记录到 users 表中:
  1. public int[] batchInsertUsers(List<User> users) {
  2.     String sql = "INSERT INTO users (id, name, email, age) VALUES (:id, :name, :email, :age)";
  3.    
  4.     SqlParameterSource[] batchParams = SqlParameterSourceUtils.createBatch(users.toArray());
  5.    
  6.     return namedParameterJdbcTemplate.batchUpdate(sql, batchParams);
  7. }
复制代码
解释


  • sql 语句界说了插入操纵的结构,并利用命名参数 :id、:name、:email 和 :age。
  • SqlParameterSourceUtils.createBatch(users.toArray()) 将用户列表转换为 SqlParameterSource 数组,每个数组元素表示一条记录的插入参数。
  • batchUpdate 方法执行批量插入操纵,返回一个 int[] 数组,表示每批次操纵受影响的行数。
如果你需要对每条记录的插入参数进行额外的处理,可以利用 MapSqlParameterSource 来构建批量插入参数:
  1. public int[] batchInsertUsersWithMap(List<User> users) {
  2.     String sql = "INSERT INTO users (id, name, email, age) VALUES (:id, :name, :email, :age)";
  3.    
  4.     SqlParameterSource[] batchParams = users.stream()
  5.         .map(user -> new MapSqlParameterSource()
  6.             .addValue("id", user.getId())
  7.             .addValue("name", user.getName())
  8.             .addValue("email", user.getEmail())
  9.             .addValue("age", user.getAge()))
  10.         .toArray(SqlParameterSource[]::new);
  11.    
  12.     return namedParameterJdbcTemplate.batchUpdate(sql, batchParams);
  13. }
复制代码
利用 MapSqlParameterSource 可以灵活地为每条记录设置参数。这种方法答应你在构建参数时进行额外的处理,如转换或校验。
批量更新与删除

与批量插入类似,NamedParameterJdbcTemplate 的 batchUpdate 方法也支持批量更新和删除操纵。
假设我们需要批量更新用户的电子邮件地址,可以利用如下代码:
  1. public int[] batchUpdateUserEmails(List<User> users) {
  2.     String sql = "UPDATE users SET email = :email WHERE id = :id";
  3.    
  4.     SqlParameterSource[] batchParams = SqlParameterSourceUtils.createBatch(users.toArray());
  5.    
  6.     return namedParameterJdbcTemplate.batchUpdate(sql, batchParams);
  7. }
复制代码
解释


  • sql 语句用于界说更新操纵,其中利用命名参数 :email 和 :id。
  • SqlParameterSourceUtils.createBatch 将用户列表转换为批量参数数组。
  • batchUpdate 方法执行批量更新操纵,返回每个更新操纵的受影响行数。
如果更新操纵需要处理复杂的逻辑,可以利用 MapSqlParameterSource 来动态构建更新参数:
  1. public int[] batchUpdateUserDetails(List<User> users) {
  2.     String sql = "UPDATE users SET name = :name, email = :email, age = :age WHERE id = :id";
  3.    
  4.     SqlParameterSource[] batchParams = users.stream()
  5.         .map(user -> new MapSqlParameterSource()
  6.             .addValue("id", user.getId())
  7.             .addValue("name", user.getName())
  8.             .addValue("email", user.getEmail())
  9.             .addValue("age", user.getAge()))
  10.         .toArray(SqlParameterSource[]::new);
  11.    
  12.     return namedParameterJdbcTemplate.batchUpdate(sql, batchParams);
  13. }
复制代码
同样的,batchUpdate 方法也可以用于批量删除操纵。假设我们需要根据用户的 ID 批量删除用户记录,可以利用如下代码:
  1. public int[] batchDeleteUsers(List<Long> userIds) {
  2.     String sql = "DELETE FROM users WHERE id = :id";
  3.    
  4.     SqlParameterSource[] batchParams = userIds.stream()
  5.         .map(id -> new MapSqlParameterSource("id", id))
  6.         .toArray(SqlParameterSource[]::new);
  7.    
  8.     return namedParameterJdbcTemplate.batchUpdate(sql, batchParams);
  9. }
复制代码
  TIP:批量操纵的留意事项
  

  • 性能提升:批量操纵可以明显减少数据库的交互次数,从而提升性能。但批量操纵的巨细应根据现实情况调整,制止一次性处理过多数据导致内存不足或数据库压力过大。
  • 事务控制:批量操纵通常在事务中执行,确保操纵要么全部成功,要么全部回滚。在批量操纵中,若某一批次失败,事务回滚后不会保留之前的成功操纵效果。
  • 非常处理:对于批量操纵,发起添加适当的非常处理逻辑,确保在出现非常时,能够有效地回滚事务并记录相关错误信息。
  利用 NamedParameterJdbcTemplate 执行复杂 SQL

在现实应用中,SQL 查询往往不仅仅是简单的 CRUD 操纵,大概需要根据不同的业务逻辑构建动态 SQL,或者执行复杂的查询。NamedParameterJdbcTemplate 提供了灵活的工具来处理这些场景,使得代码更加清晰易懂,并且更易于维护。
动态 SQL 的构建

动态 SQL 是指在运行时根据不同的条件构建 SQL 查询。这在处理复杂的查询条件时尤其有用。NamedParameterJdbcTemplate 可以结合 MapSqlParameterSource 或 BeanPropertySqlParameterSource 来动态地构建 SQL 查询,并绑定参数。
假设我们有一个需求,需要根据用户的名称和年龄来查询用户列表,如果某个条件为空,则不利用该条件进行过滤。可以利用如下代码:
  1. public List<User> findUsersByDynamicCriteria(String name, Integer age) {
  2.     StringBuilder sql = new StringBuilder("SELECT id, name, email, age FROM users WHERE 1=1");
  3.     MapSqlParameterSource params = new MapSqlParameterSource();
  4.    
  5.     if (name != null) {
  6.         sql.append(" AND name = :name");
  7.         params.addValue("name", name);
  8.     }
  9.    
  10.     if (age != null) {
  11.         sql.append(" AND age = :age");
  12.         params.addValue("age", age);
  13.     }
  14.    
  15.     return namedParameterJdbcTemplate.query(sql.toString(), params, new BeanPropertyRowMapper<>(User.class));
  16. }
复制代码
解释


  • StringBuilder 用于构建动态 SQL 查询,根据条件拼接不同的 WHERE 子句。
  • MapSqlParameterSource 动态添加参数值,确保只有在条件不为空时才会添加相应的 SQL 语句和参数。
  • query 方法最终执行查询,并返回效果列表。
对于更加复杂的业务逻辑,大概需要构建包罗多个 JOIN、GROUP BY 或 ORDER BY 子句的动态 SQL 查询。以下是一个示例,展示了怎样根据多个条件构建复杂的 SQL 查询:
  1. public List<User> findUsersWithOrders(String name, Integer minAge, Integer maxAge, String orderBy) {
  2.     StringBuilder sql = new StringBuilder("SELECT u.id, u.name, u.email, u.age, COUNT(o.id) as order_count FROM users u ");
  3.     sql.append("LEFT JOIN orders o ON u.id = o.user_id WHERE 1=1 ");
  4.    
  5.     MapSqlParameterSource params = new MapSqlParameterSource();
  6.    
  7.     if (name != null) {
  8.         sql.append(" AND u.name = :name");
  9.         params.addValue("name", name);
  10.     }
  11.    
  12.     if (minAge != null) {
  13.         sql.append(" AND u.age >= :minAge");
  14.         params.addValue("minAge", minAge);
  15.     }
  16.    
  17.     if (maxAge != null) {
  18.         sql.append(" AND u.age <= :maxAge");
  19.         params.addValue("maxAge", maxAge);
  20.     }
  21.    
  22.     sql.append(" GROUP BY u.id, u.name, u.email, u.age");
  23.    
  24.     if (orderBy != null) {
  25.         sql.append(" ORDER BY ").append(orderBy);
  26.     }
  27.    
  28.     return namedParameterJdbcTemplate.query(sql.toString(), params, new BeanPropertyRowMapper<>(User.class));
  29. }
复制代码
解释


  • 这个示例展示了如安在动态 SQL 中利用 JOIN、GROUP BY 和 ORDER BY 子句。
  • MapSqlParameterSource 用于安全地绑定参数,制止 SQL 注入的风险。
在现实应用中,利用 IN 子句进行批量查询是很常见的需求。NamedParameterJdbcTemplate 提供了灵活的方式来处理这种场景。
  1. public List<User> findUsersByIds(List<Long> ids) {
  2.     String sql = "SELECT id, name, email, age FROM users WHERE id IN (:ids)";
  3.    
  4.     MapSqlParameterSource params = new MapSqlParameterSource("ids", ids);
  5.    
  6.     return namedParameterJdbcTemplate.query(sql, params, new BeanPropertyRowMapper<>(User.class));
  7. }
复制代码
:ids 命名参数通过 MapSqlParameterSource 绑定到 List<Long> 参数中。NamedParameterJdbcTemplate 会自动将列表参数转换为适当的 SQL 格式,天生类似 IN (1, 2, 3) 的 SQL 语句。
复杂查询的处理与优化

在处理复杂查询时,除了动态 SQL,还需要考虑查询性能的优化。NamedParameterJdbcTemplate 提供了一些功能来帮助你优化查询操纵。
分页查询在处理大量数据时非常有用,它可以减少每次查询返回的数据量,从而提高性能。以下是一个分页查询的示例:
  1. public List<User> findUsersWithPagination(int pageNumber, int pageSize) {
  2.     String sql = "SELECT id, name, email, age FROM users LIMIT :offset, :limit";
  3.    
  4.     int offset = (pageNumber - 1) * pageSize;
  5.     MapSqlParameterSource params = new MapSqlParameterSource();
  6.     params.addValue("offset", offset);
  7.     params.addValue("limit", pageSize);
  8.    
  9.     return namedParameterJdbcTemplate.query(sql, params, new BeanPropertyRowMapper<>(User.class));
  10. }
复制代码
解释


  • 利用 LIMIT 子句结合 OFFSET 参数实现分页查询。
  • MapSqlParameterSource 用于安全地绑定分页参数。
有时,你大概需要在一个查询中嵌套另一个查询(子查询),或者需要在一个查询中团结多个表的数据。NamedParameterJdbcTemplate 可以帮助你处理这些复杂的查询场景。
  1. public List<UserOrderSummary> findUserOrderSummaries() {
  2.     String sql = "SELECT u.id, u.name, u.email, (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) as order_count " +
  3.                  "FROM users u";
  4.    
  5.     return namedParameterJdbcTemplate.query(sql, new MapSqlParameterSource(), (rs, rowNum) -> {
  6.         UserOrderSummary summary = new UserOrderSummary();
  7.         summary.setUserId(rs.getLong("id"));
  8.         summary.setUserName(rs.getString("name"));
  9.         summary.setUserEmail(rs.getString("email"));
  10.         summary.setOrderCount(rs.getInt("order_count"));
  11.         return summary;
  12.     });
  13. }
复制代码
在这个示例中,我们利用子查询来盘算每个用户的订单数量,并将效果映射到自界说的 UserOrderSummary 对象中。
在构建复杂 SQL 查询时,除了利用 NamedParameterJdbcTemplate 进行参数绑定和动态构建外,还应留意以下几点以优化性能:


  • 索引优化:确保查询条件涉及的列上有适当的索引,以提高查询性能。
  • 制止全表扫描:通过 WHERE 子句限定查询的效果集,制止全表扫描。
  • 减少嵌套查询:尽量减少嵌套查询的利用,尤其是在数据量较大的情况下,嵌套查询大概会明显低落性能。
  • 批量操纵:利用批量操纵代替逐条操纵,减少数据库的交互次数。
总结

JdbcTemplate vs NamedParameterJdbcTemplate

JdbcTemplate:
优点缺点简化 JDBC 编程:JdbcTemplate 大大简化了传统的 JDBC 编程,消除了诸如资源管理、非常处理等繁琐的代码。代码维护性较差:由于 SQL 语句和业务逻辑精密耦合,随着业务复杂度的增加,代码的维护性会有所低落。性能优秀:相比于其他数据访问技术(如 JPA),JdbcTemplate 更加轻量级,通常能提供更好的性能,尤其是在不需要复杂 ORM 功能的场景中。缺乏灵活性:对于复杂的对象映射和关联查询,JdbcTemplate 大概显得力不从心,需要手动处理映射逻辑。强盛的批量操纵支持:JdbcTemplate 提供了强盛的批量操纵功能,实用于处理大数据量的插入、更新和删除。 NamedParameterJdbcTemplate:
优点缺点支持命名参数:相比 JdbcTemplate,NamedParameterJdbcTemplate 支持利用命名参数,SQL 语句更加直观,参数绑定更为灵活,尤其得当复杂的 SQL 查询。性能略低:在大多数情况下,NamedParameterJdbcTemplate 的性能略低于 JdbcTemplate,这是由于额外的参数剖析和绑定过程。更好的可读性:由于支持命名参数,代码的可读性和可维护性有所提升,特殊是在有大量参数的场景中。复杂性增加:对于简单的 SQL 操纵,NamedParameterJdbcTemplate 引入的额外复杂性大概并不划算。 利用发起

利用 JdbcTemplate 和 NamedParameterJdbcTemplate 时,有一些最佳实践可以帮助提高代码的质量和体系的性能。

  • 分层架构与单一职责原则

    • 分层架构:在数据访问层中,发起采用分层架构,将数据访问逻辑与业务逻辑分开。通过 DAO 层(数据访问对象)封装数据访问操纵,可以提高代码的复用性和维护性。
    • 单一职责原则:每个类或方法只负责一个明确的功能,制止类或方法负担过多职责。这有助于代码的清晰性和易维护性。

  • 利用事务管理确保数据同等性

    • 声明式事务管理:优先利用声明式事务管理,通过 @Transactional 注解简化事务管理,确保多个数据库操纵在同一事务中执行。
    • 事务传播与隔离级别:根据业务需求合理设置事务的传播行为和隔离级别,制止脏读、不可重复读和幻读等问题。

  • SQL 优化与索引利用


  • SQL 优化:定期检察和优化 SQL 语句,制止全表扫描,尽量利用索引覆盖查询。复杂查询可以拆分为多个简单查询,或利用数据库视图、存储过程等方式进行优化。
  • 索引利用:确保在常用的查询条件字段上创建符合的索引,尤其是在高并发和大数据量场景中。定期检查并优化索引,制止不必要的性能开销。

  • 合理利用批量操纵

    • 批量操纵:对于大数据量的插入、更新和删除操纵,优先考虑利用 batchUpdate 方法进行批量处理,减少数据库交互次数,提升性能。
    • 批次巨细调优:根据现实情况调整批次巨细,制止一次性处理过多数据导致内存不足或数据库压力过大。

  • 利用缓存机制

    • 缓存:对于频仍访问但变革不频仍的数据,利用缓存机制来减少数据库查询次数。Spring 提供了便捷的缓存支持,可以结合 @Cacheable 注解来实现。

常见问题

常见错误与非常处理

在利用 JdbcTemplate 和 NamedParameterJdbcTemplate 进行开发时,常见的错误和非常告急会合在 SQL 语法错误、数据绑定非常和数据库连接问题上。
SQL 语法错误

问题形貌:SQL 语法错误通常发生在拼写错误、缺少关键字或不正确的 SQL 结构时。
解决方法


  • 日志记录:启用 SQL 日志记录功能,检查天生的 SQL 语句是否正确。可以通过配置 logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG 在日志中输出 SQL 语句。
  • 数据库工具检查:在数据库管理工具中执行天生的 SQL 语句,验证语法的正确性。
数据绑定非常

问题形貌:数据绑定非常通常发生在传递给 SQL 语句的参数类型不匹配时,例如传递 null 值给非空字段。
解决方法


  • 类型检查:确保传递给 SQL 语句的参数类型与数据库表中对应字段的类型匹配。
  • 空值处理:在需要传递 null 值的场景中,确保数据库表字段答应空值,并利用 MapSqlParameterSource.addValue(String paramName, Object value, int sqlType) 指定正确的 SQL 类型。
数据库连接问题

问题形貌:数据库连接问题通常与数据库配置、连接池设置或网络问题相关,常见的错误包括连接超时、连接池耗尽等。
解决方法


  • 连接池配置检查:确保数据库连接池配置合理,包括连接池巨细、超时时间等参数。
  • 数据库状态检查:检查数据库是否正常运行,并确保网络连接稳固。
性能问题的排查与解决

在现实应用中,性能问题大概来自多个方面,包括数据库查询效率、连接池配置、批量操纵等。
数据库慢查询分析

问题形貌:性能问题通常首先出现在数据库查询上,特殊是复杂查询和大数据量查询。
解决方法


  • 慢查询日志:启用数据库的慢查询日志功能,分析和优化执行时间较长的 SQL 语句。
  • 索引优化:检查慢查询中涉及的字段,确保有符合的索引。制止全表扫描,必要时重建索引。
连接池配置调优

问题形貌:不合理的连接池配置大概导致连接池耗尽或相应速率迟钝。
解决方法


  • 监控连接池:利用监控工具检查连接池的利用情况,调整 maximum-pool-size 和 minimum-idle 参数,确保连接池能够应对高并发请求。
  • 优化连接复用:制止频仍地创建和关闭数据库连接,尽量复用已有连接。
批量操纵调优

问题形貌:批量操纵在处理大数据量时大概会由于批次巨细不妥或事务处理问题导致性能下降。
解决方法


  • 调整批次巨细:根据具体情况调整批次巨细,制止一次性处理过多数据导致内存不足或数据库压力过大。
  • 优化事务处理:确保批量操纵在合理的事务范围内执行,制止因事务过大而导致锁定问题。
怎样选择 JdbcTemplate 与其他数据访问技术(如JPA)

选择符合的数据访问技术至关告急。JdbcTemplate 和 JPA(Java Persistence API)是两种常见的数据访问技术,它们各有优缺点,实用于不同的应用场景。
JdbcTemplate 的实用场景

优势回顾:


  • 性能优越:JdbcTemplate 是轻量级的数据访问工具,不引入复杂的 ORM 机制,性能通常优于 JPA,尤其是在数据量较大或要求相应时间较短的场景中。
  • 灵活性高:JdbcTemplate 直接操纵 SQL 语句,开发者可以完全控制 SQL 的执行过程,得当对 SQL 语句有特殊需求的场景。
  • 低内存开销:相比于 JPA 的实体管理机制,JdbcTemplate 在内存开销上更为轻量,得当资源受限的情况。
实用场景:


  • 需要直接控制 SQL 语句,制止 ORM 框架带来的性能开销。
  • 简单 CRUD 操纵较多,不需要复杂的对象关系映射。
  • 应用步伐性能要求较高,特殊是对数据库操纵的相应时间有严酷要求。
JPA 的实用场景

优势回顾:


  • 简化开发:JPA 提供了基于对象的长期化操纵,自动管理实体对象的状态,开发者可以专注于业务逻辑而不是 SQL 语句。
  • 复杂关系映射:JPA 支持复杂的对象关系映射,自动管理一对一、一对多、多对多的关系,得当数据模型复杂的应用。
  • 事务支持:JPA 内置了事务管理,结合 Spring 的声明式事务,可以更加轻松地管理事务。
实用场景:


  • 业务逻辑复杂,涉及大量的对象关系映射和关联查询。
  • 应用步伐开发周期较短,需要通过自动化工具减少开发工作量。
  • 对性能要求相对宽松,愿意担当 ORM 带来的性能开销。
怎样选择



  • 业务复杂度:如果应用步伐需要处理复杂的对象关系映射,并且对开发效率要求较高,JPA 是更好的选择。如果应用步伐的业务逻辑相对简单,并且对性能有严酷要求,JdbcTemplate 更加符合。
  • 团队经验:如果开发团队对 SQL 和数据库操纵非常熟悉,可以选择 JdbcTemplate。如果团队更熟悉对象模型和 Java EE 标准,JPA 大概是更自然的选择。
  • 性能要求:在性能要求特殊高的场景中,如大数据量处理、高并发请求等,JdbcTemplate 通常能够提供更好的性能。
  • 灵活性:在需要灵活定制 SQL 语句和数据库操纵的场景中,JdbcTemplate 更能满足需求。JPA 在这方面大概会受到 ORM 机制的限定。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

去皮卡多

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表