博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringBoot高级篇JdbcTemplate之数据查询上篇
阅读量:6577 次
发布时间:2019-06-24

本文共 7268 字,大约阅读时间需要 24 分钟。

前面一篇介绍如何使用JdbcTemplate实现插入数据,接下来进入实际业务中,最常见的查询篇。由于查询的姿势实在太多,对内容进行了拆分,本篇主要介绍几个基本的使用姿势

  • queryForMap
  • queryForList
  • queryForObject

I. 环境准备

环境依然借助前面一篇的配置,链接如:

或者直接查看项目源码:

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

II. 查询使用说明

1. queryForMap

queryForMap,一般用于查询单条数据,然后将db中查询的字段,填充到map中,key为列名,value为值

a. 基本使用姿势

最基本的使用姿势,就是直接写完整的sql,执行

String sql = "select * from money where id=1";Map
map = jdbcTemplate.queryForMap(sql);System.out.println("QueryForMap by direct sql ans: " + map);复制代码

这种用法的好处是简单,直观;但是有个非常致命的缺点,如果你提供了一个接口为

public Map
query(String condition) { String sql = "select * from money where name=" + condition; return jdbcTemplate.queryForMap(sql);}复制代码

直接看上面代码,会发现问题么???

有经验的小伙伴,可能一下子就发现了sql注入的问题,如果传入的参数是 '一灰灰blog' or 1=1 order by id desc limit 1, 这样输出和我们预期的一致么?

b. 占位符替换

正是因为直接拼sql,可能到只sql注入的问题,所以更推荐的写法是通过占位符 + 传参的方式

// 使用占位符替换方式查询sql = "select * from money where id=?";map = jdbcTemplate.queryForMap(sql, new Object[]{
1});System.out.println("QueryForMap by ? ans: " + map);// 指定传参类型, 通过传参来填充sql中的占位sql = "select * from money where id =?";map = jdbcTemplate.queryForMap(sql, 1);System.out.println("QueryForMap by ? ans: " + map);复制代码

从上面的例子中也可以看出,占位符的使用很简单,用问好(?)来代替具体的取值,然后传参

传参有两种姿势,一个是传入Object[]数组;另外一个是借助java的不定长参数方式进行传参;两个的占位替换都是根据顺序来的,也就是如果你有一个值想替换多个占位符,那就得血多次

如:

sql = "select * from money where (name=? and id=?) or (name=? and id=?)";map = jdbcTemplate.queryForMap(sql, "一灰灰blog", 1, "一灰灰blog", 2);复制代码

c. 查不到的case

使用queryForMap有个不得不注意的事项,就是如果查不到数据时,会抛一个异常出来,所以需要针对这种场景进行额外处理

// 查不到数据的情况try {    sql = "select * from money where id =?";    map = jdbcTemplate.queryForMap(sql, 100);    System.out.println("QueryForMap by ? ans: " + map);} catch (EmptyResultDataAccessException e) {    e.printStackTrace();}复制代码

2. queryForList

前面针对的主要是单个查询,如果有多个查询的场景,可能就需要用到queryForList了,它的使用姿势和上面其实差别不大;

a. 基本使用姿势

最基本的使用姿势当然是直接写sql执行了

System.out.println("============ query for List! ==============");String sql =        "select id, `name`, money, is_deleted as isDeleted, unix_timestamp(create_at) as created, unix_timestamp(update_at) as updated from money limit 3;";// 默认返回 List
> 类型数据,如果一条数据都没有,则返回一个空的集合List
> res = jdbcTemplate.queryForList(sql);System.out.println("basicQueryForList: " + res);复制代码

注意返回的结果是List<Map<String, Object>>, 如果一条都没有命中,会返回一个空集合, 和 QueryForMap 抛异常是不一样的

b. 占位符替换

直接使用sql的查询方式,依然和前面一样,可能有注入问题,当然优先推荐的使用通过占位来传参方式

