Spring JDBC - JdbcTemplate

  1. 一、JdbcTemplate 类介绍
  2. 二、JdbcTemplate 方法使用概述
    1. 1、execute 系列
      1. 1、execute(final String sql)
      2. 2、execute(StatementCallback< T > action)
      3. 3、execute(ConnectionCallback< T > action)
      4. 4、execute(String sql, PreparedStatementCallback< T > action)
      5. 5、execute(PreparedStatementCreator psc, PreparedStatementCallback< T > action)
      6. 6、execute(String callString, CallableStatementCallback< T > action)
      7. 7、execute(CallableStatementCreator csc, CallableStatementCallback< T > action)
    2. 2、update 系列
      1. 1、update(final String sql)
      2. 2、update(String sql, @Nullable Object… args)
      3. 3、update(String sql, Object[] args, int[] argTypes)
      4. 4、update(String sql, @Nullable PreparedStatementSetter pss)
      5. 5、update(PreparedStatementCreator psc)
      6. 6、update(final PreparedStatementCreator psc, final KeyHolder generatedKeyHolder)
    3. 3、batchUpdate 系列
      1. 1、batchUpdate(final String… sql)
      2. 2、batchUpdate(String sql, List<Object[]> batchArgs)
      3. 3、batchUpdate(String sql, List<Object[]> batchArgs, final int[] argTypes)
      4. 4、batchUpdate(String sql, final BatchPreparedStatementSetter pss)
      5. 5、batchUpdate(String sql, final Collection< T > batchArgs, final int batchSize, final ParameterizedPreparedStatementSetter< T > pss)
      6. 6、批量插入并返回主键 ID
    4. 4、call 系列
      1. 1、call(CallableStatementCreator csc, List< SqlParameter > declaredParameters)
    5. 5、queryForObject 系列
      1. 1、queryForObject(String sql, RowMapper< T > rowMapper)
      2. 2、queryForObject(String sql, RowMapper< T > rowMapper, @Nullable Object… args)
      3. 3、queryForObject(String sql, Object[] args, int[] argTypes, RowMapper< T > rowMapper)
      4. 4、queryForObject(String sql, Class< T > requiredType)
      5. 5、queryForObject(String sql, Class< T > requiredType, @Nullable Object… args)
      6. 6、queryForObject(String sql, Object[] args, int[] argTypes, Class< T > requiredType)
    6. 6、queryForMap 系列
      1. 1、queryForMap(String sql)
      2. 2、queryForMap(String sql, @Nullable Object… args)
      3. 3、queryForMap(String sql, Object[] args, int[] argTypes)
    7. 7、queryForRowSet 系列
      1. 1、queryForRowSet(String sql)
      2. 2、queryForRowSet(String sql, @Nullable Object… args)
      3. 3、queryForRowSet(String sql, Object[] args, int[] argTypes)
    8. 8、queryForList 系列
      1. 1、queryForList(String sql)
      2. 2、queryForList(String sql, @Nullable Object… args)
      3. 3、queryForList(String sql, Object[] args, int[] argTypes)
      4. 4、queryForList(String sql, Class< T > elementType)
      5. 5、queryForList(String sql, Class< T > elementType, @Nullable Object… args)
      6. 6、queryForList(String sql, Object[] args, int[] argTypes, Class< T > elementType)
    9. 9、query 系列
      1. 1、query(String sql, RowCallbackHandler rch)
      2. 2、query(String sql, RowCallbackHandler rch, @Nullable Object… args)
      3. 3、query(String sql, Object[] args, int[] argTypes, RowCallbackHandler rch)
      4. 4、query(String sql, @Nullable PreparedStatementSetter pss, RowCallbackHandler rch)
      5. 5、query(PreparedStatementCreator psc, RowCallbackHandler rch)
      6. 6、query(String sql, ResultSetExtractor rse)
      7. 7、query(String sql, ResultSetExtractor rse, @Nullable Object… args)
      8. 8、query(String sql, Object[] args, int[] argTypes, ResultSetExtractor rse)
      9. 9、query(String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor< T > rse)
      10. 10、query(PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, final ResultSetExtractor< T > rse)
      11. 11、query(PreparedStatementCreator psc, final ResultSetExtractor< T > rse)
      12. 12、query(String sql, RowMapper< T > rowMapper)
      13. 13、query(String sql, RowMapper< T > rowMapper, @Nullable Object… args)
      14. 14、query(String sql, Object[] args, int[] argTypes, RowMapper< T > rowMapper
      15. 15、query(String sql, @Nullable PreparedStatementSetter pss, RowMapper< T > rowMapper)
      16. 16、query(PreparedStatementCreator psc, RowMapper< T > rowMapper)
  3. 三、Junit 测试 ResultSetExtractor/RowMapper
    1. 1、RowMapper Mockito 测试
    2. 2、ResultSetExtractor Mockito 测试
  4. 四、Spring JDBC 其他对象
    1. 1、NamedParameterJdbcTemplate
    2. 2、SimpleJdbcInsert
    3. 3、SqlQuery
    4. 4、SqlUpdate
    5. 5、SimpleJdbcCall
    6. 6、总结:为什么需要这些类?
  5. 五、JdbcTemplate 参考文献 & 鸣谢

一、JdbcTemplate 类介绍

Spring 对数据库的操作在 JDBC 上面做了深层次的封装,使用 Spring 的注入功能,可以把 DataSource 注册到 JdbcTemplate 中。

JdbcTemplate 是 Spring 框架中提供的一个对象,是对原始繁琐的 Jdbc API 对象的简单封装。Spring 框架为我们提供了很多的操作模板类。例如:操作关系型数据的 JdbcTemplate 和 HibernateTemplate,操作 NoSQL 数据库的 RedisTemplate,操作消息队列的JmsTemplate等等。

JdbcTemplate 位于 spring-jdbc-x.x.x.jar 中,其全限定命名为 org.springframework.jdbc.core.JdbcTemplate。要使用 JdbcTemplate 还需要一个 spring-tx-x.x.x.jar 包,这个包含了事务和异常控制。

  • spring-jdbc-x.x.x.jar
  • spring-tx-x.x.x.jar
  • spring-core-x.x.x.jar
  • spring-beans-x.x.x.jar

环境搭建:

  • 需要导入Spring的 tx 和 jdbc 模块
  • 需要相关数据库的驱动
  • 数据库连接池(仅就本文非必需)
  • 需要有相应的数据库进行操作

1、导入依赖

<properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
    <spring-version>5.3.29</spring-version>
    <mysql-connector-version>5.1.49</mysql-connector-version>
</properties>

<dependencies>
    <!-- Spring的 context 和 jdbc 模块 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring-version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${spring-version}</version>
    </dependency>

    <!-- H2数据库驱动 -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>2.2.220</version>
    </dependency>

    <!-- Mysql数据库驱动 -->
    <!--        <dependency>-->
    <!--            <groupId>mysql</groupId>-->
    <!--            <artifactId>mysql-connector-java</artifactId>-->
    <!--            <version>${mysql-connector-version}</version>-->
    <!--        </dependency>-->
    <!-- 阿里的德鲁伊连接池 -->
    <!--        <dependency>-->
    <!--            <groupId>com.alibaba</groupId>-->
    <!--            <artifactId>druid</artifactId>-->
    <!--            <version>${druid-version}</version>-->
    <!--        </dependency>-->
</dependencies>
<repositories>
    <repository>
        <id>nexus-aliyun</id>
        <name>nexus-aliyun</name>
        <url>https://maven.aliyun.com/nexus/content/groups/public/</url>
        <releases>
            <enabled>true</enabled>
        </releases>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

2、简单使用

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

public class Main {
    public static void main(String[] args) {
        // 获取数据源, 这里使用H数据库, 不知道为什么使用mem内存模式一直报错, 所以使用file文件模式
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("org.h2.Driver"); // 驱动
        dataSource.setUrl("jdbc:h2:file:~/test;;DATABASE_TO_UPPER=false"); // 数据库连接
        dataSource.setUsername("sa"); // 用户
        dataSource.setPassword(""); // 密码
        // 如果要使用其他数据源只需要修改上面部分, 然后把dataSource注入JdbcTemplate即可

        // 获取 JdbcTemplate
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

        // 先初始化表和初始化数据
        jdbcTemplate.execute("drop table if exists user_info");
        jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
        jdbcTemplate.update("insert into user_info values(1, 'Java')");
        jdbcTemplate.update("insert into user_info values(2, 'Python')");
        jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

        // 查询数据
        System.out.println(jdbcTemplate.queryForList("select * from user_info"));
    }
}
[{user_id=1, user_name=Java}, {user_id=2, user_name=Python}, {user_id=3, user_name=JavaScript}]

3、配置文件定义,将数据源连接池以及 jdbcTemplate 改为配置文件定义。或者直接@Bean定义也行。

<!-- 德鲁伊连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</bean>

<!-- jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!-- 设置数据源 -->
    <property name="dataSource" ref="dataSource"/>
</bean>

二、JdbcTemplate 方法使用概述

JdbcTemplate 主要提供以下五类或四类方法:

  • execute 方法:可以用于执行任何SQL语句,一般用于执行DDL语句;

  • update 方法及 batchUpdate 方法:用于执行新增、修改、删除等DML语句;batchUpdate 方法用于执行批量处理相关语句。

  • query 方法及 queryForXXX 方法:用于执行查询相关DQL语句;

  • call 方法:用于执行存储过程、函数相关语句。

注意:所有执行查询的语句,都要注意查询结果为空的情况,有些方法对于空的查询结果会报异常。

其中主要三类操作如下:

  • execute:可以执行所有SQL语句,一般用于执行DDL语句。
  • update:用于执行INSERT、UPDATE、DELETE等DML语句。
  • queryXxx:用于DQL数据查询语句。

query 方法及 queryForXXX 方法 重点概述:

  • queryForObject:查询结果,将结果封装为对象,一般用于聚合函数的查询。一般用户单行单列或单行多列
  • queryForMap():查询结果将结果集封装为map集合,列名作为key,值作为value。 该方法只能查一行数据。一般用于单行多列
  • queryForList():查询结果将结果集封装为list集合。注意:每一条记录为一个Map集合,再将Map集合装载到List集合中。一般用于多行多列。
  • queryForRowSet:查询结果将结果集封装为SqlRowSet对象,该对象是一个集合,可以理解为ResultSet对象,一般用于多行多列。
  • queryForStream:查询结果将结果集封装为Stream对象,该对象可以转换成绩集合也可以是单个对象。一般用于多行多列。
  • query():查询结果,将结果封装为JavaBean对象。
    1. query 的参数:RowCallbackHandler,这个回调方式,一条记录回调一次,不返回结果。
    2. query 的参数:ResultSetExtractor,这个回调方式,接收的是批量的结果,一次对所有的结果进行转换。返回类型为泛型ResultSetExtractor< T >,T 可以是单个JavaBean也可以是一个List。
    3. query 的参数:RowMapper,一般我们使用BeanPropertyRowMapper实现类。可以完成数据到JavaBean的自动封装new BeanPropertyRowMapper<类型>(类型.class)。RowMapper 返回结果是List。

JdbcTemplate 类支持的回调类:

  • 预编译语句及存储过程创建回调:用于根据JdbcTemplate提供的连接创建相应的语句;
    • PreparedStatementCreator:通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的PreparedStatement;
    • CallableStatementCreator:通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的CallableStatement;
  • 预编译语句设值回调:用于给预编译语句相应参数设值;
    • PreparedStatementSetter:通过回调获取JdbcTemplate提供的PreparedStatement,由用户来对相应的预编译语句相应参数设值;
    • BatchPreparedStatementSetter:类似于PreparedStatementSetter,但用于批处理,需要指定批处理大小;
  • 自定义功能回调:提供给用户一个扩展点,用户可以在指定类型的扩展点执行任何数量需要的操作;
    • StatementCallback:通过回调获取JdbcTemplate提供的Statement,用户可以在该Statement执行任何数量的操作;
    • PreparedStatementCallback:通过回调获取JdbcTemplate提供的PreparedStatement,用户可以在该PreparedStatement执行任何数量的操作;
    • CallableStatementCallback:通过回调获取JdbcTemplate提供的CallableStatement,用户可以在该CallableStatement执行任何数量的操作;
  • 结果集处理回调:通过回调处理ResultSet或将ResultSet转换为需要的形式;
    • RowMapper:用于将结果集每行数据转换为需要的类型,用户需实现方法mapRow(ResultSet rs, int rowNum)来完成将每行数据转换为相应的类型。
    • RowCallbackHandler:用于处理ResultSet的每一行结果,用户需实现方法processRow(ResultSet rs)来完成处理,在该回调方法中无需执行rs.next(),该操作由JdbcTemplate来执行,用户只需按行获取数据然后处理即可。
    • ResultSetExtractor:用于结果集数据提取,用户需实现方法extractData(ResultSet rs)来处理结果集,用户必须处理整个结果集;

1、execute 系列

1、execute(final String sql)

public void execute(final String sql) throws DataAccessException {}

该方法可以执行任意SQL,参数是一个SQL,没有返回值。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute1() {
    jdbcTemplate.execute("drop table if exists user_info"); // 删除表
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))"); // 创建表
    jdbcTemplate.execute("insert into user_info values(1, 'Java')"); // 插入数据
    jdbcTemplate.execute("update user_info set user_name = 'Python' where user_id = 1"); // 更新数据
    jdbcTemplate.execute("delete from user_info where user_id = 1"); // 删除数据
    jdbcTemplate.execute("truncate table user_info"); // 清空表数据
    jdbcTemplate.execute("begin add_data_to_account; end;"); // 执行存储过程
}

