BruceBat's Blog
BruceBat's Blog

竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生


  • 首页

  • 归档

  • 分类

  • 标签

  • 搜索
NIO I/O 计算机科学 操作系统 设计模式 随记 WebSocket 计算机网络 注册中心 经典电影 xxl-job 分布式 分布式任务调度 MySQL DevOps Docker 多线程 有趣的问题 Mybatis-Plus Mybatis Java 数据结构

拥抱开源之Mybatis-Plus:动态查询条件引发的对比

发表于 2020-01-09 | 分类于 Java | 0 | 阅读次数 1758

人生苦短,不如养狗

一、问题描述--动态查询条件

背景

  文章的背景是一位同事想要使用Mybatis动态设置查询条件,但是这个过程中使用"$"却无法进行相应查询引发的问题。当时闲鱼正在拥抱Mybatis-Plus,所以顺带做了一个对比。

问题

  本文主要介绍的是在项目使用中对于动态传入where和order by条件引发的问题进行的对Mybatis和Mybatis-Plus的对比。
  项目中使用的是Mybatis,其中xml文件中一个查询语句是进行如下编写的:

order by #{condition}

  在动态传入条件的时候使用了“#”,当然不适用“$”的目的就是为了防止SQL注入,但是实际使用的时候就会出现如下的问题:

order by ?

  在日志中你可以看到传入的condition条件变成了"?",这是怎么回事?明明已经传了值,但是为什么在运行时会认为是没有值呢?

二、问题分析

"$"和"#"的区别

  对于这个问题,我们先来看下"$"和"#"的区别。

  在Mybatis中我们进行数据的传入会使用两种方式,一种是"#",一种是"$"。在使用中可能大部分都知道前一种可以防止SQL注入,而后一种不行,但是具体原因去没有进行深究。现在我们来分析一下这两种传值的不同。分析之前先来补充一个重要的知识点,Mybatis对于动态SQL的处理分为两个阶段,第一个阶段是动态解析,将动态SQL解析为一个BoundSql对象,第二个阶段是预编译。

  首先我们来看下"$"传值,类似下面的语句:

select * from user where name = ${name}

  Mybatis在遇到"$"符号的时候,会自动认为不需要进行任何处理,只需要简单纯粹的变量替换,所以在动态解析阶段就会进行变量替换,这也是导致SQL注入的重要原因。在动态解析之后,这条SQL会被处理为如下语句:

select * from user where name = 'zhangsan'

  再来看下"#"传值,还是使用上面的语句:

select * from user where name = #{name}

  和"$"不同的是,Mybatis在遇到"#"符号时会将其解析为一个JDBC预编译语句的参数标记符,简单来说,就是会使用preparestatement来进行SQL处理,使用preparestatement会将传入的参数与SQL进行分开处理,只有在DBMS完成SQL指令的编译之后才会套用参数运行(这一步中的编译操作由JDBC的SQL预编译进行处理,DBMS无需再进行SQL编译就可以直接执行)。
  上面的语句经过动态编译之后会呈现如下结果:

select * from user where name = ?

  一个#{}被解析为一个参数占位符"?",需要注意的是,使用SQL占位符来进行字符串变量替换的时候会带单引号'',这也是上面动态条件为什么使用"#"会出现SQL语法错误的原因。对于"#",变量替换是发生在DBMS中。

三、问题解决

1.使用Mybatis尝试解决

  搞明白了出现上面问题原因,应该如何解决呢?其实很简单,我们传入类似表名或者字段名这些字段时使用的是字符串,此时像上面一样使用"#",在DBMS中运行时就出现如下情况:

order by 'condition'

  此时就会出现SQL运行时错误,而在日志中打印时会将动态解析是的BoundSql对象打印出来,也就是:

order by ?

  为了解决"#"的问题,我们可以使用"$",这样在动态解析时就会进行变量替换,而不会使用SQL占位符来进行替换。但是这样就会出现SQL注入问题。这时我想到了Mybatis不行的话,不如试一试Mybatis-Plus。

2.使用Mybatis-Plus尝试解决

  在Mybatis-Plus中我们可以选择和Mybatis一样使用动态SQL语句,也可以使用EntityWrapper对象来组装查询条件,其中orderBy可以进行如下拼接:

    EntityWrapper<MqdeliverChannelEntity> wrapper = new EntityWrapper<>();
    wrapper.eq("article_id","test_01");
    wrapper.orderBy("gmt_create and 0<>(select count(*) from admin)");
    // and 0<>(select count(*) from admin) 该语句是用来测试SQL注入

  很不幸,试验失败了。使用wrapper对象的orderBy方法可以进行动态传值,但是仍然无法阻止SQL注入。执行结果如下图展示:



  看到这个结果后,我又去看了一遍源码,确实这个是作为字段直接进行SQL语句拼接,这个操作等效于使用"$"。但是我依然不死心,又跑到github上去咨询了一下作者,但是作者进行了如下回复:



  好吧,是在下输了。那么就只能使用一种Low的方式解决了--使用枚举类。至于枚举类怎么写,大家自由发挥,我就不献丑了。

四、总结

  经过对比,发现无论使用Mybatis还是Mybatis-Plus都是无法避免SQL注入,这一局两者打平。以上就是这次的分享,溜了溜了~~

本文使用 mdnice 排版

brucebat wechat
一个闲鱼程序猿的微信公众号
  • 本文作者: brucebat
  • 本文链接: https://www.swzgeek.com/archives/拥抱开源之mybatis-plus动态查询条件引发的对比
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
# NIO # I/O # 计算机科学 # 操作系统 # 设计模式 # 随记 # WebSocket # 计算机网络 # 注册中心 # 经典电影 # xxl-job # 分布式 # 分布式任务调度 # MySQL # DevOps # Docker # 多线程 # 有趣的问题 # Mybatis-Plus # Mybatis # Java # 数据结构
Java中的数据结构(一):为什么是红黑树
Java中的数据结构(二):队列(上)
  • 文章目录
  • 站点概览
brucebat

brucebat

一个有梦想的咸鱼程序猿

46 日志
8 分类
22 标签
RSS
Github E-mail
Creative Commons
© 2020 — 2023 brucebat
苏ICP备20002207号-1

苏公网安备 32011302320859号

0%