利用JDBC的PrepareStatement打印真實(shí)SQL的方法詳解
前言
本文主要給大家介紹了關(guān)于利用JDBC的PrepareStatement打印真實(shí)SQL的相關(guān)內(nèi)容,分享出來供大家參考學(xué)習(xí),下面來一起看看詳細(xì)的介紹:
我們知道,JDBC 的 PrepareStatement 優(yōu)點(diǎn)多多,通常都是推薦使用 PrepareStatement 而不是其基類 Statment。PrepareStatement 支持 ? 占位符,可以將參數(shù)按照類型轉(zhuǎn)自動(dòng)換為真實(shí)的值。既然這一過程是自動(dòng)的,封裝在 JDBC 內(nèi)部的,那么我們外部就不得而知目標(biāo)的 SQL 最終生成怎么樣——于是在調(diào)試過程中便有一個(gè)打印 SQL 的問題。我們對(duì) PrepareStatement 傳入 SQL 語句,如 SELECT * FROM table WHERE id = ?,然后我們傳入對(duì)應(yīng)的 id 參數(shù),假設(shè)是 id = 10,那怎么把得到參數(shù)的 SELECT * FROM table WHERE id = 12 結(jié)果完整地得出來呢?——這便是本文所要探討的問題。下面話不多說了,來一起看看詳細(xì)的介紹:
方法如下:
首先,我們看看典型的一個(gè) PrepareStatement 調(diào)用方法,如下一個(gè)函數(shù),
/** * 查詢單個(gè)結(jié)果,保存為 Map<String, Object> 結(jié)構(gòu)。如果查詢不到任何數(shù)據(jù)返回 null。 * * @param conn * 數(shù)據(jù)庫連接對(duì)象 * @param sql * SQL 語句,可以帶有 ? 的占位符 * @param params * 插入到 SQL 中的參數(shù),可單個(gè)可多個(gè)可不填 * @return Map<String, Object> 結(jié)構(gòu)的結(jié)果。如果查詢不到任何數(shù)據(jù)返回 null。 */ public static Map<String, Object> query(Connection conn, String sql, Object... params) { Map<String, Object> map = null; printRealSql(sql, params); // 打印真實(shí) SQL 的函數(shù) try (PreparedStatement ps = conn.prepareStatement(sql);) { if(params != null) for (int i = 0; i < params.length; i++) ps.setObject(i + 1, params[i]); try (ResultSet rs = ps.executeQuery();) { if (rs.isBeforeFirst()) { map = getResultMap(rs); } else { LOGGER.info("查詢 SQL:{0} 沒有符合的記錄!", sql); } } } catch (SQLException e) { LOGGER.warning(e); } return map; }
值得注意該函數(shù)里面:
printRealSql(sql, params); // 打印真實(shí) SQL 的函數(shù)
其參數(shù)一 sql 就是類似 SELECT * FROM table WHERE id = ?
的語句,參數(shù)二 params 為 Object... params 的參數(shù)列表,可以是任意類似的合法 SQL 值。最后,通過 printRealSql 函數(shù)最終得出形如 SELECT * FROM table WHERE id = 12
的結(jié)果。
printRealSql 函數(shù)源碼如下:
/** * 在開發(fā)過程,SQL語句有可能寫錯(cuò),如果能把運(yùn)行時(shí)出錯(cuò)的 SQL 語句直接打印出來,那對(duì)排錯(cuò)非常方便,因?yàn)槠淇梢灾苯涌截惖綌?shù)據(jù)庫客戶端進(jìn)行調(diào)試。 * * @param sql * SQL 語句,可以帶有 ? 的占位符 * @param params * 插入到 SQL 中的參數(shù),可單個(gè)可多個(gè)可不填 * @return 實(shí)際 sql 語句 */ public static String printRealSql(String sql, Object[] params) { if(params == null || params.length == 0) { LOGGER.info("The SQL is------------>\n" + sql); return sql; } if (!match(sql, params)) { LOGGER.info("SQL 語句中的占位符與參數(shù)個(gè)數(shù)不匹配。SQL:" + sql); return null; } int cols = params.length; Object[] values = new Object[cols]; System.arraycopy(params, 0, values, 0, cols); for (int i = 0; i < cols; i++) { Object value = values[i]; if (value instanceof Date) { values[i] = "'" + value + "'"; } else if (value instanceof String) { values[i] = "'" + value + "'"; } else if (value instanceof Boolean) { values[i] = (Boolean) value ? 1 : 0; } } String statement = String.format(sql.replaceAll("\\?", "%s"), values); LOGGER.info("The SQL is------------>\n" + statement); ConnectionMgr.addSql(statement); // 用來保存日志 return statement; } /** * ? 和參數(shù)的實(shí)際個(gè)數(shù)是否匹配 * * @param sql * SQL 語句,可以帶有 ? 的占位符 * @param params * 插入到 SQL 中的參數(shù),可單個(gè)可多個(gè)可不填 * @return true 表示為 ? 和參數(shù)的實(shí)際個(gè)數(shù)匹配 */ private static boolean match(String sql, Object[] params) { if(params == null || params.length == 0) return true; // 沒有參數(shù),完整輸出 Matcher m = Pattern.compile("(\\?)").matcher(sql); int count = 0; while (m.find()) { count++; } return count == params.length; }
可見,上述思路是非常簡(jiǎn)單的,——有多少個(gè) ? 占位符,就要求有多少個(gè)參數(shù),然后一一對(duì)照填入(數(shù)組)。match 函數(shù)會(huì)檢查第一個(gè)步驟,檢查個(gè)數(shù)是否匹配,否則會(huì)返回“SQL 語句中的占位符與參數(shù)個(gè)數(shù)不匹配”的提示;然后,參數(shù)的值會(huì)被轉(zhuǎn)換為符合 SQL 值所要求的類型;最后,就是將 SQL 一一填入,——此處使用了一個(gè)字符串的技巧,先把 ? 字符通通轉(zhuǎn)換為 %s,——那是 String.format 可識(shí)別的占位符,如此再傳入 Object[] 參數(shù)列表,即可得出我們期待的 SQL 結(jié)果。
我們不能保證那 SQL 可以直接放到數(shù)據(jù)庫中被解析。因?yàn)槲覀兊某踔灾皇前?SQL 打印出來,務(wù)求更近一步讓程序員在開發(fā)階段看到 SQL 是怎么樣子的,而且不是一堆 ?、?……,這樣會(huì)顯得更符合真實(shí)情形一點(diǎn)。
PrepareStatement 內(nèi)部源碼肯定有這一步驟或者某個(gè)變量是表示那個(gè)真實(shí) SQL 的,——只是沒有暴露出來。如果有,那么對(duì)程序員會(huì)更友好一些。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
idea新建maven項(xiàng)目沒有src目錄的操作方法
這篇文章主要介紹了idea新建maven項(xiàng)目沒有src目錄的兩種操作方法,需要的朋友可以參考下2018-03-03Java8中對(duì)泛型目標(biāo)類型推斷方法的改進(jìn)
這篇文章主要介紹了Java8中對(duì)泛型目標(biāo)類型推斷方法的改進(jìn),需要的朋友可以參考下2014-06-06Java源碼解析重寫鎖的設(shè)計(jì)結(jié)構(gòu)和細(xì)節(jié)
這篇文章主要為大家介紹了Java源碼解析重寫鎖的設(shè)計(jì)結(jié)構(gòu)和細(xì)節(jié),這小節(jié)我們以共享鎖作為案列,自定義一個(gè)共享鎖。有需要的朋友可以借鑒參考下2022-03-03java案例實(shí)戰(zhàn)之字符串轉(zhuǎn)換為二進(jìn)制
最近遇到個(gè)需求,要求編寫一個(gè)程序,從鍵盤錄入一個(gè)字符串,將字符串轉(zhuǎn)換為二進(jìn)制數(shù),下面這篇文章主要給大家介紹了關(guān)于java字符串轉(zhuǎn)換為二進(jìn)制的相關(guān)資料,需要的朋友可以參考下2023-06-06SSM項(xiàng)目實(shí)現(xiàn)短信驗(yàn)證碼登錄功能的示例代碼
這篇文章主要為大家分享了在SSM項(xiàng)目中實(shí)現(xiàn)短信驗(yàn)證碼登錄功能的示例代碼,文中的實(shí)現(xiàn)步驟講解詳細(xì),感興趣的小伙伴可以跟隨小編一起動(dòng)手嘗試一下2022-05-05