2、execute(StatementCallback< T > action)

public <T> T execute(StatementCallback<T> action) throws DataAccessException {}

此方法是传入一个 StatementCallback 对象。StatementCallback 接口只有一个方法。

@FunctionalInterface
public interface StatementCallback<T> {
    @Nullable
    T doInStatement(Statement stmt) throws SQLException, DataAccessException;
}

通过回调获取 JdbcTemplate 提供的 Statement,用户可以在该 Statement 进行 SQL 操作。

其实 execute(final String sql) 该方法就是调用 execute(StatementCallback< T> action) 这个方法实现的。可以执行任意 SQL。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute2() {
    // 删除表、创建表、新增数据
    Integer num = jdbcTemplate.execute(new StatementCallback<Integer>() {
        @Override
        public Integer doInStatement(Statement st) throws SQLException, DataAccessException {
            st.execute("drop table if exists user_info"); // 删除表
            st.execute("create table user_info(user_id int primary key, user_name varchar(255))"); // 创建表
            return st.executeUpdate("insert into user_info values(1, 'Java')"); // 插入数据
        }
    });
    System.out.println("插入数据: " + num + " 条");

    // 更新数据、删除数据。采用Lambda表达式
    num = jdbcTemplate.execute((StatementCallback<Integer>) st -> {
        // 更新数据
        int numByUpdate = st.executeUpdate("update user_info set user_name = 'Python' where user_id = 1");
        System.out.println("更新数据: " + numByUpdate + " 条");
        return st.executeUpdate("delete from user_info where user_id = 1"); // 删除数据
    });
    System.out.println("删除数据: " + num + " 条");
}
插入数据: 1 条
更新数据: 1 条
删除数据: 1 条

实际上一些 query()、update()、batchUpdate() 内部都是调用这个方法。

3、execute(ConnectionCallback< T > action)

public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {}

通过回调获取 JdbcTemplate 提供的 Connection,用户可在该 Connection 执行一些操作。例如:

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute3() {
    // 删除表、创建表、新增数据
    Integer num = jdbcTemplate.execute(new ConnectionCallback<Integer>() {
        @Override
        public Integer doInConnection(Connection con) throws SQLException, DataAccessException {
            Statement st = con.createStatement();
            st.execute("drop table if exists user_info"); // 删除表
            st.execute("create table user_info(user_id int primary key, user_name varchar(255))"); // 创建表
            return st.executeUpdate("insert into user_info values(1, 'Java')"); // 插入数据
        }
    });
    System.out.println("插入数据: " + num + " 条");

    // 更新数据、删除数据。采用Lambda表达式
    num = jdbcTemplate.execute((ConnectionCallback<Integer>) con -> {
        Statement st = con.createStatement();
        // 更新数据
        int numByUpdate = st.executeUpdate("update user_info set user_name = 'Python' where user_id = 1");
        System.out.println("更新数据: " + numByUpdate + " 条");
        return st.executeUpdate("delete from user_info where user_id = 1"); // 删除数据
    });
    System.out.println("删除数据: " + num + " 条");
}
插入数据: 1 条
更新数据: 1 条
删除数据: 1 条

4、execute(String sql, PreparedStatementCallback< T > action)

public <T> T execute(String sql, PreparedStatementCallback<T> action) throws DataAccessException {}

由传入的 SQL 生成一个预编译语句,PreparedStatementCallback 回调传回,由用户决定如何执行该 PreparedStatement。

@Override
@Nullable
public <T> T execute(String sql, PreparedStatementCallback<T> action) throws DataAccessException {
    return execute(new SimplePreparedStatementCreator(sql), action);
}

execute(String sql, PreparedStatementCallback< T > action) 与 execute(PreparedStatementCreator psc, PreparedStatementCallback< T > action) 内部都是调用同一个方法。

操作示例:

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute4() {
    // 先初始化表
    jdbcTemplate.execute("drop table if exists user_info"); // 删除表
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))"); // 创建表

    // 插入数据
    String insertSql = "insert into user_info values(?, ?)";
    Integer num = jdbcTemplate.execute(insertSql, new PreparedStatementCallback<Integer>() {
        @Override
        public Integer doInPreparedStatement(PreparedStatement ps)
            throws SQLException, DataAccessException {
            ps.setInt(1, 1);
            ps.setString(2, "Java");
            return ps.executeUpdate();
        }
    });
    System.out.println("插入数据: " + num + " 条");

    // 查询数据, 采用Lambda表达式。返回对象为自定义的JavaBean
    String querySql = "select * from user_info where user_id = ?";
    UserInfo userInfo = jdbcTemplate.execute(querySql, (PreparedStatementCallback<UserInfo>) ps -> {
        ps.setInt(1, 1);
        ResultSet rs = ps.executeQuery();
        rs.next();
        return new UserInfo(rs.getInt(1), rs.getString(2));
    });
    System.out.println(userInfo);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
插入数据: 1 条
UserInfo(userId=1, userName=Java)

5、execute(PreparedStatementCreator psc, PreparedStatementCallback< T > action)

public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) throws DataAccessException {}

第一个参数是创建一个预编译语句,第二个是预编译的回调函数。由用户决定如何创建如何 PreparedStatementCreator,以及如何执行该 PreparedStatement。

@Override
@Nullable
public <T> T execute(String sql, PreparedStatementCallback<T> action) throws DataAccessException {
    return execute(new SimplePreparedStatementCreator(sql), action);
}

execute(String sql, PreparedStatementCallback< T > action) 与 execute(PreparedStatementCreator psc, PreparedStatementCallback< T > action) 内部都是调用同一个方法。

操作示例:

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute5() {
    // 先初始化表
    jdbcTemplate.execute("drop table if exists user_info"); // 删除表
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))"); // 创建表

    // 插入数据
    Integer num = jdbcTemplate.execute(new PreparedStatementCreator(){
        @Override
        public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
            return con.prepareStatement("insert into user_info values(?, ?)");
        }
    }, new PreparedStatementCallback<Integer>() {
        @Override
        public Integer doInPreparedStatement(PreparedStatement ps)
                throws SQLException, DataAccessException {
            ps.setInt(1, 1);
            ps.setString(2, "Java");
            return ps.executeUpdate();
        }
    });
    System.out.println("插入数据: " + num + " 条");

    // 查询数据, 采用Lambda表达式。返回对象为自定义的JavaBean
    String querySql = "select * from user_info where user_id = ?";
    UserInfo userInfo = jdbcTemplate.execute(con -> con.prepareStatement(querySql),
            (PreparedStatementCallback<UserInfo>) ps -> {
                ps.setInt(1, 1);
                ResultSet rs = ps.executeQuery();
                rs.next();
                return new UserInfo(rs.getInt(1), rs.getString(2));
            });
    System.out.println(userInfo);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
插入数据: 1 条
UserInfo(userId=1, userName=Java)

6、execute(String callString, CallableStatementCallback< T > action)

public <T> T execute(String callString, CallableStatementCallback<T> action) throws DataAccessException {}

此方法主要是处理存储过程和函数使用。由传入的 SQL 生成一个预编译语句,CallableStatementCallback 回调传回,由用户决定如何执行该 CallableStatement。

@Override
@Nullable
public <T> T execute(String callString, CallableStatementCallback<T> action) throws DataAccessException {
    return execute(new SimpleCallableStatementCreator(callString), action);
}

操作示例:

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute6() {
    // 创建存储过程
    jdbcTemplate.execute("CREATE ALIAS getDate FOR \"java.lang.System.currentTimeMillis\"");
    jdbcTemplate.execute("CREATE ALIAS getProperty FOR \"java.lang.System.getProperty\"");

    // 调用存储过程
    String call = jdbcTemplate.execute("call getDate()", new CallableStatementCallback<String>() {
        @Override
        public String doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException {
            ResultSet resultSet = cs.executeQuery();
            resultSet.next();
            return String.valueOf(resultSet.getLong(1));
        }
    });
    System.out.println(call);

    // 调用存储过程
    call = jdbcTemplate.execute("call getProperty(?)", (CallableStatementCallback<String>) cs -> {
        cs.setString(1, "user.home");
        ResultSet resultSet = cs.executeQuery();
        resultSet.next();
        return resultSet.getString(1);
    });
    System.out.println(call);
}
1692181535524
C:\Users\lsx

7、execute(CallableStatementCreator csc, CallableStatementCallback< T > action)

public <T> T execute(CallableStatementCreator csc, CallableStatementCallback<T> action) {}

通过 CallableStatementCreator 创建存储过程或者函数的预编译语句,然后由 CallableStatementCallback 回调函数执行,获取结果。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute7() {
    // 创建存储过程
    jdbcTemplate.execute("CREATE ALIAS getDate FOR \"java.lang.System.currentTimeMillis\"");
    jdbcTemplate.execute("CREATE ALIAS getProperty FOR \"java.lang.System.getProperty\"");

    // 调用存储过程
    String call = jdbcTemplate.execute(new CallableStatementCreator() {
        @Override
        public CallableStatement createCallableStatement(Connection con) throws SQLException {
            return con.prepareCall("call getDate()");
        }
    }, new CallableStatementCallback<String>() {
        @Override
        public String doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException {
            ResultSet resultSet = cs.executeQuery();
            resultSet.next();
            return resultSet.getString(1);
        }
    });
    System.out.println(call);

    // 调用存储过程
    call = jdbcTemplate.execute(con -> {
        CallableStatement cs = con.prepareCall("call getProperty(?)");
        cs.setString(1, "user.home");
        return cs;
    }, (CallableStatementCallback<String>) cs -> {
        // cs.setString(1, "user.home");
        ResultSet resultSet = cs.executeQuery();
        resultSet.next();
        return resultSet.getString(1);
    });
    System.out.println(call);
}
1692182057557
C:\Users\lsx

2、update 系列

update()方法可以执行所有execute()能执行的SQL,几乎涵盖了所有常用的SQL。

1、update(final String sql)

public int update(final String sql) throws DataAccessException {}

可以自行查看源码,源码中可以看到这个方法其实就是调用execute(StatementCallback< T > action)他的。故此 update() 方法也能执行很多操作类的SQL,不过SQL中不能处理动态参数。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute1() {
    // 先初始化表
    jdbcTemplate.update("drop table if exists user_info");
    jdbcTemplate.update("create table user_info(user_id int primary key, user_name varchar(255))");
    // 创建存储过程
    jdbcTemplate.update("CREATE ALIAS getDate FOR \"java.lang.System.currentTimeMillis\"");

    // 插入、更新、删除数据
    int insert = jdbcTemplate.update("insert into user_info values(1, 'Java')");
    int update = jdbcTemplate.update("update user_info set user_name = 'Python' where user_id = 1");
    int delete = jdbcTemplate.update("delete from user_info where user_id = 1");
    System.out.println("插入数据: " + insert + " 条");
    System.out.println("更新数据: " + update + " 条");
    System.out.println("删除数据: " + delete + " 条");
}
插入数据: 1 条
更新数据: 1 条
删除数据: 1 条

2、update(String sql, @Nullable Object… args)

public int update(String sql, @Nullable Object... args) throws DataAccessException {}

