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进行赋值操作,

这时候会调用 jdbcPreparedStatement.setXxx 方法,但是 mybatis对 PreparedStatement 做了一个拦截,

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

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

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

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

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

如何解决

如果mybatis不使用ArrayList存值是否就可以避免这个问题,并且columnNamescolumnValues这两个值仅仅在打印log的时候使用,并没有在其他地方有使用到,

columnMap 刚好起到了,就算多次调用StatementHandler.getParameterHandler().setParameters方法,但是因为Map有过滤重复key的作用,然后使用columnMap中记录的值就可以防止参数重复打印的问题,

于是我给mybatis提了一个pr,借此来修复这个问题, pr链接:https://github.com/mybatis/mybatis-3/pull/3110

我的思路很简单,去除columnNamescolumnValues,仅保留columnMap,并且将columnMap改为LinkedHashMap类型,以此保证参数的顺序,经过测试这样做并没有发现什么问题,且保证了参数的正确输出。

不过很遗憾我的pr没有被合并,被关闭了,对方给的理由是不保证在拦截器中做出的一些操作对mybatis的运行产生一些的副作用,且给出友好提示,是否有其他的方式来解决我的需求问题。

重新判断问题

我们重新思考一下这个问题,问题出现在StatementHandler.getParameterHandler().setParameters 这里,我对参数重新赋值会导致这个问题,

那么我为何要重新赋值?有没有办法不调用这个StatementHandler.getParameterHandler().setParameters 方法?

我需要重新赋值的原因是因为在拦截StatementHandlerupdatequery方法时,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,欢迎指正。

相关推荐
王小小鸭2 小时前
【开发小技巧11】用经典报表实现badge list效果,根据回显内容用颜色加以区分
oracle·oracle apex
大白要努力!2 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle
向上的车轮5 小时前
软件世界中的超级bug有哪些?
bug
Mephisto.java5 小时前
【大数据学习 | Spark】Spark的改变分区的算子
大数据·elasticsearch·oracle·spark·kafka·memcache
远歌已逝9 小时前
维护在线重做日志(二)
数据库·oracle
jokerest12313 小时前
web——sqliabs靶场——第十三关——报错注入+布尔盲注
mybatis
武子康13 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
WindFutrue16 小时前
使用Mybatis向Mysql中的插入Point类型的数据全方位解析
数据库·mysql·mybatis
AiFlutter17 小时前
Java实现简单的搜索引擎
java·搜索引擎·mybatis