MyBatis的SQL执行结果和客户端执行结果不一致问题排查
作者:modelmd 发布时间:2022-12-30 20:37:22
最近遇到一个调试很久的问题,MyBatis 查询 Oracle 数据库查询结果与在客户端查询结果不一致。
问题引入
测试表、测试数据
创建测试表、序列
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客户端执行的时候,是可以查询数据的
排查问题
日志中的SQL 和 客户端的 SQL 一致的,唯一不同的就是日志中是使用占位符形式,也就是预编译的SQL, 而客户端直接执行的SQL。首先第一步就是把xml中的预编译SQL修改成字符串拼接的形式
<select id="selectByFieldType" resultType="com.scd.model.po.TestTable">
SELECT * FROM T_TEST_TABLE WHERE FIELD_TYPE = '${filedType}'
</select>
执行输出的结果确实有一条,和客户端的一致,运行日志结果如下
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
用这种方式虽然可以解决,但是很多编码规范在 xml 中不允许配置 $,防止SQL注入。还需要找一下为啥 # 的形式不能得到正确的结果。
调试 MyBatis源码
对比拼接 SQL 和 预编译的 SQL,区别在于设置参数填充,找到 MyBatis中这一块的执行逻辑,经过调试,定位到设置参数的代码在 org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters
由于xml中未指定TypeHandler,程序使用默认的 UnknownTypeHandler。根据参数的类型去匹配TypeHandler
参数 “Double” 是字符串,匹配到了 StringTypeHandler
继续调试,发现设置参数的代码段如下
发现整个设置数据的过程没有啥问题呀,于是把问题简化一下,弄成JDBC的方式执行看看。
JDBC 执行 SQL
按照 MyBtatis 的执行过程,把代码简化成如下
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();
}
}
}
运行测试用例,发现使用JDBC也无法获取到正确的结果。于是打算看看Oracle的SQL执行日志,看预编译的SQL与直接拼接的SQL有啥区别
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在客户端中运行也没问题
说明代码没问题,是不是字段里面有空格导致查询不到的呢?于是把字段直接复制出来。复制出来果然发现了空格
但是运行这个也不对。这个时候思考着为啥有空格呢?于是百度看了一下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)
再次执行 MyBatis 的测试方法,可以发现查出数据
待解决问题
为啥用JDBC 预编译 SQL 查询 CHAR 类型的字段会有空格匹配问题?
来源:https://blog.csdn.net/modelmd/article/details/128460956


猜你喜欢
- 对于Android中的手势识别可以从以下三个Listener入手——OnTouchListener、OnGestureListener、On
- 在做项目时,需要一个定时任务来接收数据存入数据库,后端再写一个接口来提供该该数据的最新的那一条。数据保持最新:设计字段sign的值(0,1)
- 1、题目给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。示例:提示:你可以假设 k 总是有效的,在输入数组不为空
- 在WinForm程序中,实现TextBox文本输入框占位符的方式也很多,最常用的是方式基于Windows Api SendMessage函数
- 使用Spring data JPA开发已经有一段时间了,这期间学习了一些东西,也遇到了一些问题,在这里和大家分享一下。前言:Spring d
- 下文笔者讲述StringTokenizer对象的简介说明,如下所示StringTokenizer的简介Java StringTokenize
- 本文实例讲述了C#自定义处理xml数据类。分享给大家供大家参考。具体分析如下:这个C#类专门用户处理xml数据,可以大大简化xml的操作,类
- 使用Spring3 实现用户登录以及权限认证这里我就简单介绍一下,我在实现的时候处理的一些主要的实现。1.用户登录 <form act
- a.在.xaml文件中拖入一个datagrid,然后添加列名,使用Binding="{Binding 数据库中的列名称}"
- 需求:类似医院或者商场,大屏幕无限轮播item (广告词/广告图…),供大家参考,具体内容如下代码如下/** * Created by Xi
- Mybatis判断空字符串先说结论:如果使用正确,是不会产生任何问题的。大家各种疑惑,全是使用不当产生的。先说正确的使用方式一般判空的方式就
- /// <summary> /// 遍历Co
- 1. 概述官方JavaDocsApi: java.awt.FlowLayoutFlowLayout,流式布局管理器。按水平方向依次排列放置组
- route_generator是什么这是一个简单的 Flutter 路由生成库,只需要少量的代码,然后利用注解配合源代码生成,自动生成路由表
- 先看进度条的效果:具体实现:新建类,继承自View,在onDraw中进行绘制:import android.content.Context;
- CollectionUtils.isNotEmpty()不存在问题org.apache.commons.collections.Collec
- For-Each循环For-Each循环也叫增强型的for循环,或者叫foreach循环。For-Each循环是JDK5.0的新特性(其他新
- Spark Streaming算子开发实例transform算子开发transform操作应用在DStream上时,可以用于执行任意的RDD
- Java多态对象的类型转换这里所说的对象类型转换,是指存在继承关系的对象,不是任意类型的对象。当对不存在继承关系的对象进行强制类型转换时,j
- 本文实例讲述了C#实现输入10个数存入到数组中并求max和min及平均数的方法。分享给大家供大家参考,具体如下:using System;u