SpringBoot高級篇JdbcTemplate之數據查詢上篇

前面一篇介紹如何使用JdbcTemplate實現插入數據,接下來進入實際業務中,最多見的查詢篇。因爲查詢的姿式實在太多,對內容進行了拆分,本篇主要介紹幾個基本的使用姿式java

  • queryForMap
  • queryForList
  • queryForObject

<!-- more -->mysql

I. 環境準備

環境依然藉助前面一篇的配置,連接如: 190407-SpringBoot高級篇JdbcTemplate之數據插入使用姿式詳解git

或者直接查看項目源碼: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/101-jdbctemplategithub

咱們查詢所用數據,正是前面一篇插入的結果,以下圖spring

db mysql

II. 查詢使用說明

1. queryForMap

queryForMap,通常用於查詢單條數據,而後將db中查詢的字段,填充到map中,key爲列名,value爲值sql

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);
}

直接看上面代碼,會發現問題麼???mybatis

有經驗的小夥伴,可能一會兒就發現了sql注入的問題,若是傳入的參數是 '一灰灰blog' or 1=1 order by id desc limit 1, 這樣輸出和咱們預期的一致麼?app

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<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();
}

執行上面的代碼,拋出異常

從上面的源碼也能夠看到,上面的使用姿式,適用於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);

show

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);
    }
}

輸出結果以下

result

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

QrCode

相關文章
相關標籤/搜索