MyBatis的SQL執(zhí)行結(jié)果和客戶(hù)端執(zhí)行結(jié)果不一致問(wèn)題排查
最近遇到一個(gè)調(diào)試很久的問(wèn)題,MyBatis 查詢(xún) Oracle 數(shù)據(jù)庫(kù)查詢(xún)結(jié)果與在客戶(hù)端查詢(xún)結(jié)果不一致。
問(wèn)題引入
測(cè)試表、測(cè)試數(shù)據(jù)
創(chuàng)建測(cè)試表、序列
CREATE TABLE t_test_table ( "ID" NUMBER(18,0), "CREATE_TIME" TIMESTAMP(6), "FIELD_TYPE" CHAR(20), CONSTRAINT pk_id PRIMARY KEY(ID) ) CREATE SEQUENCE seq_t_test_table;
插入測(cè)試數(shù)據(jù)
INSERT INTO t_test_table ("ID", "CREATE_TIME", "FIELD_TYPE") VALUES (SEQ_T_TEST_TABLE.nextval, SYSDATE - NUMTODSINTERVAL(1, 'DAY'), 'Integer'); INSERT INTO t_test_table ("ID", "CREATE_TIME", "FIELD_TYPE") VALUES (SEQ_T_TEST_TABLE.nextval, SYSDATE - NUMTODSINTERVAL(1, 'HOUR'), 'Double'); INSERT INTO t_test_table ("ID", "CREATE_TIME", "FIELD_TYPE") VALUES (SEQ_T_TEST_TABLE.nextval, SYSDATE - NUMTODSINTERVAL(1, 'MINUTE'), 'Long');
查詢(xún)數(shù)據(jù)是否插入成功
問(wèn)題介紹
MyBatis xml 配置
<select id="selectByFieldType" resultType="com.scd.model.po.TestTable"> SELECT * FROM T_TEST_TABLE WHERE FIELD_TYPE = #{filedType} </select>
運(yùn)行輸出的日志
20:26:08.678 [main] DEBUG com.scd.mapper.OracleBatchSeqMapper.selectByFieldType - ==> Preparing: SELECT * FROM T_TEST_TABLE WHERE FIELD_TYPE = ?
20:26:08.906 [main] DEBUG com.scd.mapper.OracleBatchSeqMapper.selectByFieldType - ==> Parameters: Double(String)
20:26:09.013 [main] DEBUG com.scd.mapper.OracleBatchSeqMapper.selectByFieldType - <== Total: 0
這里顯示輸出的結(jié)果為0條,之前有插入數(shù)據(jù)的,明顯出錯(cuò)了,于是把sql語(yǔ)句復(fù)制到DBeaver客戶(hù)端執(zhí)行的時(shí)候,是可以查詢(xún)數(shù)據(jù)的
排查問(wèn)題
日志中的SQL 和 客戶(hù)端的 SQL 一致的,唯一不同的就是日志中是使用占位符形式,也就是預(yù)編譯的SQL, 而客戶(hù)端直接執(zhí)行的SQL。首先第一步就是把xml中的預(yù)編譯SQL修改成字符串拼接的形式
<select id="selectByFieldType" resultType="com.scd.model.po.TestTable"> SELECT * FROM T_TEST_TABLE WHERE FIELD_TYPE = '${filedType}' </select>
執(zhí)行輸出的結(jié)果確實(shí)有一條,和客戶(hù)端的一致,運(yùn)行日志結(jié)果如下
20:38:45.603 [main] DEBUG com.scd.mapper.OracleBatchSeqMapper.selectByFieldType - ==> Preparing: SELECT * FROM T_TEST_TABLE WHERE FIELD_TYPE = 'Double'
20:38:45.747 [main] DEBUG com.scd.mapper.OracleBatchSeqMapper.selectByFieldType - ==> Parameters:
20:38:45.844 [main] DEBUG com.scd.mapper.OracleBatchSeqMapper.selectByFieldType - <== Total: 1
用這種方式雖然可以解決,但是很多編碼規(guī)范在 xml 中不允許配置 $,防止SQL注入。還需要找一下為啥 # 的形式不能得到正確的結(jié)果。
調(diào)試 MyBatis源碼
對(duì)比拼接 SQL 和 預(yù)編譯的 SQL,區(qū)別在于設(shè)置參數(shù)填充,找到 MyBatis中這一塊的執(zhí)行邏輯,經(jīng)過(guò)調(diào)試,定位到設(shè)置參數(shù)的代碼在 org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters
由于xml中未指定TypeHandler,程序使用默認(rèn)的 UnknownTypeHandler。根據(jù)參數(shù)的類(lèi)型去匹配TypeHandler
參數(shù) “Double” 是字符串,匹配到了 StringTypeHandler
繼續(xù)調(diào)試,發(fā)現(xiàn)設(shè)置參數(shù)的代碼段如下
發(fā)現(xiàn)整個(gè)設(shè)置數(shù)據(jù)的過(guò)程沒(méi)有啥問(wèn)題呀,于是把問(wèn)題簡(jiǎn)化一下,弄成JDBC的方式執(zhí)行看看。
JDBC 執(zhí)行 SQL
按照 MyBtatis 的執(zhí)行過(guò)程,把代碼簡(jiǎn)化成如下
import org.junit.Test; import java.sql.*; import java.util.Properties; /** * @author James * @date 2022/12/10 19:02 */ public class OracleJdbc { private static final String driver = "oracle.jdbc.driver.OracleDriver"; private static final String url = "jdbc:oracle:thin:@localhost:1521/TEST"; private static final String userName = "TEST_USER"; private static final String password = "TEST_USER"; static { // 加載驅(qū)動(dòng) try { Class.forName(driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public Connection createConnection(String url, String userName, String password) { Connection connection = null; try { connection = DriverManager.getConnection(url, userName, password); } catch (SQLException e) { e.printStackTrace(); } return connection; } @Test public void testStrQuery() { try (Connection connection = createConnection(url, userName, password)) { String sql = "SELECT * FROM T_TEST_TABLE WHERE FIELD_TYPE = ?"; PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1, "Double"); preparedStatement.execute(); while (preparedStatement.getMoreResults()) { System.out.println(preparedStatement.getResultSet()); } } catch (SQLException e) { e.printStackTrace(); } } }
運(yùn)行測(cè)試用例,發(fā)現(xiàn)使用JDBC也無(wú)法獲取到正確的結(jié)果。于是打算看看Oracle的SQL執(zhí)行日志,看預(yù)編譯的SQL與直接拼接的SQL有啥區(qū)別
select * from v$sql WHERE SQL_TEXT LIKE '%T_TEST_TABLE%' ORDER BY FIRST_LOAD_TIME DESC;
根據(jù)運(yùn)行的時(shí)間點(diǎn),找到預(yù)編譯的 SQL為
SELECT * FROM T_TEST_TABLE WHERE FIELD_TYPE = :1
直接拼接的SQL為
SELECT * FROM T_TEST_TABLE WHERE FIELD_TYPE = 'Double'
預(yù)編譯的SQL在客戶(hù)端中運(yùn)行也沒(méi)問(wèn)題
說(shuō)明代碼沒(méi)問(wèn)題,是不是字段里面有空格導(dǎo)致查詢(xún)不到的呢?于是把字段直接復(fù)制出來(lái)。復(fù)制出來(lái)果然發(fā)現(xiàn)了空格
但是運(yùn)行這個(gè)也不對(duì)。這個(gè)時(shí)候思考著為啥有空格呢?于是百度看了一下CHAR字段的介紹,原來(lái)Oracle的 CHAR字段長(zhǎng)度不夠的用空格填充,輸出上面的字符長(zhǎng)度看看
解決問(wèn)題
由于Oracle的CHAR類(lèi)型在插入的數(shù)據(jù)長(zhǎng)度不夠的情況下會(huì)用空格填充,于是把字段類(lèi)型修改成 VARCHAR2
ALTER TABLE T_TEST_TABLE MODIFY "FIELD_TYPE" VARCHAR2(20)
把之前的數(shù)據(jù)空格清除
UPDATE T_TEST_TABLE SET FIELD_TYPE = TRIM(FIELD_TYPE)
再次執(zhí)行 MyBatis 的測(cè)試方法,可以發(fā)現(xiàn)查出數(shù)據(jù)
待解決問(wèn)題
為啥用JDBC 預(yù)編譯 SQL 查詢(xún) CHAR 類(lèi)型的字段會(huì)有空格匹配問(wèn)題?
到此這篇關(guān)于MyBatis的SQL執(zhí)行結(jié)果和客戶(hù)端執(zhí)行結(jié)果不一致問(wèn)題排查的文章就介紹到這了,更多相關(guān)MyBatis SQL和客戶(hù)端執(zhí)行結(jié)果不一致內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MyBatis 中 ${}和 #{}的正確使用方法(千萬(wàn)不要亂用)
這篇文章主要介紹了MyBatis 中 ${}和 #{}的正確使用方法,本文給大家提到了MyBatis 中 ${}和 #{}的區(qū)別,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07解決FileWriter 寫(xiě)入文本不換行的問(wèn)題
這篇文章主要介紹了解決FileWriter 寫(xiě)入文本不換行的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Idea創(chuàng)建多模塊maven聚合項(xiàng)目的實(shí)現(xiàn)
這篇文章主要介紹了Idea創(chuàng)建多模塊maven聚合項(xiàng)目的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12add方法理解ArrayList的擴(kuò)容機(jī)制
這篇文章主要為大家介紹了add方法理解ArrayList的擴(kuò)容機(jī)制示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03checkpoint 機(jī)制具體實(shí)現(xiàn)示例詳解
這篇文章主要為大家介紹了checkpoint 機(jī)制具體實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02Java實(shí)現(xiàn)AES加密算法的簡(jiǎn)單示例分享
這篇文章主要介紹了Java實(shí)現(xiàn)AES加密算法的簡(jiǎn)單示例分享,AES算法是基于對(duì)密碼值的置換和替代,需要的朋友可以參考下2016-04-04基于Java快速實(shí)現(xiàn)一個(gè)簡(jiǎn)單版的HashMap詳解
這篇文章主要為大家詳細(xì)介紹了如何利用Java簡(jiǎn)單實(shí)現(xiàn)一個(gè)底層數(shù)據(jù)結(jié)構(gòu)為數(shù)組?+?鏈表的HashMap,不考慮鏈表長(zhǎng)度超過(guò)8個(gè)時(shí)變?yōu)榧t黑樹(shù)的情況,需要的可以參考一下2023-02-02詳解Java中布隆過(guò)濾器(Bloom Filter)原理及其使用場(chǎng)景
布隆過(guò)濾器是1970年由布隆提出的,它實(shí)際上是一個(gè)很長(zhǎng)的二進(jìn)制向量和一系列隨機(jī)映射函數(shù),它的作用是檢索一個(gè)元素是否存在我們的集合之中,本文給大家詳細(xì)的講解一下布隆過(guò)濾器,感興趣的同學(xué)可以參考閱讀2023-05-05