MyBatis的SQL執(zhí)行結果和客戶端執(zhí)行結果不一致問題排查
最近遇到一個調試很久的問題,MyBatis 查詢 Oracle 數據庫查詢結果與在客戶端查詢結果不一致。
問題引入
測試表、測試數據
創(chuàng)建測試表、序列
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;
插入測試數據
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');
查詢數據是否插入成功

問題介紹
MyBatis xml 配置
<select id="selectByFieldType" resultType="com.scd.model.po.TestTable">
SELECT * FROM T_TEST_TABLE WHERE FIELD_TYPE = #{filedType}
</select>
運行輸出的日志
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
這里顯示輸出的結果為0條,之前有插入數據的,明顯出錯了,于是把sql語句復制到DBeaver客戶端執(zhí)行的時候,是可以查詢數據的

排查問題
日志中的SQL 和 客戶端的 SQL 一致的,唯一不同的就是日志中是使用占位符形式,也就是預編譯的SQL, 而客戶端直接執(zhí)行的SQL。首先第一步就是把xml中的預編譯SQL修改成字符串拼接的形式
<select id="selectByFieldType" resultType="com.scd.model.po.TestTable">
SELECT * FROM T_TEST_TABLE WHERE FIELD_TYPE = '${filedType}'
</select>執(zhí)行輸出的結果確實有一條,和客戶端的一致,運行日志結果如下
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注入。還需要找一下為啥 # 的形式不能得到正確的結果。
調試 MyBatis源碼
對比拼接 SQL 和 預編譯的 SQL,區(qū)別在于設置參數填充,找到 MyBatis中這一塊的執(zhí)行邏輯,經過調試,定位到設置參數的代碼在 org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters

由于xml中未指定TypeHandler,程序使用默認的 UnknownTypeHandler。根據參數的類型去匹配TypeHandler

參數 “Double” 是字符串,匹配到了 StringTypeHandler

繼續(xù)調試,發(fā)現設置參數的代碼段如下

發(fā)現整個設置數據的過程沒有啥問題呀,于是把問題簡化一下,弄成JDBC的方式執(zhí)行看看。
JDBC 執(zhí)行 SQL
按照 MyBtatis 的執(zhí)行過程,把代碼簡化成如下
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 {
// 加載驅動
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();
}
}
}
運行測試用例,發(fā)現使用JDBC也無法獲取到正確的結果。于是打算看看Oracle的SQL執(zhí)行日志,看預編譯的SQL與直接拼接的SQL有啥區(qū)別
select * from v$sql WHERE SQL_TEXT LIKE '%T_TEST_TABLE%' ORDER BY FIRST_LOAD_TIME DESC;

根據運行的時間點,找到預編譯的 SQL為
SELECT * FROM T_TEST_TABLE WHERE FIELD_TYPE = :1
直接拼接的SQL為
SELECT * FROM T_TEST_TABLE WHERE FIELD_TYPE = 'Double'
預編譯的SQL在客戶端中運行也沒問題

說明代碼沒問題,是不是字段里面有空格導致查詢不到的呢?于是把字段直接復制出來。復制出來果然發(fā)現了空格

但是運行這個也不對。這個時候思考著為啥有空格呢?于是百度看了一下CHAR字段的介紹,原來Oracle的 CHAR字段長度不夠的用空格填充,輸出上面的字符長度看看

解決問題
由于Oracle的CHAR類型在插入的數據長度不夠的情況下會用空格填充,于是把字段類型修改成 VARCHAR2
ALTER TABLE T_TEST_TABLE MODIFY "FIELD_TYPE" VARCHAR2(20)
把之前的數據空格清除
UPDATE T_TEST_TABLE SET FIELD_TYPE = TRIM(FIELD_TYPE)
再次執(zhí)行 MyBatis 的測試方法,可以發(fā)現查出數據

待解決問題
為啥用JDBC 預編譯 SQL 查詢 CHAR 類型的字段會有空格匹配問題?
到此這篇關于MyBatis的SQL執(zhí)行結果和客戶端執(zhí)行結果不一致問題排查的文章就介紹到這了,更多相關MyBatis SQL和客戶端執(zhí)行結果不一致內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
MyBatis 中 ${}和 #{}的正確使用方法(千萬不要亂用)
這篇文章主要介紹了MyBatis 中 ${}和 #{}的正確使用方法,本文給大家提到了MyBatis 中 ${}和 #{}的區(qū)別,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07
詳解Java中布隆過濾器(Bloom Filter)原理及其使用場景
布隆過濾器是1970年由布隆提出的,它實際上是一個很長的二進制向量和一系列隨機映射函數,它的作用是檢索一個元素是否存在我們的集合之中,本文給大家詳細的講解一下布隆過濾器,感興趣的同學可以參考閱讀2023-05-05