推荐使用。方法比较简单。其中第二个多参参数形式为SQL中动态参数的值数组。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute2() {
    // 先初始化表
    jdbcTemplate.update("drop table if exists user_info");
    jdbcTemplate.update("create table user_info(user_id int primary key, user_name varchar(255))");

    // 插入、更新、删除数据
    int insert = jdbcTemplate.update("insert into user_info values(?, ?)", 1, "Java");
    int update = jdbcTemplate.update("update user_info set user_name = ? where user_id = ?", "Python", 1);
    int delete = jdbcTemplate.update("delete from user_info where user_id = ?", 1);
    System.out.println("插入数据: " + insert + " 条");
    System.out.println("更新数据: " + update + " 条");
    System.out.println("删除数据: " + delete + " 条");
}
插入数据: 1 条
更新数据: 1 条
删除数据: 1 条

3、update(String sql, Object[] args, int[] argTypes)

public int update(String sql, Object[] args, int[] argTypes) throws DataAccessException {}

执行带有动态参数的sql,通过两个数组入参。对于此方法中:

  • 第一个参数:执行的SQL。
  • 第二个参数:SQL中参数的值。
  • 第三个数组:动态参数的各个类型。
@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute3() {
    // 先初始化表
    jdbcTemplate.update("drop table if exists user_info");
    jdbcTemplate.update("create table user_info(user_id int primary key, user_name varchar(255))");

    // 插入、更新、删除数据
    Object[] param1 = {1, "Java"};
    int[] index1 = {Types.INTEGER, Types.VARCHAR};
    int insert = jdbcTemplate.update("insert into user_info values(?, ?)", param1, index1);
    Object[] param2 = {"Python", 1};
    int[] index2 = {Types.VARCHAR, Types.INTEGER};
    int update = jdbcTemplate.update("update user_info set user_name = ? where user_id = ?", param2, index2);
    Object[] param3 = {1};
    int[] index3 = {Types.INTEGER};
    int delete = jdbcTemplate.update("delete from user_info where user_id = ?", param3, index3);
    System.out.println("插入数据: " + insert + " 条");
    System.out.println("更新数据: " + update + " 条");
    System.out.println("删除数据: " + delete + " 条");
}
插入数据: 1 条
更新数据: 1 条
删除数据: 1 条

4、update(String sql, @Nullable PreparedStatementSetter pss)

public int update(String sql, @Nullable PreparedStatementSetter pss) throws DataAccessException {}

此方法可执行带有动态参数的SQL,参数由PreparedStatement对象操作,无需ps.execute();方法,否则SQL会被执行两次。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute4() {
    // 先初始化表
    jdbcTemplate.update("drop table if exists user_info");
    jdbcTemplate.update("create table user_info(user_id int primary key, user_name varchar(255))");

    // 插入、更新、删除数据
    int insert = jdbcTemplate.update("insert into user_info values(?, ?)", new PreparedStatementSetter() {
        @Override
        public void setValues(PreparedStatement pss) throws SQLException {
            pss.setInt(1, 1);
            pss.setString(2, "Java");
        }
    });
    int update = jdbcTemplate.update("update user_info set user_name = ? where user_id = ?", pss -> {
        pss.setString(1, "Python");
        pss.setInt(2, 1);
    });

    int delete = jdbcTemplate.update("delete from user_info where user_id = ?", pss -> {
        pss.setInt(1, 1);
    });
    System.out.println("插入数据: " + insert + " 条");
    System.out.println("更新数据: " + update + " 条");
    System.out.println("删除数据: " + delete + " 条");
}
插入数据: 1 条
更新数据: 1 条
删除数据: 1 条

5、update(PreparedStatementCreator psc)

public int update(PreparedStatementCreator psc) throws DataAccessException {}

此方法执行SQL时可以传递动态的参数,使用 PreparedStatement 对象操作参数。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute5() {
    // 先初始化表
    jdbcTemplate.update("drop table if exists user_info");
    jdbcTemplate.update("create table user_info(user_id int primary key, user_name varchar(255))");

    // 插入、更新、删除数据
    int insert = jdbcTemplate.update(new PreparedStatementCreator() {
        @Override
        public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
            PreparedStatement ps = con.prepareStatement("insert into user_info values(?, ?)");
            ps.setInt(1, 1);
            ps.setString(2, "Java");
            return ps;
        }
    });
    int update = jdbcTemplate.update((PreparedStatementCreator) con -> {
        PreparedStatement ps = con.prepareStatement("update user_info set user_name = ? where user_id = ?");
        ps.setString(1, "Python");
        ps.setInt(2, 1);
        return ps;
    });
    int delete = jdbcTemplate.update(con -> {
        PreparedStatement ps = con.prepareStatement("delete from user_info where user_id = ?");
        ps.setInt(1, 1);
        return ps;
    });
    System.out.println("插入数据: " + insert + " 条");
    System.out.println("更新数据: " + update + " 条");
    System.out.println("删除数据: " + delete + " 条");
}
插入数据: 1 条
更新数据: 1 条
删除数据: 1 条

6、update(final PreparedStatementCreator psc, final KeyHolder generatedKeyHolder)

public int update(final PreparedStatementCreator psc, final KeyHolder generatedKeyHolder) throws DataAccessException {}

此方法对于执行insert sql语句时,可查询执行的主键id的值。也就是最重要的一点根据KeyHolder获取插入记录的ID。

不过获取主键的时候需要注意数据库类别,Oracle和MySQL是不一样的。不过本人使用的是H2数据库,与MySQL类似。所以如果操作的是Mysql数据库插入动作,获取Id应该是:

Number key = keyHolder.getKey();
int intValue = key.intValue();

操作示例:获取主键ID

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute6() {
    // 先初始化表
    jdbcTemplate.update("drop table if exists user_info");
    jdbcTemplate.update("create table user_info(user_id int primary key auto_increment, user_name varchar(255))");

    // 插入数据返回主键
    GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
    int insert1 = jdbcTemplate.update(con -> {
        // 直接设置返回自增主键值, Statement.RETURN_GENERATED_KEYS 属性是返回自增主键
        PreparedStatement ps = con.prepareStatement("insert into user_info(user_name) values(?)", Statement.RETURN_GENERATED_KEYS);
        ps.setString(1, "Java");
        return ps;
    }, keyHolder);
    System.out.println("插入数据: " + insert1 + " 条");
    System.out.println(keyHolder.getKeys());
    System.out.println(keyHolder.getKey());
    int insert2 = jdbcTemplate.update(con -> {
        // 指定需要返回的列名
        PreparedStatement ps = con.prepareStatement("insert into user_info(user_name) values(?)", new String[]{"user_id"});
        ps.setString(1, "Java");
        return ps;
    }, keyHolder);
    System.out.println("插入数据: " + insert2 + " 条");
    System.out.println(keyHolder.getKeys());
    System.out.println(keyHolder.getKey());
}
插入数据: 1 条
{user_id=1}
1
插入数据: 1 条
{user_id=2}
2

3、batchUpdate 系列

同 update() 方法一样,batchUpdate() 也能处理很多批量操作的sql,比如:insert、update、delete 语句等等。

1、batchUpdate(final String… sql)

public int[] batchUpdate(final String... sql) throws DataAccessException {}

使用方法如下,对于执行的SQL不能带有动态参数。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute1() {
    // 先初始化表
    jdbcTemplate.update("drop table if exists user_info");
    jdbcTemplate.update("create table user_info(user_id int primary key, user_name varchar(255))");

    String sql1 = "insert into user_info values(1, 'Java')";
    String sql2 = "insert into user_info values(2, 'Python')";
    String sql3 = "insert into user_info values(3, 'JavaScript')";
    int[] ints = jdbcTemplate.batchUpdate(sql1, sql2, sql3);
    System.out.println(Arrays.toString(ints));
}
[1, 1, 1]

2、batchUpdate(String sql, List<Object[]> batchArgs)

public int[] batchUpdate(String sql, List<Object[]> batchArgs) throws DataAccessException {}

对于第二个参数 List<Object[]> batchArgs用于存放sql中的动态参数对应的值,如下:

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute2() {
    // 先初始化表
    jdbcTemplate.update("drop table if exists user_info");
    jdbcTemplate.update("create table user_info(user_id int primary key, user_name varchar(255))");

    String sql = "insert into user_info values(?, ?)";
    Object[] objects1 = new Object[]{1, "Java"};
    Object[] objects2 = new Object[]{2, "Python"};
    Object[] objects3 = new Object[]{3, "JavaScript"};
    List<Object[]> objects = List.of(objects1, objects2, objects3);
    int[] ints = jdbcTemplate.batchUpdate(sql, objects);
    System.out.println(Arrays.toString(ints));
}
[1, 1, 1]

3、batchUpdate(String sql, List<Object[]> batchArgs, final int[] argTypes)

public int[] batchUpdate(String sql, List<Object[]> batchArgs, final int[] argTypes) throws DataAccessException {}

同上面的方法一样,采用数组泛型的List存放参数值,第三个参数为数据库中对应字段的属性。比如:

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute3() {
    // 先初始化表
    jdbcTemplate.update("drop table if exists user_info");
    jdbcTemplate.update("create table user_info(user_id int primary key, user_name varchar(255))");

    String sql = "insert into user_info values(?, ?)";
    Object[] objects1 = new Object[]{1, "Java"};
    Object[] objects2 = new Object[]{2, "Python"};
    Object[] objects3 = new Object[]{3, "JavaScript"};
    int[] types = new int[]{Types.INTEGER, Types.VARCHAR};
    List<Object[]> objects = List.of(objects1, objects2, objects3);
    int[] ints = jdbcTemplate.batchUpdate(sql, objects, types);
    System.out.println(Arrays.toString(ints));
}
[1, 1, 1]

4、batchUpdate(String sql, final BatchPreparedStatementSetter pss)

public int[] batchUpdate(String sql, final BatchPreparedStatementSetter pss) throws DataAccessException {}

动态参数通过BatchPreparedStatementSetter接口的匿名类实现。比如:

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute4() {
    // 先初始化表
    jdbcTemplate.update("drop table if exists user_info");
    jdbcTemplate.update("create table user_info(user_id int primary key, user_name varchar(255))");

    String sql = "insert into user_info values(?, ?)";
    Object[] objects1 = new Object[]{1, "Java"};
    Object[] objects2 = new Object[]{2, "Python"};
    Object[] objects3 = new Object[]{3, "JavaScript"};
    List<Object[]> objects = List.of(objects1, objects2, objects3);
    int[] ints = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
        @Override
        public void setValues(PreparedStatement ps, int i) throws SQLException {
            ps.setInt(1, (Integer) objects.get(i)[0]);
            ps.setString(2, (String) objects.get(i)[1]);
        }

        @Override
        public int getBatchSize() {
            return objects.size();
        }
    });
    System.out.println(Arrays.toString(ints));
}

5、batchUpdate(String sql, final Collection< T > batchArgs, final int batchSize, final ParameterizedPreparedStatementSetter< T > pss)

public <T> int[][] batchUpdate(String sql, final Collection<T> batchArgs, final int batchSize,
            final ParameterizedPreparedStatementSetter<T> pss) throws DataAccessException {}

推荐使用。这个批量操作的方法比较好用。

  • 第一个参数:要执行的SQL。
  • 第二个参数:SQL参数的对象集合。
  • 第三个参数:需要处理数据的数量。
  • 第四个参数:根据第二个参数泛型取对应的值赋值给PreparedStatement对象,用于执行SQL。
