190417-SpringBoot高階篇JdbcTemplate之資料查詢下篇
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
我們查詢所用資料,正是前面一篇插入的結果,如下圖
II. 查詢使用說明
1. queryForRowSet
查詢上篇中介紹的三種方法,返回的記錄對應的結構要麼是map,要麼是通過 RowMapper
進行結果封裝;而 queryForRowSet
方法的呼叫,返回的則是 SqlRowSet
物件,這是一個集合,也就是說,可以查詢多條記錄
使用姿勢也比較簡單,如下
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也支援使用佔位方式,如
// 採用佔位符方式查詢 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方法不返回結果,但是需要傳入一個回撥物件,查詢到結果之後,會自動呼叫
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的方法
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
前面回撥方式主要針對的是不關係返回結果,這裡的則是將返回的結果,封裝成我們預期的物件,然後返回
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
既然前面有批量處理,那當然也就有單行的轉換方式了,如下
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,通過傳參替換,這類的使用前一篇博文介紹得比較多了,這裡給出一個簡單的演示
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連線引數
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,會丟擲異常,那麼這裡呢?
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. 測試
接下來測試下上面的輸出
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); } }
上面執行輸出結果如下
2. 小結
本文主要介紹了另外兩種查詢姿勢, queryForRowSet
與 query
queryForRowSet
- 返回
SqlRowSet
物件,需要遍歷獲取所有的結果
query
- 提供三種結果處理方式
ResultSetExtractor RowMapper
- 可以返回>=0條資料
- 如果需要對查詢的連線引數進行設定,使用
PreparedStatementCreator
來建立PreparedStatement
方式處理
IV. 其他
相關博文
0. 專案
- 工程: https://github.com/liuyueyi/spring-boot-demo
- 專案: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/101-jdbctemplate
1. 一灰灰Blog
- 一灰灰Blog個人部落格 https://blog.hhui.top
- 一灰灰Blog-Spring專題部落格 http://spring.hhui.top
一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛
2. 宣告
盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
- 微博地址:小灰灰Blog
- QQ: 一灰灰/3302797840
3. 掃描關注
一灰灰blog
知識星球
打賞 如果覺得我的文章對您有幫助,請隨意打賞。