最新的Yusi主题上线啦!

mysql-jdbc in参数化

java lzxianren 2466℃ 0评论

问题由来

开发中使用了jfinal框架,其关于数据库的操作是在jdbc上进行了简单的封装,支持ORM和基本的事务(这部分也是ActiveRecord思想的实现,在jf中叫Arp)。

业务中有个需求,需要查询某张表中是某些id的行,id由前台传过来,dao写了一条sql, select * from xx where id in (?) ,然后用 Db.find(sql,ids) ,其中ids 的类型是Set<String>。

验证后,发现并没有按照预期返回相应的行记录。

产生原因分析

通过阅读Db、DbPro的源码,发现find最后其实只是调用了原生的mysql-connect中的PreparedStatement.    具体源码如下:

关键在上述标黄的两行代码,再进一步看fillStatement方法,我们可以看到,代码更简单了,就是调用的PreparedStatementsetObject方法。

再进一步看setObject方法。由于PreparedStatement是个接口,查看其实现会有很多,比如druid的、c3p0等,但最后都是到了com.mysql.jdbc包下面的PreparedStatement类里面。具体源码如下:

从上述代码中可以看到,就是一堆if/else判断类型然后再根据不同的类型进行处理。我们发现没有Set 或者List这样的集合处理,而是统一处理为setSerializableObject。继续看源码:

关键的代码是13、14两行加黄色的部分,调用了ObjectOutputStream 这个类进行序列化,然后设置参数的类型为BINARY。这个类我们就不再通过源码具体分析了,网上搜一下,就可以得到相关内容。

Java中ObjectInputStream 与 ObjectOutputStream这两个包装类可用于输入流中读取对象类数据和将对象类型的数据写入到底层输入流 。ObjectInputStream 与 ObjectOutputStream 类所读写的对象必须实现了 Serializable 接口。需要注意的是:对象中的 transientstatic 类型的成员变量不会被读取和写入 。阅读原文

Set<String>最后序列化后的内容可以看到是多行String。貌似与预期不符。希望应该是逗号分隔的字符串。这样就没办法了,只好自己把Db.find(sql,ids)中的ids换成逗号分隔的String,这个倒是不难,String.join方法即可做到。

只可惜,即便已经按照逗号分隔去传参数了,依然没有返回结果。用log4jdbc看了下最终的执行sql,发现把in里面的内容作为一个字符串执行了,这样当然不行,应该是分开的才对。那到底该怎么办呢?

解决方案

  1. sql中写多个?进行占位,然后分别set。
  2. sql中直接拼接最终的字符串。
  3. mysql有一个find_in_set函数可以使用。

我最后采用的是第三种方案,sql如: select * from xx where find_in_set(id,?); 占位符填的内容就是类似于“a,b”这样的字符串。

为什么没有选择前两种呢,第一种觉得有点傻(个人感觉,可能脑子回路不一样),第二种可能会产生sql注入的问题。

深层次分析

我到底为什么会写in呢?明明in应该是在子查询用的啊!而且感觉之前也用in这么搞过,没出问题啊!

嚼劲脑汁,最后发现是在hibernate中使用过SQLQuery 这个接口中的setParameterList方法,可以完美支持in+Collection。源码如下:

不过上面并不是真正的重点,真正的重点是

这块代码就不细解释了,本质就是hibernate叕叕把写好的sql改成了多个参数的形式了(解决方法1)。。。

总结

通过上面的分析过程,总体重温并学习了以下几个知识点。

  1. mysql的in到底是干啥的。
  2. mysql的in到底应该传什么内容。
  3. mysql的find_in_set函数怎么用。
  4. ObjectOutputStream这个类怎么用。
  5. sql中有in该如何处理。
  6. hibernate是如何处理集合参数的。

未完待续。。。

欢迎各位亲朋好友前来评价!

转载请注明:程序员的自我修养 » mysql-jdbc in参数化

喜欢 (3)or分享 (0)
发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址