String sql2 = "select id, `name`, money, is_deleted as isDeleted, unix_timestamp(create_at) as created, " +        "unix_timestamp(update_at) as updated from money where id=? or name=?;";res = jdbcTemplate.queryForList(sql2, 2, "一灰灰2");System.out.println("queryForList by template: " + res);复制代码

3. queryForObject

如果是简单查询,直接用上面两个也就够了,但是对于使用过mybatis,Hibernate的同学来说,每次返回Map<String, Object>,就真的有点蛋疼了, 对于mysql这种数据库,表的结构基本不变,完全可以和POJO进行关联,对于业务开发者而言,当然是操作具体的POJO比Map要简单直观多了

下面将介绍下,如何使用 queryForObject 来达到我们的目标

a. 原始使用姿势

首先介绍下利用 RowMapper 来演示下,最原始的使用姿势

第一步是定义对应的POJO类

@Datapublic static class MoneyPO implements Serializable {    private static final long serialVersionUID = -5423883314375017670L;    private Integer id;    private String name;    private Integer money;    private boolean isDeleted;    private Long created;    private Long updated;}复制代码

然后就是使用姿势

// sql + 指定返回类型方式访问// 使用这种sql的有点就是方便使用反射方式,实现PO的赋值String sql =        "select id, `name`, money, is_deleted as isDeleted, unix_timestamp(create_at) as created, unix_timestamp(update_at) as updated from money limit 1;";// 需要注意,下标以1开始MoneyPO moneyPO = jdbcTemplate.queryForObject(sql, new RowMapper
() { @Override public MoneyPO mapRow(ResultSet rs, int rowNum) throws SQLException { MoneyPO po = new MoneyPO(); po.setId(rs.getInt(1)); po.setName(rs.getString(2)); po.setMoney(rs.getInt(3)); po.setDeleted(rs.getBoolean(4)); po.setCreated(rs.getLong(5)); po.setUpdated(rs.getLong(6)); return po; }});System.out.println("queryFroObject by RowMapper: " + moneyPO);复制代码

从使用姿势上看,RowMapper 就是一个sql执行之后的回调,实现结果封装,这里需要注意的就是 ResultSet 封装了完整的返回结果,可以通过下标方式指定,下标是从1开始,而不是我们常见的0,需要额外注意

这个下标从1开始,感觉有点蛋疼,总容易记错,所以更推荐的方法是直接通过列名获取数据

// 直接使用columnName来获取对应的值,这里就可以考虑使用反射方式来赋值,减少getter/settermoneyPO = jdbcTemplate.queryForObject(sql, new RowMapper
() { @Override public MoneyPO mapRow(ResultSet rs, int rowNum) throws SQLException { MoneyPO po = new MoneyPO(); po.setId(rs.getInt("id")); po.setName(rs.getString("name")); po.setMoney(rs.getInt("money")); po.setDeleted(rs.getBoolean("isDeleted")); po.setCreated(rs.getLong("created")); po.setUpdated(rs.getLong("updated")); return po; }});System.out.println("queryFroObject by RowMapper: " + moneyPO);复制代码

b. 高级使用

当sql返回的列名和POJO的属性名可以完全匹配上的话,上面的这种写法就显得非常冗余和麻烦了,我需要更优雅简洁的使用姿势,最好就是直接传入POJO类型,自动实现转换

如果希望得到这个效果,你需要的就是下面这个了: BeanPropertyRowMapper

// 更简单的方式,直接通过BeanPropertyRowMapper来实现属性的赋值,前提是sql返回的列名能正确匹配moneyPO = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(MoneyPO.class));System.out.println("queryForObject by BeanPropertyRowMapper: " + moneyPO);复制代码

c. 易错使用姿势

查看JdbcTemplate提供的接口时,可以看到下面这个接口

@Overridepublic 
T queryForObject(String sql, Class
requiredType, @Nullable Object... args) throws DataAccessException { return queryForObject(sql, args, getSingleColumnRowMapper(requiredType));}复制代码

