MyBatis 参数重复打印的bug
现象
最近有个需求,需要在mybatis对数据库进行写入操作的时候,根据条件对对象中的某个值进行置空,然后再进行写入,这样数据库中的值就会为空了。
根据网上查看的资料,选择在 StatementHandler 类执行 update 的时候进行对参数的拦截,
修改参数完毕后,再调用 statementHandler.getParameterHandler().setParameters 方法将修改后的值重新 set 进去,
此时出现了问题,发现mybatis在控制台log中打印出来的参数竟然多了一份,下面是临摹的情况,没有实际用update ,而是拦截的query方法,简化了一下逻辑:
这里我调用了3次 setParameters ,加上mybatis本身的一次,输出了4个 Parameters,

实际上我的xml中只接收了一个参数,并且虽然log多打印了一些参数,实际对我的结果并无影响。

为什么会发生这个问题
问题就出在 StatementHandler.getParameterHandler().setParameters 这个方法上,这里是对本次数据库操作的参数进行赋值,
这过程中有一个 typeHandler.setParameter(ps, i + 1, value, jdbcType) 操作,

这里会根据相应的类型处理器,进行赋值,这个过程如下,typeHandler进行赋值操作,
这时候会调用 jdbc 的 PreparedStatement.setXxx 方法,但是 mybatis对 PreparedStatement 做了一个拦截,

这个拦截就是日志记录类 PreparedStatementLogger ,当调用setXxx方法时,

Logger类会调用 setColumn 方法,这个setColumn方法就是记录本次入参情况,

最终调用 PreparedStatement.executeXXX方法时,本次代理将会根据条件决定要不要打印出参数等log,

而这个参数log,就是前面我们提到的setColumn方法中存入的 columnValues 属性,

可以看到,这个属性使用的是 ArrayList ,这也就说明了为什么会重复打印参数log出来,这意味着当你多次调用 StatementHandler.getParameterHandler().setParameters 这个方法时,columnValues 不会清除之前记录的参数,并且继续保存你这次重新set的参数进来。

如何解决
如果mybatis不使用ArrayList存值是否就可以避免这个问题,并且columnNames和columnValues这两个值仅仅在打印log的时候使用,并没有在其他地方有使用到,
而columnMap 刚好起到了,就算多次调用StatementHandler.getParameterHandler().setParameters方法,但是因为Map有过滤重复key的作用,然后使用columnMap中记录的值就可以防止参数重复打印的问题,


于是我给mybatis提了一个pr,借此来修复这个问题, pr链接:https://github.com/mybatis/mybatis-3/pull/3110
我的思路很简单,去除columnNames和columnValues,仅保留columnMap,并且将columnMap改为LinkedHashMap类型,以此保证参数的顺序,经过测试这样做并没有发现什么问题,且保证了参数的正确输出。

不过很遗憾我的pr没有被合并,被关闭了,对方给的理由是不保证在拦截器中做出的一些操作对mybatis的运行产生一些的副作用,且给出友好提示,是否有其他的方式来解决我的需求问题。
重新判断问题
我们重新思考一下这个问题,问题出现在StatementHandler.getParameterHandler().setParameters 这里,我对参数重新赋值会导致这个问题,
那么我为何要重新赋值?有没有办法不调用这个StatementHandler.getParameterHandler().setParameters 方法?
我需要重新赋值的原因是因为在拦截StatementHandler的update或query方法时,mybatis自身已经调用过setParameters方法,
此时如果我不重新调用一下,单纯的修改parameterObject 自身,那么PreparedStatement.executeXXX设置的参数其实还是上次的,并不会因为我修改了parameterObject 而变化,
所以根据提示,我们有没有办法在mybatis自身执行setParameters前进行对parameterObject 的修改,这样我们在执行过程中,由mybatis来做这个赋值的事情,log就只会打印一次了。
最终解决
那么mybatis是何时自己进行setParameters的?答案在StatementHandler.parameterize中,
是的,它会在update或者query方法执行前,对参数进行处理,所以我们应当拦截这一步的操作,在这一步对参数进行处理,

