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

V.PhyloMaker实战:从物种列表到维管束植物系统发育树的完整流程

本文详细介绍了使用V.PhyloMaker工具包,从物种列表快速构建维管束植物系统发育树的完整实战流程。内容涵盖R包安装、物种名标准化、核心函数phylo.maker的三种构建方案选择、结果树的可视化与导出,以及与picante包的联动分析,为生态学和生物地理学研究提供了高效解决方案。

weixin_29057237的博客 379

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

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

HaHa_Sir的博客 8788

从仿真到器件:铌酸锂电光调制器Comsol建模避坑指南

本文详细介绍了铌酸锂电光调制器在Comsol中的建模全流程,涵盖晶体切向选择、材料参数配置、多物理场耦合等关键技术。通过实战案例和调试技巧,帮助工程师避开常见建模陷阱,提升仿真精度与效率,特别适用于高速光通信系统的器件设计与优化。

m2n3b4v5c6的博客 420

ST-LINK调试STM32H743必看:CubeMX工程中缺失的这行代码导致程序跑飞

本文深入剖析了使用ST-LINK调试STM32H743时,因CubeMX工程中缺失电源电压档位配置代码而导致程序跑飞或出现“Can not access memory”错误的问题。文章解释了H7系列电源与时钟耦合的核心原理,提供了具体的代码修复方案和系统化的调试排查思路,帮助开发者从根本上解决这一常见调试困境。

weixin_29191669的博客 281

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

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

Coder-文小白的博客 9992

【Codex安装使用手册】

Codex安装部署使用教程

qq_43392230的博客 4980

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

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

花木凋零成兰的博客 1005

别再手动建模了!用Simscape Multibody Link一键导入SolidWorks装配体到Simulink

本文介绍了如何利用Simscape Multibody Link插件将SolidWorks装配体一键导入Simulink,实现CAD到仿真的无缝衔接。该技术显著提升机械系统设计与控制效率,特别适用于工业机器人、车辆悬架等复杂机构的动力学分析。文章详细解析了插件安装、模型导出优化及仿真验证的全流程,帮助工程师摆脱手动建模的低效工作模式。

weixin_27256197的博客 463

微信公众平台 - 微信内置浏览器 WeixinJSBridge.invoke(‘xxx‘) API 报错:system:access_denied(常见于微信支付、分享朋友圈/转发好友等功能)解决方案

微信支付提示System:access_denied,调用微信支付返回:system:access_denied为什么,安卓版微信支付偶尔出现system:access_denied,微信公众号开发分享出现system:access_denied,system:access_denied如何解决?微信WeixinJSBridge.invoke(‘xxx‘)API调用后出现报错,微信内部H5WeixinJSBridge.invokesystem:access_denied,使用微信jssdk时报ac

🏆 前端领域优质创作者 5145

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

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

qq_36189144的博客 1087

《一》:LBE,VR 大空间系列——场景环境需求解析

当前,大量 VR 体验活动在室内场地开展。因此,对于场地照明有特定要求:除天花板外,场地的所有表面均需有照明覆盖。这是为了保证玩家无论处于场地的哪个位置,地面与墙面都不会出现明显的影子。场地内不建议安装射灯或高强度的点光源,若已安装此类灯具,需使用柔光板进行遮盖。此外,要避免在阳光下使用 VR 设备台。参考图(请忽略其中Maker标签)

unitygroup的博客 944

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

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

cqt3589的博客 381

SAP-ABAP:SAP系统调优利器:RZ11动态参数修改全指南

SAP系统调优利器:RZ11动态参数修改全指南

baidu_35680696的博客 970

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

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

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

weixin_74268571的博客 7541

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

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

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

星月昭铭的博客 4888

pscad IEEE标准系统搭建说明

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

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

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、付费专栏及课程。

余额充值