@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute5() {
    // 先初始化表
    jdbcTemplate.update("drop table if exists user_info");
    jdbcTemplate.update("create table user_info(user_id int primary key, user_name varchar(255))");

    String sql = "insert into user_info values(?, ?)";
    List<UserInfo> datas = List.of(
            new UserInfo(1, "Java"),
            new UserInfo(2, "Python"),
            new UserInfo(3, "JavaScript"));
    int[][] ints = jdbcTemplate.batchUpdate(sql, datas, datas.size(), new ParameterizedPreparedStatementSetter<UserInfo>() {
        @Override
        public void setValues(PreparedStatement ps, UserInfo userInfo) throws SQLException {
            ps.setInt(1, userInfo.getUserId());
            ps.setString(2, userInfo.getUserName());
        }
    });
    System.out.println(Arrays.toString(ints[0]));
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
[{user_id=1}, {user_id=2}, {user_id=3}]

6、批量插入并返回主键 ID

上面还漏了一个批量插入时,也需要返回主键ID,改怎么办?

直接看JdbcTemplate的接口,并没有发现类似单个插入获取主键的方式,是不是意味着没法实现呢?

当然不是了,既然没有提供,我们完全可以依葫芦画瓢,自己实现一个 ExtendJdbcTemplate, 首先看先单个插入返回ID的实现如下:

@Override
public int update(final PreparedStatementCreator psc, final KeyHolder generatedKeyHolder)
    throws DataAccessException {

    Assert.notNull(generatedKeyHolder, "KeyHolder must not be null");
    logger.debug("Executing SQL update and returning generated keys");

    return updateCount(execute(psc, ps -> {
        int rows = ps.executeUpdate();
        List<Map<String, Object>> generatedKeys = generatedKeyHolder.getKeyList();
        generatedKeys.clear();
        ResultSet keys = ps.getGeneratedKeys();
        if (keys != null) {
            try {
                RowMapperResultSetExtractor<Map<String, Object>> rse =
                    new RowMapperResultSetExtractor<>(getColumnMapRowMapper(), 1);
                generatedKeys.addAll(result(rse.extractData(keys)));
            }
            finally {
                JdbcUtils.closeResultSet(keys);
            }
        }
        if (logger.isTraceEnabled()) {
            logger.trace("SQL update affected " + rows + " rows and returned " + generatedKeys.size() + " keys");
        }
        return rows;
    }, true));
}

接下来,我们自己的实现可以如下:

import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.*;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.*;
import java.util.*;

@Component
public class ExtendJdbcTemplate extends JdbcTemplate {
    public ExtendJdbcTemplate(DataSource dataSource) {
        super(dataSource);
    }

    public int[] batchUpdate(final PreparedStatementCreator psc, final BatchPreparedStatementSetter pss,
                             final KeyHolder generatedKeyHolder) throws DataAccessException {
        return execute(psc, ps -> {
            try {
                int batchSize = pss.getBatchSize();
                int totalRowsAffected = 0;
                int[] rowsAffected = new int[batchSize];
                List<Map<String, Object>> generatedKeys = generatedKeyHolder.getKeyList();
                generatedKeys.clear();
                ResultSet keys = null;
                for (int i = 0; i < batchSize; i++) {
                    pss.setValues(ps, i);
                    rowsAffected[i] = ps.executeUpdate();
                    totalRowsAffected += rowsAffected[i];
                    try {
                        keys = ps.getGeneratedKeys();
                        if (keys != null) {
                            RowMapper<Map<String, Object>> rowMapper = new ColumnMapRowMapper();
                            RowMapperResultSetExtractor<Map<String, Object>> rse =
                                    new RowMapperResultSetExtractor<>(rowMapper, 1);
                            generatedKeys.addAll(Objects.requireNonNull(rse.extractData(keys)));
                        }
                    } finally {
                        JdbcUtils.closeResultSet(keys);
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("SQL batch update affected " + totalRowsAffected +
                            " rows and returned " + generatedKeys.size() + " keys");
                }
                return rowsAffected;
            } finally {
                if (pss instanceof ParameterDisposer) {
                    ((ParameterDisposer) pss).cleanupParameters();
                }
            }
        });
    }
}
@Autowired
private ExtendJdbcTemplate extendJdbcTemplate;

@PostConstruct
public void execute6() {
    // 先初始化表
    jdbcTemplate.update("drop table if exists user_info");
    jdbcTemplate.update("create table user_info(user_id int primary key, user_name varchar(255))");

    String sql = "insert into user_info values(?, ?)";
    List<UserInfo> userInfos = List.of(
            new UserInfo(1, "Java"),
            new UserInfo(2, "Python"),
            new UserInfo(3, "JavaScript"));

    GeneratedKeyHolder generatedKeyHolder = new GeneratedKeyHolder();
    extendJdbcTemplate.batchUpdate(new PreparedStatementCreator() {
        @Override
        public PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
            return conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
        }
    }, new BatchPreparedStatementSetter() {
        @Override
        public void setValues(PreparedStatement ps, int i) throws SQLException {
            ps.setInt(1, userInfos.get(i).getUserId());
            ps.setString(2, userInfos.get(i).getUserName());
        }

        @Override
        public int getBatchSize() {
            return userInfos.size();
        }
    }, generatedKeyHolder);
    System.out.println(generatedKeyHolder.getKeyList());
}

4、call 系列

1、call(CallableStatementCreator csc, List< SqlParameter > declaredParameters)

public Map<String, Object> call(CallableStatementCreator csc, List<SqlParameter> declaredParameters)
            throws DataAccessException {}
@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 创建存储过程
    jdbcTemplate.execute("CREATE ALIAS getDate FOR \"java.lang.System.currentTimeMillis\"");
    jdbcTemplate.execute("CREATE ALIAS getProperty FOR \"java.lang.System.getProperty\"");

    // H2调用存储过程实际上也可以不用给SqlParameter,如果有Out参数就必须要要使用
    SqlParameter parameter = new SqlParameter(Types.VARCHAR);
    Map<String, Object> call = jdbcTemplate.call(new CallableStatementCreator() {
        @Override
        public CallableStatement createCallableStatement(Connection con) throws SQLException {
            CallableStatement cs = con.prepareCall("call getProperty(?)");
            cs.setString(1, "user.home");
            return cs;
        }
    }, Collections.singletonList(parameter));
    System.out.println(call);

    // Lambda 方式调用
    call = jdbcTemplate.call(con -> con.prepareCall("call getDate()"), Collections.emptyList());
    System.out.println(call);
}
{#result-set-1=[{PUBLIC.getProperty(?1)=C:\Users\lsx}]}
{#result-set-1=[{PUBLIC.getDate()=1692331016474}]}

5、queryForObject 系列

queryForObject() 方法只能查询单个列且结果只有一行。否则报错。这一点尤其注意。

如果查询结果为空也会报错:

org.springframework.dao.EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0 。

如果查询结果是多行,则报错:

org.springframework.dao.IncorrectResultSizeDataAccessException: Incorrect result size: expected 1, actual 12

注意:可以在网上看到很人多:查询的结果必须有且只有一行一列的值。这种说法也不完全对,如果查询结果映射为 RowMapper< T > ,泛型中可以是 Java 对象,这样就可以使一行多列了。

queryForObject:查询结果,将结果封装为对象,一般用于聚合函数的查询。

1、queryForObject(String sql, RowMapper< T > rowMapper)

public <T> T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException {}

返回一个泛型对象。且查询SQL不能动态传参,除非将参数在查询之前动态的拼接好,RowMapper 用于处理查询的结果。此种方式既可以返回单行单列,也可以返回单行多列。主要看 RowMapper 接口中泛型的设置。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    // 查询单行单列
    String sql = "select user_name from user_info where user_id = 1";
    String str = jdbcTemplate.queryForObject(sql, new RowMapper<String>() {
        @Override
        public String mapRow(ResultSet rs, int rowNum) throws SQLException {
            return rs.getString(1);
        }
    });
    System.out.println(str);

    // 查询单行多列
    sql = "select user_id,user_name from user_info where user_id = 1";
    UserInfo userInfo = jdbcTemplate.queryForObject(sql, (rs, rowNum) ->
                                                    new UserInfo(rs.getInt(1), rs.getString(2)));
    System.out.println(userInfo);

    // 注意这里Spring自身提供了一个RowMapper接口实现类:ColumnMapRowMapper
    Map<String, Object> map = jdbcTemplate.queryForObject(sql, new ColumnMapRowMapper());
    System.out.println(map);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
Java
UserInfo(userId=1, userName=Java)
{user_id=1, user_name=Java}

2、queryForObject(String sql, RowMapper< T > rowMapper, @Nullable Object… args)

public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args) throws DataAccessException {}

和上面的方法一样,区别在于查询SQL能动态传参。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    // 查询单行单列
    String sql = "select user_name from user_info where user_id = ?";
    String str = jdbcTemplate.queryForObject(sql, new RowMapper<String>() {
        @Override
        public String mapRow(ResultSet rs, int rowNum) throws SQLException {
            return rs.getString(1);
        }
    }, 1);
    System.out.println(str);

    // 查询单行多列
    sql = "select user_id,user_name from user_info where user_id = ?";
    UserInfo userInfo = jdbcTemplate.queryForObject(sql,
            (rs, rowNum) -> new UserInfo(rs.getInt(1), rs.getString(2)),
            1);
    System.out.println(userInfo);

    // 注意这里Spring自身提供了一个RowMapper接口实现类:ColumnMapRowMapper
    Map<String, Object> map = jdbcTemplate.queryForObject(sql, new ColumnMapRowMapper(), 1);
    System.out.println(map);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
Java
UserInfo(userId=1, userName=Java)
{user_id=1, user_name=Java}

3、queryForObject(String sql, Object[] args, int[] argTypes, RowMapper< T > rowMapper)

public <T> T queryForObject(String sql, Object[] args, int[] argTypes, RowMapper<T> rowMapper) throws DataAccessException {}

查询带有参数的 SQL,并且指定参数在数据库中的类型,如果SQL中没有参数,则为null。

  • 第一个参数:查询SQL。

  • 第二个参数:如果SQL中有动态参数,则此为SQL中占位符参数的数组,注意顺序保持一致。如果没有则为null。

  • 第三个参数:如果SQL有占位符需要处理参数,则此为参数在数据库中的类型,如果SQL中没有参数,则为null。

  • 第四个参数:手动处理结果集。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    // 查询单行单列
    String sql = "select user_name from user_info where user_id = ?";
    Object[] param = {1};
    int[] index = {Types.INTEGER};
    String str = jdbcTemplate.queryForObject(sql, param, index, new RowMapper<String>() {
        @Override
        public String mapRow(ResultSet rs, int rowNum) throws SQLException {
            return rs.getString(1);
        }
    });
    System.out.println(str);

    // 查询单行多列
    sql = "select user_id,user_name from user_info where user_id = ?";
    UserInfo userInfo = jdbcTemplate.queryForObject(sql, param, index,
            (rs, rowNum) -> new UserInfo(rs.getInt(1), rs.getString(2)));
    System.out.println(userInfo);

    // 注意这里Spring自身提供了一个RowMapper接口实现类:ColumnMapRowMapper
    Map<String, Object> map = jdbcTemplate.queryForObject(sql, param, index, new ColumnMapRowMapper());
    System.out.println(map);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
Java
UserInfo(userId=1, userName=Java)
{user_id=1, user_name=Java}

4、queryForObject(String sql, Class< T > requiredType)

public <T> T queryForObject(String sql, Class<T> requiredType) throws DataAccessException {}

这个方法就是用上面的**queryForObject(String sql, RowMapper< T > rowMapper)**方法,只不过把第二个参数封装了。requiredType 必须是常用的8个基本类型,否则报错。且查询SQL不能动态传参。

@Override
@Nullable
public <T> T queryForObject(String sql, Class<T> requiredType) throws DataAccessException {
    return queryForObject(sql, getSingleColumnRowMapper(requiredType));
}

如果只是查询单个字段时,或者聚合查询时,推荐使用此方法。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    String userName = jdbcTemplate.queryForObject("select user_name from user_info where user_id = 1", String.class);
    System.out.println("userName: " + userName);

    Integer count = jdbcTemplate.queryForObject("select count(*) from user_info", Integer.class);
    System.out.println("count: " + count);

    // 下面这个会报错:
    //  org.springframework.dao.TypeMismatchDataAccessException: Type mismatch affecting row number 0 and column type 'CHARACTER VARYING':
    //  Value [Java] is of type [java.lang.String] and cannot be converted to required type [UserInfo]
    // UserInfo userInfo = jdbcTemplate.queryForObject("select user_name from user_info where user_id = 1", UserInfo.class);
}
userName: Java
count: 3

5、queryForObject(String sql, Class< T > requiredType, @Nullable Object… args)

public <T> T queryForObject(String sql, Class<T> requiredType, @Nullable Object... args) throws DataAccessException {}

在上面的方法的基础上,增加了查询SQL可以动态传参。需要注意:requiredType 必须是常用的8个基本类型,否则报错。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    String sql = "select user_name from user_info where user_id = ? and user_name = ?";
    String userName = jdbcTemplate.queryForObject(sql, String.class, 1, "Java");
    System.out.println("userName: " + userName);
}
userName: Java

6、queryForObject(String sql, Object[] args, int[] argTypes, Class< T > requiredType)

public <T> T queryForObject(String sql, Object[] args, int[] argTypes, Class<T> requiredType) throws DataAccessException {}