修改后的拦截器,可以看到这里我们对StatementHandler.parameterize方法进行拦截处理,并且修改参数,此时log中输出的就是我们最后一次修改的参数。

当然mybatis还提供了其他的拦截点,例如不拦截StatementHandler类,我们直接到源头ParameterHandler.setParameters,拦截设置参数方法,
在这里,我们修改他的参数也是可以的,如下图:

最终结论
可以看到,我们只要不在拦截器中调用setParameters方法,就不会触发log的重复打印,因为mybatis的log记录类,使用ArrayList记录每次的setXX入参, 因此选好时机做相应的处理,就不会出现问题,在合适的拦截点做相应的事情,
MyBatis的参数记录可能也没有考虑过重复调用的问题,或者也许有其他的考量,总之我们了解这个问题的原因,并且做相应的规避即可。
演示与复现问题的demo都在:https://github.com/qiaomengnan16/mybatis-log-bug,欢迎指正。
相关文章:
MyBatis 参数重复打印的bug
现象 最近有个需求,需要在mybatis对数据库进行写入操作的时候,根据条件对对象中的某个值进行置空,然后再进行写入,这样数据库中的值就会为空了。 根据网上查看的资料,选择在 StatementHandler 类执行 update 的时候进…...
ES6学习之路:迭代器Iterator和生成器Generator
迭代器 一、知识背景 什么是迭代器 迭代器就是在一个数据集合中不断取出数据的过程迭代和遍历的区别 遍历是把所有数据都取出迭代器注重的是依次取出数据,它不会在意有多少数据,也不会保证能够取出多少或者能够把数据都取完。比如斐波那契额数列&#…...
如何使用 DynamiCrafter Interp Loop 无缝连接两张照片
DynamiCrafter Interp Loop 是一个基于 AI 的工具,可以用来无缝连接两张照片。它使用深度学习技术来生成中间帧,从而使两张照片之间的过渡更加自然流畅。 使用步骤 访问 DynamiCrafter Interp Loop 网站:https://huggingface.co/spaces/Dou…...
今天起,Windows可以一键召唤GPT-4了
ChatGPT狂飙160天,世界已经不是之前的样子。 新建了人工智能中文站https://ai.weoknow.com 每天给大家更新可用的国内可用chatGPT资源 发布在https://it.weoknow.com 更多资源欢迎关注 微软 AI 大计的最后一块拼图完成了? 把 Copilot 按钮放在 Window…...
使用Kaggle API快速下载Kaggle数据集
前言 在使用Kaggle网站下载数据集时,直接在网页上点击下载可能会很慢,甚至会出现下载失败的情况。本文将介绍如何使用Kaggle API快速下载数据集。 具体步骤 安装Kaggle API包 在终端中输入以下命令来安装Kaggle API相关的包: pip install…...
java 通过 microsoft graph 调用outlook(二)
这次提供一些基础调用方式API PS: getMailFolders 接口返回的属性中,包含了未读邮件数量unreadItemCount 一 POM文件 <!-- office 365 --><dependency><groupId>com.google.guava</groupId><artifactId>guava<…...
【机器学习】代价函数
🎈个人主页:豌豆射手^ 🎉欢迎 👍点赞✍评论⭐收藏 🤗收录专栏:机器学习 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进…...
[leetcode] 100. 相同的树
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 示例 1: 输入:p [1,2,3], q [1,2,3] 输出:true示例 2&a…...
08、Lua 函数
Lua 函数 Lua 函数Lua函数主要有两种用途函数定义解析:optional_function_scopefunction_nameargument1, argument2, argument3..., argumentnfunction_bodyresult_params_comma_separated 范例 : 定义一个函数 max()Lua 中函数可以作为参数传递给函数多返回值Lua函…...
【数据分析面试】1. 计算年度收入百分比(SQL)
题目 你需要为公司的营收来源生成一份年度报告。计算截止目前为止,在表格中记录的第一年和最后一年所创造的总收入百分比。将百分比四舍五入到两位小数。 示例: 输入: annual_payments 表 列名类型amountINTEGERcreated_atDATETIMEstatusV…...
数据库SQL语句速查手册
SQL 语句语法AND / ORSELECT column_name(s) FROM table_name WHERE condition AND|OR conditionALTER TABLEALTER TABLE table_name ADD column_name datatypeorALTER TABLE table_name DROP COLUMN column_nameAS (alias)SELECT column_name AS column_alias FROM table_name…...
智慧城市一屏统览,数字孪生综合治理
现代城市作为一个复杂系统,牵一发而动全身,城市化进程中产生新的矛盾和社会问题都会影响整个城市系统的正常运转。智慧城市是应对这些问题的策略之一。城市工作要树立系统思维,从构成城市诸多要素、结构、功能等方面入手,系统推进…...
Python读取PDF文字转txt,解决分栏识别问题,能读两栏
搜索了一下,大致有这些库能将PDF转txt 1. PyPDF/PyPDF2(截止2024.03.28这两个已经合并成了一个)pypdf PyPI 2. pdfplumber GitHub - jsvine/pdfplumber: Plumb a PDF for detailed information about each char, rectangle, line, et cete…...
微信支付平台与微信服务号关联配置要点
目录 JSAPI支付 前期资料及相关准备 申请微信服务号 服务号配置要点 微信认证 基本配置 功能设置 申请微信支付号 支付号配置要点 设置操作密码 API安全 开发设置 与服务号关联 小结 JSAPI支付 我们的开发应用场景以JSAPI支付为举例,这也是常用的一…...
C++类复习
C类 1. 类内成员函数隐式声明为inline class Str {int x;int y 3; public:inline void fun(){std::cout<<"pf,yes!"<<std::endl;} };这段代码不会报错,但是类内的成员函数隐式声明为inline函数,不需要单独写在前面。因此将成员…...
Spring使用(一)注解
Spring使用 资源 Spring 框架内部使用 Resource 接口作为所有资源的抽象和访问接口,在上一篇文章的示例代码中的配置文件是通过ClassPathResource 进行封装的,ClassPathResource 是 Resource 的一个特定类型的实现,代表的是位于 classpath …...
Linux基本指令篇
在前边,我们已经了解过了Linux操作系统的发展和应用,从该篇起,就正式进入对Linux的学习。 今天我们就来在Xshell上远程登录我们的云服务器。首先我们要知道自己云服务器的公网ip,然后修改一下密码。 点击跳转 修改完密码之后我们…...
CSS实现小车旅行动画实现
小车旅行动画实现 效果展示 CSS 知识点 灵活使用 background 属性下的 repeating-linear-gradient 实现路面效果灵活运用 animation 属性与 transform 实现小车和其他元素的动画效果 动画场景分析 从效果图可以看出需要实现此动画的话,需要position属性控制元素…...
6_相机坐标系_相机4个坐标系详述
相机系列文章是用来记录使用opencv3来完成单目相机和6轴机械臂手眼标定。本人吃饭的主职是linux下6轴机械臂相关应用开发。但对于机械臂运动学、相机应用等都非常感兴趣,所以对一些线性代数基础薄弱又想深入了解机械臂内部运算的同志比较有体会。由于是探索性学习&a…...
软考 - 系统架构设计师 - 敏捷开发方法
前言 敏捷开发方法是一种以人为核心、迭代、循序渐进的软件开发方法。它强调团队合作、客户需求和适应变化,旨在通过快速迭代和反馈来快速交付高质量的软件产品。 敏捷开发方法的优势在于能够快速响应变化、提高开发效率和质量、增强团队协作和沟通,并降…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...
C# 表达式和运算符(求值顺序)
求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如,已知表达式3*52,依照子表达式的求值顺序,有两种可能的结果,如图9-3所示。 如果乘法先执行,结果是17。如果5…...
Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...
