【DB系列】JdbcTemplate之数据查询下篇

文章目录
  1. I. 环境准备
  2. II. 查询使用说明
    1. 1. queryForRowSet
    2. 2. query
      1. a. 回调方式 queryByCallBack
      2. b. 结果批量处理 ResultSetExtractor
      3. c. 结果单行处理 RowMapper
      4. d. 占位sql
      5. e. PreparedStatement 方式
      6. f. 查不到数据场景
  3. III. 测试&小结
    1. 1. 测试
    2. 2. 小结
  4. IV. 其他
    1. 0. 项目
    2. 1. 一灰灰Blog
    3. 2. 声明
    4. 3. 扫描关注

SpringBoot高级篇JdbcTemplate之数据查询上篇 讲了如何使用JdbcTemplate进行简单的查询操作,主要介绍了三种方法的调用姿势 queryForMap, queryForList, queryForObject 本篇则继续介绍剩下的两种方法使用说明

  • queryForRowSet
  • query

I. 环境准备

环境依然借助前面一篇的配置,链接如: 190407-SpringBoot高级篇JdbcTemplate之数据插入使用姿势详解

或者直接查看项目源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/101-jdbctemplate

我们查询所用数据,正是前面一篇插入的结果,如下图

data

II. 查询使用说明

1. queryForRowSet

查询上篇中介绍的三种方法,返回的记录对应的结构要么是map,要么是通过RowMapper进行结果封装;而queryForRowSet方法的调用,返回的则是SqlRowSet对象,这是一个集合,也就是说,可以查询多条记录

使用姿势也比较简单,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void queryForRowSet() {
String sql = "select * from money where id > 1 limit 2";
SqlRowSet result = jdbcTemplate.queryForRowSet(sql);
while (result.next()) {
MoneyPO moneyPO = new MoneyPO();
moneyPO.setId(result.getInt("id"));
moneyPO.setName(result.getString("name"));
moneyPO.setMoney(result.getInt("money"));
moneyPO.setDeleted(result.getBoolean("is_deleted"));
moneyPO.setCreated(result.getDate("create_at").getTime());
moneyPO.setUpdated(result.getDate("update_at").getTime());

System.out.println("QueryForRowSet by DirectSql: " + moneyPO);
}
}

对于使用姿势而言与之前的区别不大,还有一种就是sql也支持使用占位方式,如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 采用占位符方式查询
sql = "select * from money where id > ? limit ?";
result = jdbcTemplate.queryForRowSet(sql, 1, 2);
while (result.next()) {
MoneyPO moneyPO = new MoneyPO();
moneyPO.setId(result.getInt("id"));
moneyPO.setName(result.getString("name"));
moneyPO.setMoney(result.getInt("money"));
moneyPO.setDeleted(result.getBoolean("is_deleted"));
moneyPO.setCreated(result.getDate("create_at").getTime());
moneyPO.setUpdated(result.getDate("update_at").getTime());

System.out.println("QueryForRowSet by ? sql: " + moneyPO);
}

重点关注下结果的处理,需要通过迭代器的方式进行数据遍历,获取每一列记录的值的方式和前面一样,可以通过序号的方式获取(序号从1开始),也可以通过制定列名方式(db列名)

2. query

对于query方法的使用,从不同的结果处理方式来看,划分了四种,下面逐一说明

a. 回调方式 queryByCallBack

这种回调方式,query方法不返回结果,但是需要传入一个回调对象,查询到结果之后,会自动调用

1
2
3
4
5
6
7
8
9
10
11
private void queryByCallBack() {
String sql = "select * from money where id > 1 limit 2";
// 这个是回调方式,不返回结果;一条记录回调一次
jdbcTemplate.query(sql, new RowCallbackHandler() {
@Override
public void processRow(ResultSet rs) throws SQLException {
MoneyPO moneyPO = result2po(rs);
System.out.println("queryByCallBack: " + moneyPO);
}
});
}

上面的实例代码中,可以看到回调方法中传入一个ResultSet对象,简单封装一个转换为PO的方法

1
2
3
4
5
6
7
8
9
10
private MoneyPO result2po(ResultSet result) throws SQLException {
MoneyPO moneyPO = new MoneyPO();
moneyPO.setId(result.getInt("id"));
moneyPO.setName(result.getString("name"));
moneyPO.setMoney(result.getInt("money"));
moneyPO.setDeleted(result.getBoolean("is_deleted"));
moneyPO.setCreated(result.getDate("create_at").getTime());
moneyPO.setUpdated(result.getDate("update_at").getTime());
return moneyPO;
}

在后面的测试中,会看到上面会输出两行数据,也就是说

返回结果中每一条记录都执行一次上面的回调方法,即返回n条数据,上面回调执行n次

b. 结果批量处理 ResultSetExtractor

前面回调方式主要针对的是不关系返回结果,这里的则是将返回的结果,封装成我们预期的对象,然后返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void queryByResultSet() {
String sql = "select * from money where id > 1 limit 2";
// extractData 接收的是批量的结果,因此可以理解为一次对所有的结果进行转换,可以和 RowMapper 方式进行对比
List<MoneyPO> result = jdbcTemplate.query(sql, new ResultSetExtractor<List<MoneyPO>>() {
@Override
public List<MoneyPO> extractData(ResultSet rs) throws SQLException, DataAccessException {
List<MoneyPO> list = new ArrayList<>();
while (rs.next()) {
list.add(result2po(rs));
}
return list;
}
});

System.out.println("queryByResultSet: " + result);
}