查询带有参数的 SQL,并且指定参数在数据库中的类型,如果SQL中没有参数,则为null。需要注意:requiredType 必须是常用的8个基本类型,否则报错。

  • 第一个参数:查询SQL。
  • 第二个参数:如果SQL中有动态参数,则此为SQL中占位符参数的数组,注意顺序保持一致。如果没有则为null。
  • 第三个参数:如果SQL有占位符需要处理参数,则此为参数在数据库中的类型,如果SQL中没有参数,则为null。
  • 第四个参数:手动处理结果集。结果集只能为常用的8个基本类型,否则报错。
@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    String sql = "select user_name from user_info where user_id = ? and user_name = ?";
    Object[] param = {1, "Java"};
    int[] index = {Types.INTEGER, Types.VARCHAR};
    String userName = jdbcTemplate.queryForObject(sql, param, index, String.class);
    System.out.println("userName: " + userName);
}
userName: Java

6、queryForMap 系列

queryForMap() 方法是 JdbcOperations 接口的方法,由 JdbcTemplate 子类进行了重写。queryForMap 方法查询结果将结果集封装为 Map 集合,将列名作为 key,将值作为 value 将这条记录封装为一个 Map 集合。 注意:这个方法查询的结果集长度只能是1,也就是一行。

1、queryForMap(String sql)

public Map<String, Object> queryForMap(String sql) throws DataAccessException {}

返回一个Map对象。且查询SQL不能动态传参。

操作案例请参考:queryForMap(String sql, Object[] args, int[] argTypes)

2、queryForMap(String sql, @Nullable Object… args)

public Map<String, Object> queryForMap(String sql, @Nullable Object... args) throws DataAccessException {}

与上面方法唯一的区别是:查询SQL能动态传参。

操作案例请参考:queryForMap(String sql, Object[] args, int[] argTypes)

3、queryForMap(String sql, Object[] args, int[] argTypes)

public Map<String, Object> queryForMap(String sql, Object[] args, int[] argTypes) throws DataAccessException {}

查询带有参数的 SQL,并且指定参数在数据库中的类型,如果SQL中没有参数,则为null。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    Map<String, Object> map = jdbcTemplate.queryForMap("select * from user_info where user_id = 1");
    System.out.println(map);

    map = jdbcTemplate.queryForMap("select * from user_info where user_id = ?", 1);
    System.out.println(map);

    Object[] param = {1};
    int[] index = {Types.INTEGER};
    map = jdbcTemplate.queryForMap("select * from user_info where user_id = ?", param, index);
    System.out.println(map);

    // 如下会报错: Invocation of init method failed;
    // nested exception is org.springframework.dao.IncorrectResultSizeDataAccessException: 
    // Incorrect result size: expected 1, actual 3
    // System.out.println(jdbcTemplate.queryForMap("select * from user_info"));
}
{user_id=1, user_name=Java}
{user_id=1, user_name=Java}
{user_id=1, user_name=Java}

7、queryForRowSet 系列

queryForRowSet 方法也是 JdbcOperations 接口的方法,由 JdbcTemplate 子类进行了重写。queryForRowSet 方法返回是 SqlRowSet 对象,这是一个集合,也就是说,可以查询多条记录。【与 queryForMap 的区别:queryForMap 返回 Map 集合,并且只能放回一行数据。而 queryForRowSet 返回的是一个SqlRowSet对象,这是个集合对象,所以可以查询多条记录】

1、queryForRowSet(String sql)

public SqlRowSet queryForRowSet(String sql) throws DataAccessException {}

返回一个SqlRowSet 对象。且查询SQL不能动态传参。

操作案例请参考:queryForRowSet(String sql, Object[] args, int[] argTypes)

2、queryForRowSet(String sql, @Nullable Object… args)

public SqlRowSet queryForRowSet(String sql, @Nullable Object... args) throws DataAccessException {}

与上面方法唯一的区别是:查询SQL能动态传参。

操作案例请参考:queryForRowSet(String sql, Object[] args, int[] argTypes)

3、queryForRowSet(String sql, Object[] args, int[] argTypes)

public SqlRowSet queryForRowSet(String sql, Object[] args, int[] argTypes) throws DataAccessException {}

查询带有参数的 SQL,并且指定参数在数据库中的类型,如果SQL中没有参数,则为null。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    String sql = "select * from user_info";
    SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql);
    List<UserInfo> userInfos = new ArrayList<>();
    while (rowSet.next()) {
        userInfos.add(new UserInfo(rowSet.getInt(1), rowSet.getString(2)));
    }
    System.out.println(userInfos);
    userInfos.clear();

    sql = "select * from user_info where user_id = ?";
    rowSet = jdbcTemplate.queryForRowSet(sql, 1);
    while (rowSet.next()) {
        userInfos.add(new UserInfo(rowSet.getInt(1), rowSet.getString(2)));
    }
    System.out.println(userInfos);
    userInfos.clear();

    Object[] param = {1};
    int[] index = {Types.INTEGER};
    rowSet = jdbcTemplate.queryForRowSet(sql, param, index);
    while (rowSet.next()) {
        userInfos.add(new UserInfo(rowSet.getInt(1), rowSet.getString(2)));
    }
    System.out.println(userInfos);
    userInfos.clear();
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
[UserInfo(userId=1, userName=Java), UserInfo(userId=2, userName=Python), UserInfo(userId=3, userName=JavaScript)]
[UserInfo(userId=1, userName=Java)]
[UserInfo(userId=1, userName=Java)]

8、queryForList 系列

queryForList() 方法查询有两种形式:

  1. 查询结果将结果集封装为List集合。注意:将每一条记录封装为一个Map集合,再将Map集合装载到List集合中。
  2. 查询结果将结果集封装为List集合,如果指定elementType类型,则只能查询单列。再将单列这个类型装载到List集合中。

查询多行多列,以List的形式返回结果,如果指定elementType类型,则只能查询单列,否则报错:

org.springframework.jdbc.IncorrectResultSetColumnCountException: Incorrect column count: expected 1, actual 4 

如果指定elementType类型,则只能查询单列,且类型只能时8中基本数据类型,否则报错:

org.springframework.dao.TypeMismatchDataAccessException:

Type mismatch affecting row number 0 and column type 'xxxxxx':

Value [xxxxxx] is of type [java.lang.String] and cannot be converted to required type [xxx.xxx.xxx.xxx]

如果经常查询一些数据,建议使用queryForList()。需要注意的是:如果查询结果为空则返回空集合,也就是如下,不会报错。

list.isEmpty() == true

1、queryForList(String sql)

public List<Map<String, Object>> queryForList(String sql) throws DataAccessException {}

可以查询多行多列,返回一个List集合,List元素是Map,也就是查询结果中每一行的数据,只不过以Map的形式返回。 如果SQL中没有动态参数,推荐使用。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from user_info");
    System.out.println(maps);
}
[{user_id=1, user_name=Java}, {user_id=2, user_name=Python}, {user_id=3, user_name=JavaScript}]

2、queryForList(String sql, @Nullable Object… args)

public List<Map<String, Object>> queryForList(String sql, @Nullable Object... args) throws DataAccessException {}

与上面方法一样,如果SQL中有动态参数,推荐使用这个,比较方便。例如:

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from user_info where user_id < ?", 4);
    System.out.println(maps);
}
[{user_id=1, user_name=Java}, {user_id=2, user_name=Python}, {user_id=3, user_name=JavaScript}]

3、queryForList(String sql, Object[] args, int[] argTypes)

public List<Map<String, Object>> queryForList(String sql, Object[] args, int[] argTypes) throws DataAccessException {}

查询多行多列,SQL中可以传多个动态参数,并且指定参数在数据库中的类型,该参数可有可无,如:

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    Object[] param = {4};
    int[] index = {Types.INTEGER};
    List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from user_info where user_id < ?", param, index);
    System.out.println(maps);
}
[{user_id=1, user_name=Java}, {user_id=2, user_name=Python}, {user_id=3, user_name=JavaScript}]

4、queryForList(String sql, Class< T > elementType)

public <T> List<T> queryForList(String sql, Class<T> elementType) throws DataAccessException {}

此方法用于SQL中没有动态参数,且查询结果为单列多行,返回一个指定类型的List集合,指定的类型只能是8个基本数据类型。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    List<String> userNames = jdbcTemplate.queryForList("select user_name from user_info", String.class);
    System.out.println(userNames);
}
[Java, Python, JavaScript]

5、queryForList(String sql, Class< T > elementType, @Nullable Object… args)

public <T> List<T> queryForList(String sql, Class<T> elementType, @Nullable Object... args) throws DataAccessException {}

查询多行单列,第二个参数的类型必须是八个基本数据类型之一,动态占位符参数采用多个参数传参的方式。如:

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    String sql = "select user_name from user_info where user_id < ?";
    List<String> userNames = jdbcTemplate.queryForList(sql, String.class, 4);
    System.out.println(userNames);
}
[Java, Python, JavaScript]

6、queryForList(String sql, Object[] args, int[] argTypes, Class< T > elementType)

public <T> List<T> queryForList(String sql, Object[] args, int[] argTypes, Class<T> elementType) throws DataAccessException {}

查询多行单列,SQL中可以有动态参数。

  • 第一个参数:SQL语句。

  • 第二个参数:如果SQL中有动态参数,则此为SQL中占位符参数的数组,注意顺序保持一致。如果没有则为null。

  • 第三个参数:如果SQL有占位符需要处理参数,则此为参数的数据库类型,如果SQL中没有参数,则为null。该参数其实可有可无

  • 第四个参数:必须是8个基本数据类型其一。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    Object[] param = {4};
    int[] index = {Types.INTEGER};
    String sql = "select user_name from user_info where user_id < ?";
    List<String> userNames = jdbcTemplate.queryForList(sql, param, index, String.class);
    System.out.println(userNames);
}
[Java, Python, JavaScript]

9、query 系列

1、query(String sql, RowCallbackHandler rch)

public void query(String sql, RowCallbackHandler rch) throws DataAccessException {}

其实这个方式就是调用query(String sql, @Nullable PreparedStatementSetter pss, RowCallbackHandler rch)方法,需要用户自己取处理结果集,不过不需要判断ResultSet.next(),交由ResultSetExtractor的实现类的extractData(ResultSet rs)完成。用户只需要处理ResultSet的每一行数据。不支持SQL动态传入参数。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    // 查询单个对象, 这个回调方式, 一条记录回调一次, 不返回结果
    UserInfo userInfo = new UserInfo();
    jdbcTemplate.query("select * from user_info where user_id = 1", new RowCallbackHandler() {
        @Override
        public void processRow(ResultSet rs) throws SQLException {
            userInfo.setUserId(rs.getInt(1));
            userInfo.setUserName(rs.getString(2));
        }
    });
    System.out.println(userInfo);

    // 查询多个对象, 这个回调方式, 一条记录回调一次, 不返回结果, 我们可以用List去一条一条的接收数据
    List<UserInfo> userInfos = new ArrayList<>();
    jdbcTemplate.query("select * from user_info", rs -> {
        userInfos.add(new UserInfo(rs.getInt(1), rs.getString(2)));
    });
    System.out.println(userInfos);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
UserInfo(userId=1, userName=Java)
[UserInfo(userId=1, userName=Java), UserInfo(userId=2, userName=Python), UserInfo(userId=3, userName=JavaScript)]

2、query(String sql, RowCallbackHandler rch, @Nullable Object… args)

public <T> T query(String sql, ResultSetExtractor<T> rse, @Nullable Object... args) throws DataAccessException {}

和上面的方法类似,只不过把参数改为动态可传参数的形式。如果没有参数就传个空的数组。主要是针对有可变参数的SQL。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    // 查询单个对象, 这个回调方式, 一条记录回调一次, 不返回结果
    UserInfo userInfo = new UserInfo();
    jdbcTemplate.query("select * from user_info where user_id = ?", new RowCallbackHandler() {
        @Override
        public void processRow(ResultSet rs) throws SQLException {
            userInfo.setUserId(rs.getInt(1));
            userInfo.setUserName(rs.getString(2));
        }
    }, 1);
    System.out.println(userInfo);

    // 查询多个对象, 这个回调方式, 一条记录回调一次, 不返回结果, 我们可以用List去一条一条的接收数据
    List<UserInfo> userInfos = new ArrayList<>();
    jdbcTemplate.query("select * from user_info where user_id < ?", rs -> {
        userInfos.add(new UserInfo(rs.getInt(1), rs.getString(2)));
    }, 4);
    System.out.println(userInfos);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
UserInfo(userId=1, userName=Java)
[UserInfo(userId=1, userName=Java), UserInfo(userId=2, userName=Python), UserInfo(userId=3, userName=JavaScript)]

