MyBatis的SQL执行结果和客户端执行结果不一致问题排查

Mybatis控制台打印SQL执行信息(执行方法、执行SQL、执行时间) SQL性能监控是一个程序必要的功能,通常我们可以使用数据库自带的客户端工具进行SQL性能分析。然而对于一些专业度不高的人员来说,当程序出现卡顿或者响应速度变慢时,排查问题变得困难。当程序出现卡顿,通常通过检查服务器磁盘使用情况、程序内存大小,网络带宽以及数据库I/O等方面进行问题排查。然而数据库I/O打高的情况通常是由于SQL执行效率过低导致的。一般项目制的公司都有属于自己的实施人员,然而要让实施人员去排查具体SQL执行过慢问题,这显然对于专业度不高的工作人员来说是一种挑战和煎熬。 阅读详情
AI摘要

本文探讨了MyBatis查询Oracle数据库时,CHAR字段查询结果不一致的问题。通过逐步排查,从修改SQL预编译方式到调试MyBatis源码,最终定位问题并给出解决方案。

最近遇到一个调试很久的问题,MyBatis 查询 Oracle 数据库查询结果与在客户端查询结果不一致。

问题引入

测试表、测试数据

  1. 创建测试表、序列
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;
  1. 插入测试数据
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');
  1. 查询数据是否插入成功
    在这里插入图片描述

问题介绍

  1. MyBatis xml 配置
<select id="selectByFieldType" resultType="com.scd.model.po.TestTable">
        SELECT * FROM T_TEST_TABLE WHERE FIELD_TYPE = #{filedType}
</select>
  1. 运行输出的日志
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 类型的字段会有空格匹配问题?

Mybatis-Plus“读-批量写-读”数据不一致的问题分享 作者:十年培训经验的菜包原文链接:https://juejin.cn/post/7185206630630293562。 阅读详情

相关推荐

Java中的Date方法与系统时间不一致

为了解决这个问题,我们可以使用新的日期和时间API(如java.time包中的类)来获取当前系统时间,并输出该时间。另外,如果仍然需要使用Date类,可以通过SimpleDateFormat类来格式化Date对象,以确保输出的时间与系统时间一致。这就可能导致输出的时间与实际系统时间不一致。然而,有时我们可能会遇到一个问题,就是使用Date类输出的时间与系统时间不一致。当我们使用Date类的toString()方法来输出日期和时间时,有时会发现输出的时间与实际系统时间不一致。如果你还有其他问题,请随时提问。

DevProZ的博客 1083

数据库信息与通过jdbc连接查询的数据不一致问题

只需将Java的实体类属性名与数据库中的属性名改为一样即可,否则它会返回该属性类型的默认值

Mr_GZB的博客 1743

基于Arduino 的 FM 调频收音机

这是一个基于 Arduino 的开源的收音机项目,它具备一台标准收音机的全部功能,从控制部分到外壳都可以自己 DIY。在项目中,我们将介绍让 FM 收音机调谐器的 RDA5807 模块与 I2C 协议进行通信的方法,整个过程只需要两根线就可以与 IC 通信,接线非常简单。3D 打印的外壳也非常简洁。总之,是个非常简单和实用的项目,来试试吧!

ByteBox的博客 1642

MyBatis 查出数据不一致 MyBatis返回数据和数据库查询不一致

1、出现MyBatis返回数据比 实际sql查询返回的数据要少的情况,一般可能是resultMap 标签配置错误2、一对一的关联查询,可以不用标签进行关联查询, 可以直接对象名.属性的方式进行配置,如:user.id,可以避免出现 MyBatis返回数据比,sql查询返回的少。-- 对象名.属性, 避免使用 标签 -->2.2、一对一的关联查询,可以不用SELECT3、一对一的关联查询,别用进行映射关系维护的,反正不是我用的。

HaHa_Sir的博客 8788

CloudCompare实战:5分钟搞定点云数据预处理(含常见格式转换技巧)

