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

 更新时间:2023年04月10日 09:24:52   作者:modelmd  
本文主要介绍了MyBatis的SQL执行结果和客户端执行结果不一致问题排查,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

最近遇到一个调试很久的问题,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 类型的字段会有空格匹配问题?

到此这篇关于MyBatis的SQL执行结果和客户端执行结果不一致问题排查的文章就介绍到这了,更多相关MyBatis SQL和客户端执行结果不一致内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java获取本机IP地址的三种方法总结

    Java获取本机IP地址的三种方法总结

    这篇文章主要为大家详细介绍了java获取本机IP地址的三种方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04
  • gateway与spring-boot-starter-web冲突问题的解决

    gateway与spring-boot-starter-web冲突问题的解决

    这篇文章主要介绍了gateway与spring-boot-starter-web冲突问题的解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • SpringBoot结合Mybatis实现创建数据库表的方法

    SpringBoot结合Mybatis实现创建数据库表的方法

    本文主要介绍了SpringBoot结合Mybatis实现创建数据库表的方法,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • SpringBoot上传图片与视频不显示问题的解决方案

    SpringBoot上传图片与视频不显示问题的解决方案

    这篇文章主要介绍了关于springboot上传图片与视频不显示问题,最近做毕设时候需要上传视频的图片与视频,但是每次都需要重启前端才能展示出此图片,所以本文给大家介绍了SpringBoot上传图片与视频不显示问题的解决方案,需要的朋友可以参考下
    2024-03-03
  • springboot使用Mybatis-plus分页插件的案例详解

    springboot使用Mybatis-plus分页插件的案例详解

    这篇文章主要介绍了springboot使用Mybatis-plus分页插件的相关知识,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-05-05
  • SpringBoot自定义Redis实现缓存序列化详解

    SpringBoot自定义Redis实现缓存序列化详解

    Spring提供了一个RedisTemplate来进行对Redis的操作,但是RedisTemplate默认配置的是使用Java本机序列化。如果要对对象操作,就不是那么的方便。所以本文为大家介绍了另一种SpringBoot结合Redis实现序列化的方法,需要的可以参考一下
    2022-07-07
  • Java 天生就是多线程

    Java 天生就是多线程

    这篇文章主要介绍了Java天生就是多线程,程序天生就是多线程程序,因为执行main()方法的是一个名称为main的线程,更多相关内容需要的小伙伴可以参考一下
    2022-07-07
  • java 中 poi解析Excel文件版本问题解决办法

    java 中 poi解析Excel文件版本问题解决办法

    这篇文章主要介绍了java 中 poi解析Excel文件版本问题解决办法的相关资料,需要的朋友可以参考下
    2017-08-08
  • MyBatis版本升级导致OffsetDateTime入参解析异常问题复盘

    MyBatis版本升级导致OffsetDateTime入参解析异常问题复盘

    这篇文章主要介绍了MyBatis版本升级导致OffsetDateTime入参解析异常问题复盘,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • MybatisPlus之likeRight的用法

    MybatisPlus之likeRight的用法

    这篇文章主要介绍了MybatisPlus之likeRight的用法说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06

最新评论