3、query(String sql, Object[] args, int[] argTypes, RowCallbackHandler rch)

public void query(String sql, Object[] args, int[] argTypes, RowCallbackHandler rch) throws DataAccessException {}

在上面方法得基础多多了一个SQL参数类型,该参数可有可无。

  • 第一个参数:要查询的SQL。

  • 第二个参数:如果SQL中有动态参数,则此为sql中占位符参数的数组,注意顺序保持一致。如果没有则为null。

  • 第三个参数:如果SQL有占位符需要处理参数,则此为参数的数据类型,如果sql中没有参数,则为null。该参数可有可无。

  • 第四个参数:对结果集的每一行进行处理,转换成想要的对象。不需要做ResultSet.next()判断。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    // 查询单个对象, 这个回调方式, 一条记录回调一次, 不返回结果
    Object[] param1 = {1};
    int[] index1 = {Types.INTEGER};
    String sql = "select * from user_info where user_id = ?";
    UserInfo userInfo = new UserInfo();
    jdbcTemplate.query(sql, param1, index1, new RowCallbackHandler() {
        @Override
        public void processRow(ResultSet rs) throws SQLException {
            userInfo.setUserId(rs.getInt(1));
            userInfo.setUserName(rs.getString(2));
        }
    });
    System.out.println(userInfo);

    // 查询多个对象, 这个回调方式, 一条记录回调一次, 不返回结果, 我们可以用List去一条一条的接收数据
    Object[] param2 = {4};
    int[] index2 = {Types.INTEGER};
    sql = "select * from user_info where user_id < ?";
    List<UserInfo> userInfos = new ArrayList<>();
    jdbcTemplate.query(sql, param2, index2, rs -> {
        userInfos.add(new UserInfo(rs.getInt(1), rs.getString(2)));
    });
    System.out.println(userInfos);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
UserInfo(userId=1, userName=Java)
[UserInfo(userId=1, userName=Java), UserInfo(userId=2, userName=Python), UserInfo(userId=3, userName=JavaScript)]

4、query(String sql, @Nullable PreparedStatementSetter pss, RowCallbackHandler rch)

public void query(String sql, @Nullable PreparedStatementSetter pss, RowCallbackHandler rch) throws DataAccessException {}
  • 第一个参数:需要查询的SQL。
  • 第二个参数:对SQL中的参数处理,如果没有则不处理。
  • 第三个参数:对结果集的每一行进行处理,转换成想要的对象。不需要做ResultSet.next()判断。
@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    // 查询单个对象, 这个回调方式, 一条记录回调一次, 不返回结果
    UserInfo userInfo = new UserInfo();
    String sql = "select * from user_info where user_id = ?";
    jdbcTemplate.query(sql, new PreparedStatementSetter() {
        @Override
        public void setValues(PreparedStatement ps) throws SQLException {
            ps.setInt(1, 1);
        }
    }, new RowCallbackHandler() {
        @Override
        public void processRow(ResultSet rs) throws SQLException {
            userInfo.setUserId(rs.getInt(1));
            userInfo.setUserName(rs.getString(2));
        }
    });
    System.out.println(userInfo);

    // 查询多个对象, 这个回调方式, 一条记录回调一次, 不返回结果, 我们可以用List去一条一条的接收数据
    List<UserInfo> userInfos = new ArrayList<>();
    sql = "select * from user_info where user_id < ?";
    jdbcTemplate.query(sql, ps -> ps.setInt(1, 4), rs -> {
        userInfos.add(new UserInfo(rs.getInt(1), rs.getString(2)));
    });
    System.out.println(userInfos);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
UserInfo(userId=1, userName=Java)
[UserInfo(userId=1, userName=Java), UserInfo(userId=2, userName=Python), UserInfo(userId=3, userName=JavaScript)]

5、query(PreparedStatementCreator psc, RowCallbackHandler rch)

public void query(PreparedStatementCreator psc, RowCallbackHandler rch) throws DataAccessException {}
  • 参数一:构建一个预编译的SQL语句,如果有可变参数,给其赋值。
  • 参数二:用户处理每一行的结果集,不需要判断ResultSet.next()。
  • 注意:这是个没有返回值的方法。
@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    // 查询单个对象, 这个回调方式, 一条记录回调一次, 不返回结果
    UserInfo userInfo = new UserInfo();
    jdbcTemplate.query(new PreparedStatementCreator() {
        @Override
        public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
            String sql = "select * from user_info where user_id = ?";
            PreparedStatement ps = con.prepareStatement(sql);
            ps.setInt(1, 1);
            return ps;
        }
    }, new RowCallbackHandler() {
        @Override
        public void processRow(ResultSet rs) throws SQLException {
            userInfo.setUserId(rs.getInt(1));
            userInfo.setUserName(rs.getString(2));
        }
    });
    System.out.println(userInfo);

    // 查询多个对象, 这个回调方式, 一条记录回调一次, 不返回结果, 我们可以用List去一条一条的接收数据
    List<UserInfo> userInfos = new ArrayList<>();
    jdbcTemplate.query(con -> {
        String sql = "select * from user_info where user_id < ?";
        PreparedStatement ps = con.prepareStatement(sql);
        ps.setInt(1, 4);
        return ps;
    }, rs -> {
        userInfos.add(new UserInfo(rs.getInt(1), rs.getString(2)));
    });
    System.out.println(userInfos);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}

6、query(String sql, ResultSetExtractor rse)

public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {}

该方法返回一个泛型对象。SQL为查询的SQL语句,ResultSetExtractor是结果集数据提取用的,通过extractData(ResultSet rs)处理整个结果集。不支持传入参数。可以根据自己想要的类型返回结果,需要手工处理结果集。这种查询处理既可以查询单条数据,也可以查询多条数据。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    // 查询单个对象, 这个回调方式, 接收的是批量的结果,一次对所有的结果进行转换, 返回泛型<T>
    String sql = "select * from user_info where user_id = 1";
    UserInfo userInfo = jdbcTemplate.query(sql, new ResultSetExtractor<UserInfo>() {
        @Override
        public UserInfo extractData(ResultSet rs) throws SQLException, DataAccessException {
            rs.next();
            return new UserInfo(rs.getInt(1), rs.getString(2));
        }
    });
    System.out.println(userInfo);

    // 查询多个对象, 这个回调方式, 接收的是批量的结果,一次对所有的结果进行转换, 返回List<T>
    List<UserInfo> userInfos = jdbcTemplate.query("select * from user_info", rs -> {
        List<UserInfo> list = new ArrayList<>();
        while (rs.next()) {
            list.add(new UserInfo(rs.getInt(1), rs.getString(2)));
        }
        return list;
    });
    System.out.println(userInfos);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
UserInfo(userId=1, userName=Java)
[UserInfo(userId=1, userName=Java), UserInfo(userId=2, userName=Python), UserInfo(userId=3, userName=JavaScript)]

7、query(String sql, ResultSetExtractor rse, @Nullable Object… args)

public <T> T query(String sql, ResultSetExtractor<T> rse, @Nullable Object... args) throws DataAccessException {}

和上面的方法类似,只不过把参数改为动态可传参数的形式。如果没有参数就传个空的数组。主要是针对有可变参数的SQL。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    // 查询单个对象, 这个回调方式, 接收的是批量的结果,一次对所有的结果进行转换, 返回泛型<T>
    String sql = "select * from user_info where user_id = ?";
    UserInfo userInfo = jdbcTemplate.query(sql, new ResultSetExtractor<UserInfo>() {
        @Override
        public UserInfo extractData(ResultSet rs) throws SQLException, DataAccessException {
            rs.next();
            return new UserInfo(rs.getInt(1), rs.getString(2));
        }
    }, 1);
    System.out.println(userInfo);

    // 查询多个对象, 这个回调方式, 接收的是批量的结果,一次对所有的结果进行转换, 返回List<T>
    sql = "select * from user_info where user_id < ?";
    List<UserInfo> userInfos = jdbcTemplate.query(sql, rs -> {
        List<UserInfo> list = new ArrayList<>();
        while (rs.next()) {
            list.add(new UserInfo(rs.getInt(1), rs.getString(2)));
        }
        return list;
    }, 4);
    System.out.println(userInfos);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
UserInfo(userId=1, userName=Java)
[UserInfo(userId=1, userName=Java), UserInfo(userId=2, userName=Python), UserInfo(userId=3, userName=JavaScript)]

8、query(String sql, Object[] args, int[] argTypes, ResultSetExtractor rse)

public <T> T query(String sql, Object[] args, int[] argTypes, ResultSetExtractor<T> rse) throws DataAccessException {}

这个方式和上面的几个没有什么差别,看一下方法内部的调用就知道了,也是调用的query(PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, final ResultSetExtractor< T > rse),只不过把Object[] args, int[] argTypes这两个参数分开了。主要是针对有可变参数的sql。这两个参数的意思是:

  • Object[] args:占位符对应的参数值。
  • int[] argTypes:占位符参数值的类型。
@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    // 查询单个对象, 这个回调方式, 接收的是批量的结果,一次对所有的结果进行转换, 返回泛型<T>
    Object[] param1 = {1};
    int[] index1 = {Types.INTEGER};
    String sql = "select * from user_info where user_id = ?";
    UserInfo userInfo = jdbcTemplate.query(sql, param1, index1, new ResultSetExtractor<UserInfo>() {
        @Override
        public UserInfo extractData(ResultSet rs) throws SQLException, DataAccessException {
            rs.next();
            return new UserInfo(rs.getInt(1), rs.getString(2));
        }
    });
    System.out.println(userInfo);

    // 查询多个对象, 这个回调方式, 接收的是批量的结果,一次对所有的结果进行转换, 返回List<T>
    Object[] param2 = {4};
    int[] index2 = {Types.INTEGER};
    sql = "select * from user_info where user_id < ?";
    List<UserInfo> userInfos = jdbcTemplate.query(sql, param2, index2, rs -> {
        List<UserInfo> list = new ArrayList<>();
        while (rs.next()) {
            list.add(new UserInfo(rs.getInt(1), rs.getString(2)));
        }
        return list;
    });
    System.out.println(userInfos);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
UserInfo(userId=1, userName=Java)
[UserInfo(userId=1, userName=Java), UserInfo(userId=2, userName=Python), UserInfo(userId=3, userName=JavaScript)]

9、query(String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor< T > rse)

public <T> T query(String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor<T> rse) throws DataAccessException {}

和上面的方法一样,使用的是PreparedStatementSetter来支持动态的参数,需要处理SQL中的占位符。具体的处理方式都一样。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    // 查询单个对象, 这个回调方式, 接收的是批量的结果,一次对所有的结果进行转换, 返回泛型<T>
    String sql = "select * from user_info where user_id = ?";
    UserInfo userInfo = jdbcTemplate.query(sql, new PreparedStatementSetter() {
        @Override
        public void setValues(PreparedStatement ps) throws SQLException {
            ps.setInt(1, 1);
        }
    }, new ResultSetExtractor<UserInfo>() {
        @Override
        public UserInfo extractData(ResultSet rs) throws SQLException, DataAccessException {
            rs.next();
            return new UserInfo(rs.getInt(1), rs.getString(2));
        }
    });
    System.out.println(userInfo);

    // 查询多个对象, 这个回调方式, 接收的是批量的结果,一次对所有的结果进行转换, 返回List<T>
    sql = "select * from user_info where user_id < ?";
    List<UserInfo> userInfos = jdbcTemplate.query(sql, ps -> ps.setInt(1, 4), rs -> {
        List<UserInfo> list = new ArrayList<>();
        while (rs.next()) {
            list.add(new UserInfo(rs.getInt(1), rs.getString(2)));
        }
        return list;
    });
    System.out.println(userInfos);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
UserInfo(userId=1, userName=Java)
[UserInfo(userId=1, userName=Java), UserInfo(userId=2, userName=Python), UserInfo(userId=3, userName=JavaScript)]

10、query(PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, final ResultSetExtractor< T > rse)

public <T> T query(PreparedStatementCreator psc, 
                   @Nullable final PreparedStatementSetter pss, 
                   final ResultSetExtractor<T> rse) throws DataAccessException {}

