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

SpringBoot高級篇JdbcTemplate之數據查詢上篇 講了如何使用JdbcTemplate進行簡單的查詢操作,主要介紹了三種方法的調用姿勢 queryForMap, queryForList, queryForObject 本篇則繼續介紹剩下的兩種方法使用說明

  • queryForRowSet
  • query

<!-- more -->

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對象,這是一個集合,也就是說,可以查詢多條記錄

使用姿勢也比較簡單,如下

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

上面執行輸出結果如下

test output

2. 小結

本文主要介紹了另外兩種查詢姿勢, queryForRowSetquery

queryForRowSet

  • 返回SqlRowSet對象,需要遍歷獲取所有的結果

query

  • 提供三種結果處理方式

    • 不返回結果的回調姿勢
    • 對結果批量處理的方式 ResultSetExtractor
    • 對結果單個迭代處理方式 RowMapper
  • 可以返回>=0條數據
  • 如果需要對查詢的連接參數進行設置,使用PreparedStatementCreator來創建PreparedStatement方式處理

IV. 其他

相關博文

2. 聲明

盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

QrCode

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章