额外注意下上面你的使用,如果返回的是多条数据,注意泛型参数类型为List<?>, 简单来说这是一个对结果进行批量转换的使用场景

因此在上面的extractData方法调用时,传入的是多条数据,需要自己进行迭代遍历,而不能像第一种那样使用

c. 结果单行处理 RowMapper

既然前面有批量处理,那当然也就有单行的转换方式了,如下

1
2
3
4
5
6
7
8
9
10
11
private void queryByRowMapper() {
String sql = "select * from money where id > 1 limit 2";
// 如果返回的是多条数据,会逐一的调用 mapRow方法,因此可以理解为单个记录的转换
List<MoneyPO> result = jdbcTemplate.query(sql, new RowMapper<MoneyPO>() {
@Override
public MoneyPO mapRow(ResultSet rs, int rowNum) throws SQLException {
return result2po(rs);
}
});
System.out.println("queryByRowMapper: " + result);
}

在实际使用中,只需要记住RowMapper方式传入的是单条记录,n次调用;而ResultSetExtractor方式传入的全部的记录,1次调用

d. 占位sql

前面介绍的几种都是直接写sql,这当然不是推荐的写法,更常见的是占位sql,通过传参替换,这类的使用前一篇博文介绍得比较多了,这里给出一个简单的演示

1
2
3
4
5
6
7
8
9
10
11
private void queryByPlaceHolder() {
String sql = "select * from money where id > ? limit ?";
// 占位方式,在最后面加上实际的sql参数,第二个参数也可以换成 ResultSetExtractor
List<MoneyPO> result = jdbcTemplate.query(sql, new RowMapper<MoneyPO>() {
@Override
public MoneyPO mapRow(ResultSet rs, int rowNum) throws SQLException {
return result2po(rs);
}
}, 1, 2);
System.out.println("queryByPlaceHolder: " + result);
}

e. PreparedStatement 方式

在插入记录的时候,PreparedStatement这个我们用得很多,特别是在要求返回主键id时,离不开它了, 在实际的查询中,也是可以这么用的,特别是在使用PreparedStatementCreator,我们可以设置查询的db连接参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void queryByPreparedStatement() {
// 使用 PreparedStatementCreator查询,主要是可以设置连接相关参数, 如设置为只读
List<MoneyPO> result = jdbcTemplate.query(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
con.setReadOnly(true);
PreparedStatement statement = con.prepareStatement("select * from money where id > ? limit ?");
// 表示 id > 1
statement.setInt(1, 1);
// 表示 limit 2
statement.setInt(2, 2);
return statement;
}
}, new RowMapper<MoneyPO>() {
@Override
public MoneyPO mapRow(ResultSet rs, int rowNum) throws SQLException {
return result2po(rs);
}
});

System.out.println("queryByPreparedStatement: " + result);
}

上面是一个典型的使用case,当然在实际使用JdbcTemplate时,基本不这么玩

f. 查不到数据场景

前面一篇查询中,在单个查询中如果没有结果命中sql,会抛出异常,那么这里呢?

1
2
3
4
5
6
7
8
9
10
11
12
private void queryNoRecord() {
// 没有命中的情况下,会怎样
List<MoneyPO> result = jdbcTemplate
.query("select * from money where id > ? limit ?", new Object[]{100, 2}, new RowMapper<MoneyPO>() {
@Override
public MoneyPO mapRow(ResultSet rs, int rowNum) throws SQLException {
return result2po(rs);
}
});

System.out.println("queryNoRecord: " + result);
}

从后面的输出结果会看出,没有记录命中时,并没有什么关系,上面会返回一个空集合

III. 测试&小结

1. 测试

接下来测试下上面的输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.git.hui.boot.jdbc;

import com.git.hui.boot.jdbc.insert.InsertService;
import com.git.hui.boot.jdbc.query.QueryService;
import com.git.hui.boot.jdbc.query.QueryServiceV2;
import com.git.hui.boot.jdbc.update.UpdateService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* Created by @author yihui in 11:04 19/4/4.
*/
@SpringBootApplication
public class Application {
private QueryServiceV2 queryServiceV2;

public Application(QueryServiceV2 queryServiceV2) {
this.queryServiceV2 = queryServiceV2;
queryTest2();
}

public void queryTest2() {
// 第三个调用
queryServiceV2.queryForRowSet();
queryServiceV2.query();
}

public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}

上面执行输出结果如下

test output

2. 小结

本文主要介绍了另外两种查询姿势, queryForRowSetquery

queryForRowSet

  • 返回SqlRowSet对象,需要遍历获取所有的结果

query

  • 提供三种结果处理方式
    • 不返回结果的回调姿势
    • 对结果批量处理的方式 ResultSetExtractor
    • 对结果单个迭代处理方式 RowMapper
  • 可以返回>=0条数据
  • 如果需要对查询的连接参数进行设置,使用PreparedStatementCreator来创建PreparedStatement方式处理

IV. 其他

相关博文

0. 项目

1. 一灰灰Blog

一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

2. 声明

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

3. 扫描关注

一灰灰blog

QrCode

知识星球

goals


打赏 如果觉得我的文章对您有帮助,请随意打赏。
分享到