这是个很重要的方法,其它很多query()方法底层都是调用这个方法实现的。该方法有三个参数:

  • PreparedStatementCreator:创建一个预编译的SQL语句。

  • PreparedStatementSetter:如果SQL中有动态的参数占位符,则给占位符赋值参数值。

  • ResultSetExtractor:处理SQL执行的结果,将结果集中每一行转换为需要对象,需要先调用ResultSet.next()。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    // 查询单个对象, 这个回调方式, 接收的是批量的结果,一次对所有的结果进行转换, 返回泛型<T>
    UserInfo userInfo = jdbcTemplate.query(new PreparedStatementCreator() {
        @Override
        public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
            String sql = "select * from user_info where user_id = ?";
            return con.prepareStatement(sql);
        }
    }, new PreparedStatementSetter() {
        @Override
        public void setValues(PreparedStatement ps) throws SQLException {
            ps.setInt(1, 1);
        }
    }, new ResultSetExtractor<UserInfo>() {
        @Override
        public UserInfo extractData(ResultSet rs) throws SQLException, DataAccessException {
            rs.next();
            return new UserInfo(rs.getInt(1), rs.getString(2));
        }
    });
    System.out.println(userInfo);

    // 查询多个对象, 这个回调方式, 接收的是批量的结果,一次对所有的结果进行转换, 返回List<T>
    List<UserInfo> userInfos = jdbcTemplate.query(con -> {
        String sql = "select * from user_info where user_id < ?";
        return con.prepareStatement(sql);
    }, ps -> ps.setInt(1, 4), rs -> {
        List<UserInfo> list = new ArrayList<>();
        while (rs.next()) {
            list.add(new UserInfo(rs.getInt(1), rs.getString(2)));
        }
        return list;
    });
    System.out.println(userInfos);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
UserInfo(userId=1, userName=Java)
[UserInfo(userId=1, userName=Java), UserInfo(userId=2, userName=Python), UserInfo(userId=3, userName=JavaScript)]

11、query(PreparedStatementCreator psc, final ResultSetExtractor< T > rse)

public <T> T query(PreparedStatementCreator psc, ResultSetExtractor<T> rse) throws DataAccessException {}

该方法其实和上面的一样,从其方法内部的实现可以看出就是调用上面的方法(query(PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, final ResultSetExtractor< T > rse))。只是不支持传入动态的参数了,使用方式和上面类似:

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    // 查询单个对象, 这个回调方式, 接收的是批量的结果,一次对所有的结果进行转换, 返回泛型<T>
    UserInfo userInfo = jdbcTemplate.query(new PreparedStatementCreator() {
        @Override
        public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
            String sql = "select * from user_info where user_id = ?";
            PreparedStatement ps = con.prepareStatement(sql);
            ps.setInt(1, 1); // 实际上这种方式不算是动态传参
            return ps;
        }
    }, new ResultSetExtractor<UserInfo>() {
        @Override
        public UserInfo extractData(ResultSet rs) throws SQLException, DataAccessException {
            rs.next();
            return new UserInfo(rs.getInt(1), rs.getString(2));
        }
    });
    System.out.println(userInfo);

    // 查询多个对象, 这个回调方式, 接收的是批量的结果,一次对所有的结果进行转换, 返回List<T>
    List<UserInfo> userInfos = jdbcTemplate.query(con -> {
        String sql = "select * from user_info where user_id < ?";
        PreparedStatement ps = con.prepareStatement(sql);
        ps.setInt(1, 4); // 实际上这种方式不算是动态传参
        return ps;
    }, rs -> {
        List<UserInfo> list = new ArrayList<>();
        while (rs.next()) {
            list.add(new UserInfo(rs.getInt(1), rs.getString(2)));
        }
        return list;
    });
    System.out.println(userInfos);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
UserInfo(userId=1, userName=Java)
[UserInfo(userId=1, userName=Java), UserInfo(userId=2, userName=Python), UserInfo(userId=3, userName=JavaScript)]

12、query(String sql, RowMapper< T > rowMapper)

public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {}

该方法返回一个RowMapper指定泛型对象的List集合,用户需要通过mapRow(ResultSet rs, int rowNum)实现方法手动将每一行转换为需要的类型,可以是Map也可以是自定义的对象。不支持动态传入参数。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    // 这个回调方式, 一行数据回调一次, 结果自动封装到List中, 可理解为 RowCallbackHandler + ResultSetExtractor
    String sql = "select * from user_info where user_id = 1";
    jdbcTemplate.query(sql, new RowMapper<UserInfo>() {
        @Override
        public UserInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
            return new UserInfo(rs.getInt(1), rs.getString(2));
        }
    });

    // BeanPropertyRowMapper 为 RowMapper的子类, 可以直接转换为 Java Bean, 是 Spring 封装的.
    sql = "select * from user_info";
    userInfos = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(UserInfo.class));
    System.out.println(userInfos);

    // ColumnMapRowMapper 为 RowMapper的子类, 可以直接转换为 Map, 是 Spring 封装的.
    List<Map<String, Object>> userNames = jdbcTemplate.query(sql, new ColumnMapRowMapper());
    System.out.println(userNames);

    // 查询多行单列
    List<Integer> ids = jdbcTemplate.query(sql, (rs, rowNum) -> rs.getInt(1));
    System.out.println(ids);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
[UserInfo(userId=1, userName=Java)]
[UserInfo(userId=1, userName=Java), UserInfo(userId=2, userName=Python), UserInfo(userId=3, userName=JavaScript)]
[{user_id=1, user_name=Java}, {user_id=2, user_name=Python}, {user_id=3, user_name=JavaScript}]
[1, 2, 3]

13、query(String sql, RowMapper< T > rowMapper, @Nullable Object… args)

public <T> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object... args) throws DataAccessException {}
  • 第一个参数:需要执行的查询SQL。
  • 第二个参数:查询结果的每一行结果集。
  • 第三个参数:采用多参数组的形式,指SQL中占位符的参数值,没有则不写。
@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    // 这个回调方式, 一行数据回调一次, 结果自动封装到List中, 可理解为 RowCallbackHandler + ResultSetExtractor
    String sql = "select * from user_info where user_id = ?";
    List<UserInfo> userInfos = jdbcTemplate.query(sql, new RowMapper<UserInfo>() {
        @Override
        public UserInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
            return new UserInfo(rs.getInt(1), rs.getString(2));
        }
    }, 1);
    System.out.println(userInfos);

    // BeanPropertyRowMapper 为 RowMapper的子类, 可以直接转换为 Java Bean, 是 Spring 封装的.
    sql = "select * from user_info where user_id < ?";
    userInfos = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(UserInfo.class), 4);
    System.out.println(userInfos);

    // ColumnMapRowMapper 为 RowMapper的子类, 可以直接转换为 Map, 是 Spring 封装的.
    List<Map<String, Object>> userNames = jdbcTemplate.query(sql, new ColumnMapRowMapper(), 4);
    System.out.println(userNames);

    // 查询多行单列
    List<Integer> ids = jdbcTemplate.query(sql, (rs, rowNum) -> rs.getInt(1), 4);
    System.out.println(ids);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
[UserInfo(userId=1, userName=Java)]
[UserInfo(userId=1, userName=Java), UserInfo(userId=2, userName=Python), UserInfo(userId=3, userName=JavaScript)]
[{user_id=1, user_name=Java}, {user_id=2, user_name=Python}, {user_id=3, user_name=JavaScript}]
[1, 2, 3]

14、query(String sql, Object[] args, int[] argTypes, RowMapper< T > rowMapper

public <T> List<T> query(String sql, Object[] args, int[] argTypes, RowMapper<T> rowMapper) throws DataAccessException {}
  • 第一个参数:需要执行的查询SQL。
  • 第二个参数:有SQL中占位符参数构成的数组。如果没有则为传null。注意顺序保持一致。
  • 第三个参数:参数值的数据库类型。如果没有则为传null。该参数可有可无。
  • 第四个参数:查询结果的每一行结果集。
@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

        // 这个回调方式, 一行数据回调一次, 结果自动封装到List中, 可理解为 RowCallbackHandler + ResultSetExtractor
        Object[] param = {1};
        int[] index = {Types.INTEGER};
        String sql = "select * from user_info where user_id = ?";
        List<UserInfo> userInfos = jdbcTemplate.query(sql, param, index, new RowMapper<UserInfo>() {
            @Override
            public UserInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
                return new UserInfo(rs.getInt(1), rs.getString(2));
            }
        });
        System.out.println(userInfos);

        // BeanPropertyRowMapper 为 RowMapper的子类, 可以直接转换为 Java Bean, 是 Spring 封装的.
        param[0] = 4;
        sql = "select * from user_info where user_id < ?";
        userInfos = jdbcTemplate.query(sql, param, index, new BeanPropertyRowMapper<>(UserInfo.class));
        System.out.println(userInfos);

        // ColumnMapRowMapper 为 RowMapper的子类, 可以直接转换为 Map, 是 Spring 封装的.
        List<Map<String, Object>> userNames = jdbcTemplate.query(sql, param, index, new ColumnMapRowMapper());
        System.out.println(userNames);

        // 查询多行单列
        List<Integer> ids = jdbcTemplate.query(sql, param, index, (rs, rowNum) -> rs.getInt(1));
        System.out.println(ids);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
[UserInfo(userId=1, userName=Java)]
[UserInfo(userId=1, userName=Java), UserInfo(userId=2, userName=Python), UserInfo(userId=3, userName=JavaScript)]
[{user_id=1, user_name=Java}, {user_id=2, user_name=Python}, {user_id=3, user_name=JavaScript}]
[1, 2, 3]

15、query(String sql, @Nullable PreparedStatementSetter pss, RowMapper< T > rowMapper)

public <T> List<T> query(String sql, @Nullable PreparedStatementSetter pss, RowMapper<T> rowMapper) throws DataAccessException {}

使用的是PreparedStatementSetter来支持动态的参数,需要处理SQL中的占位符。具体的处理方式都一样。

@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    // 这个回调方式, 一行数据回调一次, 结果自动封装到List中, 可理解为 RowCallbackHandler + ResultSetExtractor
    String sql = "select * from user_info where user_id = ?";
    List<UserInfo> userInfos = jdbcTemplate.query(sql, new PreparedStatementSetter() {
        @Override
        public void setValues(PreparedStatement ps) throws SQLException {
            ps.setInt(1, 1);
        }
    }, new RowMapper<UserInfo>() {
        @Override
        public UserInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
            return new UserInfo(rs.getInt(1), rs.getString(2));
        }
    });
    System.out.println(userInfos);

    // BeanPropertyRowMapper 为 RowMapper的子类, 可以直接转换为 Java Bean, 是 Spring 封装的.
    sql = "select * from user_info where user_id < ?";
    userInfos = jdbcTemplate.query(sql,  ps -> ps.setInt(1, 4), new BeanPropertyRowMapper<>(UserInfo.class));
    System.out.println(userInfos);

    // ColumnMapRowMapper 为 RowMapper的子类, 可以直接转换为 Map, 是 Spring 封装的.
    List<Map<String, Object>> userNames = jdbcTemplate.query(sql, ps -> ps.setInt(1, 4), new ColumnMapRowMapper());
    System.out.println(userNames);

    // 查询多行单列
    List<Integer> ids = jdbcTemplate.query(sql, ps -> ps.setInt(1, 4), (rs, rowNum) -> rs.getInt(1));
    System.out.println(ids);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
[UserInfo(userId=1, userName=Java)]
[UserInfo(userId=1, userName=Java), UserInfo(userId=2, userName=Python), UserInfo(userId=3, userName=JavaScript)]
[{user_id=1, user_name=Java}, {user_id=2, user_name=Python}, {user_id=3, user_name=JavaScript}]
[1, 2, 3]

16、query(PreparedStatementCreator psc, RowMapper< T > rowMapper)

public <T> List<T> query(PreparedStatementCreator psc, RowMapper<T> rowMapper) throws DataAccessException {}
  • 第一个参数:创建一个预编译的SQL,如果SQL中有占位符,则需要进行处理。
  • 第二个参数:查询的每一行结果集,需要手动进行转换为想要的对象。
  • 注意:这个方法和上面的几个query(rowMapper)方法返回的是List< T >对象。无论查询时单条记录还是多条记录,返回的都是List。
@Autowired
JdbcTemplate jdbcTemplate;

