SpringBoot高级篇JdbcTemplate之数据查询上篇

栏目: Java · 发布时间: 5年前

内容简介:前面一篇介绍如何使用JdbcTemplate实现插入数据,接下来进入实际业务中,最常见的查询篇。由于查询的姿势实在太多,对内容进行了拆分,本篇主要介绍几个基本的使用姿势<!-- more -->环境依然借助前面一篇的配置,链接如:

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

  • queryForMap
  • queryForList
  • queryForObject

<!-- more -->

I. 环境准备

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

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

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

SpringBoot高级篇JdbcTemplate之数据查询上篇

II. 查询使用说明

1. queryForMap

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

a. 基本使用姿势

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

String sql = "select * from money where id=1";
Map<String, Object> map = jdbcTemplate.queryForMap(sql);
System.out.println("QueryForMap by direct sql ans: " + map);

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

public Map<String, Object> 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();
}

SpringBoot高级篇JdbcTemplate之数据查询上篇

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<Map<String, Object>> 类型数据,如果一条数据都没有,则返回一个空的集合
List<Map<String, Object>> 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类

@Data
public 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<MoneyPO>() {
    @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/setter
moneyPO = jdbcTemplate.queryForObject(sql, new RowMapper<MoneyPO>() {
    @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提供的接口时,可以看到下面这个接口

@Override
public <T> T queryForObject(String sql, Class<T> 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();
}

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

SpringBoot高级篇JdbcTemplate之数据查询上篇

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

// 下面开始测试下 org.springframework.jdbc.core.JdbcTemplate.queryForObject(java.lang.String, java.lang.Class<T>, 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);

SpringBoot高级篇JdbcTemplate之数据查询上篇

4. 测试

上面所有代码可以查看: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/101-jdbctemplate/src/main/java/com/git/hui/boot/jdbc/query/QueryService.java

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

@SpringBootApplication
public 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);
    }
}

输出结果如下

SpringBoot高级篇JdbcTemplate之数据查询上篇

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或者有更好的建议,欢迎批评指正,不吝感激

SpringBoot高级篇JdbcTemplate之数据查询上篇


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Computers and Intractability

Computers and Intractability

M R Garey、D S Johnson / W. H. Freeman / 1979-4-26 / GBP 53.99

This book's introduction features a humorous story of a man with a line of people behind him, who explains to his boss, "I can't find an efficient algorithm, but neither can all these famous people." ......一起来看看 《Computers and Intractability》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

html转js在线工具
html转js在线工具

html转js在线工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具