本文提供了一套基于CloudCompare的5分钟点云数据预处理实战指南。文章详细讲解了如何利用这款开源工具,快速完成点云数据的格式转换(如LAS转PLY)、统计离群值降噪、体素网格下采样以及交互式裁剪等核心操作,帮助用户高效清理和规整杂乱数据,为后续分析建模奠定基础。

weixin_29207533的博客 477

1688店铺所有商品API接口(整店商品查询API接口)

1688店铺所有商品API接口、1688店铺列表API接口,1688店铺API接口,1688店铺查询商品API接口,1688店铺商品API接口,1688店铺详情接口,1688商品API接口

tbprice的博客 1569

同一条SQL语句,MyBatis 查询结果与 MySQL 执行结果居然不一致!

一、前言 不知道大家平时在开发过程中有没有遇到这样的问题,同一条SQL语句,MyBatis 查询结果与 MySQL 执行结果居然不一致,具体说应该是MyBatis 查询结果比MySQL 执行结果的数据更少。不要不相信,如果不注意,这样的坑,你还真有可能会踩的。 二、代码演示 下面用代码简单演示一下: 创建两张表用于测试用: teacher表: CREATE TABLE `teacher` ( `id` int(11) NOT NULL, `name` varchar(255) CHARAC

Coder-文小白的博客 9992

事件相机数据处理实战:从原始事件流到视觉表征的三种核心方法

本文深入探讨了事件相机数据处理的核心环节,即如何将异步、稀疏的原始事件流转换为深度学习模型可处理的视觉表征。文章重点解析了三种核心方法:逐通道累积、体素网格双线性插值和对称累积事件表示,详细阐述了其原理、代码实现、可视化效果及各自的适用场景,为事件相机在目标识别、光流估计等视觉任务中的高效应用提供了实战指南。

read5的博客 439

MyBatis的SQL执行结果和客户端相同SQL执行结果不一致问题

本次能及时发现问题的原因, 是因为接口是 Get 接口, 本身参数不多, 也有先例可以参考;在 Debug 的过程中, 使用笨方法, 一个一个参数点击开来是存在发现问题的可能性的;可是不排除参数极多的情况, 在这种情况下, 难道一个一个参数进行点击吗?尝试借助文本工具软件, 查看是否能显现隐藏字符, 采用软件有 Notepad++ 和 Windows自带的记事本, 结果如下:可以发现Windows的记事本显现了隐藏字符, 可以很容易看到奇怪的地方, 此外也可以尝试借助项目中的文件;

花木凋零成兰的博客 1005

Solidworks在Windows 11上安装失败?这里有解决方案

通过上述步骤,你应该能够解决在Windows 11上安装Solidworks时遇到的问题。这个方法通过调整注册表的设置,提高了安装程序的权限,从而有效避免了安装过程中的权限障碍。当然,每个系统的具体情况可能有所不同,如果在尝试上述方法后仍然无法解决问题,建议联系Solidworks的技术支持团队,获取更专业的帮助。希望本文能够对你有所帮助,祝你工作顺利,设计无忧!

nntxthml的博客 4912

Electron多窗口开发避坑指南:Pinia状态同步的3种解决方案对比

本文深入探讨了在Electron多窗口应用中实现Pinia状态同步的三种核心方案。针对Electron多窗口架构下状态隔离的挑战,文章对比了基础事件通信、共享存储以及智能Pinia插件等方案的优劣与适用场景,并提供了生产级的实现指南与选型建议,帮助开发者构建健壮高效的桌面应用。

spice的博客 479

不敢相信,相同 SQL 下 Mybatis 查询结果和数据库竟然不一样!

点击上方“阿拉奇学Java”,选择“置顶或者星标”每天早晨00点00分,与你相约!来源 |jianshu.com/p/7c569ca6852e往日回顾:写出我的第一个框架:迷你版Sp...

qq_36189144的博客 1087

AD快速上手笔记

本文详细介绍了使用Altium Designer从零创建PCB项目全流程:新建工程、绘制原理图(元件放置、连线、电源端口)、编译检查、生成PCB、布局布线(含铺铜)、DRC检查及输出Gerber与钻孔文件。重点强调了库选择、参数修改、层切换、铺铜设置和生产文件导出规范,确保设计无误,便于顺利下单生产。

qq_53529450的博客 42

MyBatis的SQL执行结果和客户端执行结果不一致

因为我的用到了时间排序,nvl('2023-7-20',sysdate-365),通过程序执行发现与navicat客户端不一致,最后打印nvl('2023-7-20',sysdate-365)发现是mybatis执行结果是。

cqt3589的博客 381

MATLAB - 机械臂手眼标定(眼在手内) - 估计安装在机器人上的移动相机的姿态

执行手眼校准有助于操作配备末端执行器(简称 “手”)的机械臂,该末端执行器依赖于摄像头提供的视觉数据。一旦完成了眼在手外的校准,机械臂就能准确地移动到摄像头识别的特定像素位置。这种能力是执行精确拾放任务(如分类、堆垛和分拣)的基础,即使在摄像机的确切位置和方向未知的情况下也是如此。机器人-摄像机系统有两种构型:眼在手内和眼在手外。在眼在手内构型中,摄像头直接安装在机器人手臂的末端执行器上。相反,在眼在手的构型中,摄像头固定在一个静止的物体上,机器人手臂在其视野内。

机器人控制与仿真应用 3352

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

MyBatis-Plus 查询不到数据,但使用 SQL 可以查询到数据的问题排查

MyBatis-Plus 查询不到数据,但使用 SQL 可以查询到数据的问题详细排查和解决方案

weixin_74268571的博客 7541

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

SpringBoot项目中mybatis执行sql很慢的排查改造过程(Interceptor插件、fetchSize、隐式转换等)

刚入职公司,就发现公司项目跑sql特别慢,差不多一万条数据插入到数据库要5秒以上(没有听错,就是这个速度),查询修改删除也是特别慢。直到22年年底实在是受不了了,我就去排查了一下。用的是Oracle数据库,mybatis、mybatis plus,其中mybatis是引入的平台的依赖。平台封装了一些工具和插件。

星月昭铭的博客 4888

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

Spring、MyBatis、Druid、MySQL不使用事务执行SQL语句分析

使用MySQL数据库时,使用事务与不使用事务相比,出现问题时排查更复杂。不使用事务时,客户端只需要请求MySQL服务一次(只考虑显式执行的SQL语句);使用事务时,客户端至少需要请求MySQL服务四次(开启事务、执行SQL语句、提交/回滚事务、恢复自动提交)。在Java中存在一些用法会导致事务失效,有的问题比较明显可以较快定位,有的问题隐藏较深可能需要较长时间排查。因此需要对MySQL的事务执行原理进行分析,并整理用于排查事务相关问题的快速有效的方法。在Java应用中访问MySQL服务时,涉及Java应用、

adrninistrat0r的博客 2025

MyBatis SQL执行报错排查与解决方案

在ORM框架使用过程中,SQL语句的正确执行是数据访问层的关键。MyBatis作为Java生态中广泛使用的持久层框架,其SQL执行流程包含XML解析、动态SQL处理、参数绑定等多个阶段。参数绑定阶段是最常见的错误发生点,涉及属性名匹配、类型转换等核心机制。通过日志调试和SQL拦截器可以快速定位问题,而统一命名规范、参数校验等最佳实践能有效预防错误。本文针对MyBatis中SQL语法正确但执行报错的典型场景,深入分析参数映射不匹配、动态SQL解析等常见问题,提供从基础排查到高级解决方案的完整指南。

weixin_30402231的博客 211
上一篇: Oracle时间函数
下一篇: SpringBoot - thymeleaf 动态填充 html 页面数据
Chengdu.S
博客等级 码龄9年 284粉丝 86原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Chengdu.S

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值