@PostConstruct
public void execute() {
    // 先初始化表和初始化数据
    jdbcTemplate.execute("drop table if exists user_info");
    jdbcTemplate.execute("create table user_info(user_id int primary key, user_name varchar(255))");
    jdbcTemplate.update("insert into user_info values(1, 'Java')");
    jdbcTemplate.update("insert into user_info values(2, 'Python')");
    jdbcTemplate.update("insert into user_info values(3, 'JavaScript')");

    // 这个回调方式, 一行数据回调一次, 结果自动封装到List中, 可理解为 RowCallbackHandler + ResultSetExtractor
    List<UserInfo> userInfos = jdbcTemplate.query(new PreparedStatementCreator() {
        @Override
        public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
            String sql = "select * from user_info where user_id = ?";
            PreparedStatement ps = con.prepareStatement(sql);
            ps.setInt(1, 1);
            return ps;
        }
    }, new RowMapper<UserInfo>() {
        @Override
        public UserInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
            return new UserInfo(rs.getInt(1), rs.getString(2));
        }
    });
    System.out.println(userInfos);

    // BeanPropertyRowMapper 为 RowMapper的子类, 可以直接转换为 Java Bean, 是 Spring 封装的.
    userInfos = jdbcTemplate.query(con -> {
        String sql = "select * from user_info where user_id < ?";
        PreparedStatement ps = con.prepareStatement(sql);
        ps.setInt(1, 4);
        return ps;
    }, new BeanPropertyRowMapper<>(UserInfo.class));
    System.out.println(userInfos);

    // ColumnMapRowMapper 为 RowMapper的子类, 可以直接转换为 Map, 是 Spring 封装的.
    List<Map<String, Object>> userNames = jdbcTemplate.query(con -> {
        String sql = "select * from user_info where user_id < ?";
        PreparedStatement ps = con.prepareStatement(sql);
        ps.setInt(1, 4);
        return ps;
    }, new ColumnMapRowMapper());
    System.out.println(userNames);

    // 查询多行单列
    List<Integer> ids = jdbcTemplate.query(con -> {
        String sql = "select * from user_info where user_id < ?";
        PreparedStatement ps = con.prepareStatement(sql);
        ps.setInt(1, 4);
        return ps;
    }, (rs, rowNum) -> rs.getInt(1));
    System.out.println(ids);
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserInfo {
    private int userId;
    private String userName;
}
[UserInfo(userId=1, userName=Java)]
[UserInfo(userId=1, userName=Java), UserInfo(userId=2, userName=Python), UserInfo(userId=3, userName=JavaScript)]
[{user_id=1, user_name=Java}, {user_id=2, user_name=Python}, {user_id=3, user_name=JavaScript}]
[1, 2, 3]

三、Junit 测试 ResultSetExtractor/RowMapper

作者:YD_1989、来源:Spring JdbcTemplate Junit 测试 - ResultSetExtractor/RowMapper:https://blog.csdn.net/m0_58680865/article/details/134429114

Spring JdbcTemplate Junit 测试覆盖率 - 以 ResultSetExtractor / RowMapper 为例

1、RowMapper Mockito 测试

1、创建实体类 User

@Data
public class User {
    private Integer id;
    private String name;
    private String applicant;
    private String address;
    private Boolean flag;
}

2、JdbcTemplate 业务代码

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

@Repository
public class InsertGroup {

    @Autowired
    JdbcTemplate jdbcTemplate;

    public List<User> getUsers(String sql, String name, String address) {

        return jdbcTemplate.query(sql, new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet rs, int rowNum) throws SQLException {

                User user = new User();
                user.setId(rs.getInt("ID"));
                user.setName(rs.getString("Name"));
                user.setApplicant(rs.getString("Applicant"));
                user.setAddress(rs.getString("Address"));
                user.setFlag(rs.getBoolean("Flag"));
                return user;
            }
        }, name, address);
    }
}

3、Junit Mockito 测试

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.*;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
public class InsertGroupTest {

    @Mock
    JdbcTemplate jdbcTemplate;

    @InjectMocks
    InsertGroup insertGroup;

    @Before
    public void init() {
        MockitoAnnotations.openMocks(this);
    }


    @Test
    public void test() {

        /**
         * 需要注意的是:invocation.getArgument(1)
         * jdbcTemplate.query() 中 Mock 的参数索引是以0开始的,RowMapper 是第二个元素,因此索引是 1,如果是第三个位置,那么索引是 2,即 invocation.getArgument(2)
         * 同时需要注意的是,jdbcTemplate 中 query 的方法有很多,但是传的参数是不同的,因此 Mock 的参数数量要根据自己实际用到的 query 参数为准
         */
        Mockito.when(jdbcTemplate.query(ArgumentMatchers.anyString(), 
                                        ArgumentMatchers.any(RowMapper.class), 
                                        ArgumentMatchers.any()))
            .thenAnswer((invocation) -> {

                RowMapper<User> rowMapper = (RowMapper<User>) invocation.getArgument(1);
                ResultSet rs = Mockito.mock(ResultSet.class);

                // Mock ResultSet to return one rows.
                // Mockito.when(rs.getInt(ArgumentMatchers.eq("ID"))).thenReturn(506);

                // Mock ResultSet to return two rows.
                Mockito.when(rs.getInt(ArgumentMatchers.eq("ID"))).thenReturn(412, 300);
                Mockito.when(rs.getString(ArgumentMatchers.eq("Name"))).thenReturn("刘亦菲", "刘诗诗");
                Mockito.when(rs.getBoolean(ArgumentMatchers.eq("Flag"))).thenReturn(true, false);

                List<User> users = new ArrayList<>();
                users.add(rowMapper.mapRow(rs, 0));
                users.add(rowMapper.mapRow(rs, 1));

                return users;
            });

        List<User> userList = insertGroup.getUsers("sql", "1", "2");

        // Assert First Row
        assertFirstUser(userList.get(0));

        // Assert Second Row
        assertSecondUser(userList.get(1));
    }

    public void assertFirstUser(User user) {
        Assert.assertEquals(Integer.valueOf(412), user.getId());
        Assert.assertEquals("刘亦菲", user.getName());
        Assert.assertTrue(user.getFlag());
    }

    public void assertSecondUser(User user) {
        Assert.assertEquals(Integer.valueOf(300), user.getId());
        Assert.assertEquals("刘诗诗", user.getName());
        Assert.assertFalse(user.getFlag());
    }
}

2、ResultSetExtractor Mockito 测试

1、创建 User 实体

@Data
public class User {
    private Integer id;
    private String name;
    private String applicant;
    private String address;
    private Boolean flag;
}

2、JdbcTemplate 业务代码

public List<User> getUsers2(String sql, String name, String address) {

    return jdbcTemplate.query(sql, new ResultSetExtractor<List<User>>() {
        @Override
        public List<User> extractData(ResultSet rs) throws SQLException, DataAccessException {
            List<User> userList = new ArrayList<>();
            while (rs.next()) {
                User user = new User();
                user.setId(rs.getInt("ID"));
                user.setName(rs.getString("Name"));
                user.setApplicant(rs.getString("Applicant"));
                user.setAddress(rs.getString("Address"));
                user.setFlag(rs.getBoolean("Flag"));
                userList.add(user);
            }
            return userList;
        }
    }, name, address);
}

3、Junit Mockito 测试

@Test
public void test2() {
    Mockito.when(jdbcTemplate.query(ArgumentMatchers.anyString(), 
                                    ArgumentMatchers.any(ResultSetExtractor.class), 
                                    ArgumentMatchers.any()))
        .thenAnswer((invocation) -> {
            ResultSetExtractor<List<User>> resultSetExtractor =
                (ResultSetExtractor<List<User>>) invocation.getArgument(1);

            ResultSet rs = Mockito.mock(ResultSet.class);

            // two times it returns true and third time returns false.
            Mockito.when(rs.next()).thenReturn(true, true, false);

            // Mock ResultSet to return two rows.
            Mockito.when(rs.getInt(ArgumentMatchers.eq("ID"))).thenReturn(412, 300);
            Mockito.when(rs.getString(ArgumentMatchers.eq("Name"))) .thenReturn("刘亦菲", "刘诗诗");
            Mockito.when(rs.getBoolean(ArgumentMatchers.eq("Flag"))).thenReturn(true, false);

            return resultSetExtractor.extractData(rs);
        });

    List<User> users = insertGroup.getUsers2("sql", "1", "2");

    Assert.assertEquals(Integer.valueOf(412), users.get(0).getId());
    Assert.assertEquals("刘亦菲", users.get(0).getName());
    Assert.assertTrue(users.get(0).getFlag());
}

四、Spring JDBC 其他对象

Spring JDBC 其他对象:http://m.weizhi.cc/tutorial/detail-7428.html

1、NamedParameterJdbcTemplate

NamedParameterJdbcTemplate 类拓展了 JdbcTemplate 类,对 JdbcTemplate 类进行了封装从而支持具名参数特性

2、SimpleJdbcInsert

SimpleJdbcInsert 是一个简化插入操作的类,它帮助你自动生成插入 SQL 语句,而不需要手动编写。

优势:

  • 自动生成 SQL 插入语句:你只需要提供表名和列名,SimpleJdbcInsert 会生成插入语句。
  • 支持自动生成主键(如果数据库支持自动生成主键)。
  • 提供便捷的 API 来插入单行或多行数据。

适用场景:当你想避免写手动的 SQL 插入语句时,SimpleJdbcInsert 提供了更简洁的方式,尤其是表结构比较固定的情况下。

3、SqlQuery

SqlQuery:是一个抽象类,专门用于执行静态 SQL 查询。它的优势是可以:

  • 让查询的结构更加清晰:通过将查询封装在类中,你可以将 SQL 查询和其参数声明、结果映射等封装在一个地方,方便维护。
  • 提供参数声明和查询编译功能:你可以提前声明查询的参数,避免在每次执行查询时重复设置。
  • 让代码更加结构化:每个查询作为一个类,使得代码更具可读性和复用性。

适用场景:如果你有很多类似的查询操作,可以使用 SqlQuery 来简化代码,使其更加模块化、易于管理。

4、SqlUpdate

SqlUpdate 类类似于 SqlQuery,但是用于执行更新、删除或插入操作,而不是查询。它封装了常见的更新操作,并且可以提前声明 SQL 和参数。

优势:

  • 像 SqlQuery 一样,它简化了参数声明和 SQL 语句的封装。
  • 支持批量更新操作。
  • 可以自动处理返回的更新行数。

适用场景:当需要进行多次相似的更新操作(如批量更新、插入或删除)时,SqlUpdate 可以减少样板代码,简化操作。

5、SimpleJdbcCall

SimpleJdbcCall 用于简化调用存储过程或函数的操作。

优势:

  • 自动生成存储过程的调用语句。
  • 自动推断存储过程的输入和输出参数。
  • 简化存储过程调用时的输入、输出和结果映射。

适用场景:如果你需要频繁调用存储过程或数据库函数,SimpleJdbcCall 可以减少代码的复杂性,简化参数声明和结果处理。

6、总结:为什么需要这些类?

  1. 减少样板代码:虽然 JdbcTemplate 很强大,但有些重复操作如参数声明、SQL 编写、结果映射等会产生很多样板代码。这些类简化了这些操作,使代码更加简洁。
  2. 增强代码可读性和可维护性:通过封装 SQL 语句和操作逻辑在类中,代码结构变得更加清晰,易于维护,尤其是在需要大量重复查询或更新的场景下。
  3. 自动化一些常见的操作:例如 SimpleJdbcInsert 和 SimpleJdbcCall 自动生成 SQL 语句,使开发者不需要手动编写复杂的 SQL,减少出错的机会。
  4. 增强复用性:这些类提供了更加模块化的设计,可以在不同地方复用同样的 SQL 逻辑,从而提升代码的复用性。

因此,虽然 JdbcTemplate 已经简化了 JDBC 操作,但这些类进一步提升了开发效率,减少了重复代码,适用于特定场景下的高效开发。

五、JdbcTemplate 参考文献 & 鸣谢

  1. Spring中JdbcTemplate各个方法的使用介绍(持续更新中….):https://blog.csdn.net/whxjason/article/details/108949343
  2. SpringBoot高级篇JdbcTemplate之数据查询上篇:https://mp.weixin.qq.com/s/SeN5q4g92LfHYOAVlQxytA
  3. SpringBoot高级篇JdbcTemplate之数据查询下篇:https://mp.weixin.qq.com/s/laab3OyzTKL8mfR80Wy_QA
  4. 【JavaWeb】73:JdbcTemplate竟然只能算是江南七怪级别的:https://mp.weixin.qq.com/s/qsVKW3wwbAYYi4zrrReL5A
  5. Spring之JdbcTemplate使用:https://www.cnblogs.com/antLaddie/p/12859647.html
  6. Spring JdbcTemplate Junit 测试 - ResultSetExtractor/RowMapper:https://blog.csdn.net/m0_58680865/article/details/134429114

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 8629303@qq.com

×

喜欢就点赞,疼爱就打赏

GitHub