自然而然的想到,直接传入POJO的类型进去,是不是就可以得到我们预期的结果了?

String sql =                "select id, `name`, money, is_deleted as isDeleted, unix_timestamp(create_at) as created, unix_timestamp(update_at) as updated from money limit 1;";try {    MoneyPO po = jdbcTemplate.queryForObject(sql, MoneyPO.class);    System.out.println("queryForObject by requireType return: " + po);} catch (Exception e) {    e.printStackTrace();}复制代码

执行上面的代码,抛出异常

从上面的源码也可以看到,上面的使用姿势,适用于sql只返回一列数据的场景,即下面的case

// 下面开始测试下 org.springframework.jdbc.core.JdbcTemplate.queryForObject(java.lang.String, java.lang.Class
, java.lang.Object...)// 根据测试,这个类型,只能是基本类型String sql2 = "select id from money where id=?";Integer res = jdbcTemplate.queryForObject(sql2, Integer.class, 1);System.out.println("queryForObject by requireId return: " + res);复制代码

4. 测试

上面所有代码可以查看:

简单的继承调用下上面的所有方法

@SpringBootApplicationpublic class Application {    private QueryService queryService;    public Application(QueryService queryService) {        this.queryService = queryService;        queryTest();    }    public void queryTest() {        queryService.queryForMap();        queryService.queryForObject();        queryService.queryForList();    }    public static void main(String[] args) {        SpringApplication.run(Application.class);    }}复制代码

输出结果如下

III. 小结

本篇博文主要介绍了JdbcTemplate查询的简单使用姿势,主要是queryForMap, queryForList, queryForObject三种方法的调用

1. 根据返回结果数量

单条记录查询

  • queryForMap : 返回一条记录,返回的结果塞入Map<String, Object>, key为固定的String对应查询的列名;value为实际值
  • queryForObject :同样返回一条数据,与上面的区别在于可以借助RowMapper来实现返回结果转换为对应的POJO

需要注意的是,上面的查询,必须有一条记录返回,如果查不到,则抛异常

批量查询

  • queryForList :一次查询>=0条数据,返回类型为 List<Map<String, Object>>

2. 根据sql类型

有两种sql传参方式

  • 一个是写完整的sql语句,就和我们普通的sql查询一样;问题是存在注入的风险
  • 其次是使用占位符(?), 实际的值通过参数方式传入

IV. 其他

0. 项目

  • 工程:
  • 项目:

1. 声明

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

  • 一灰灰Blog个人博客
  • 一灰灰Blog-Spring专题博客
  • 微博地址:
  • QQ: 一灰灰/3302797840

转载地址:http://xmwno.baihongyu.com/

你可能感兴趣的文章
PHP中使用socket通信响应速度慢的原因与解决办法
查看>>
Win7下安装Mysql(解压缩版)
查看>>
UVA 11992 Fast Matrix Operations (降维)
查看>>
Asp.net core Identity + identity server + angular 学习笔记 (第一篇)
查看>>
暂时不想读研的几点理由
查看>>
增加临时表空间组Oracle11g单实例
查看>>
Diff Two Arrays
查看>>
stark组件(1):动态生成URL
查看>>
169. Majority Element
查看>>
下拉菜单
查看>>
[清华集训2014]玛里苟斯
查看>>
Doctype作用?严格模式与混杂模式如何区分?它们有何意义
查看>>
【MVC+EasyUI实例】对数据网格的增删改查(上)
查看>>
第三章:如何建模服务
查看>>
Project Euler 345: Matrix Sum
查看>>
你可能不知道的技术细节:存储过程参数传递的影响
查看>>
POJ1703 Find them, Catch them
查看>>
HTML转义字符大全(转)
查看>>
[摘录]调动员工积极性的七个关键
查看>>
Linux getcwd()的实现【转】
查看>>