目录
这篇文章我们将详细讲解一下Mybatis的2种参数注入方式,并通过实例来比较两者的区别。
1.模糊查询
在讲参数注入方式之前,我们先来看一下Mybatis的模糊查询如何写。
还是一样,我们首先来写UserDao.xml文件,代码如下:
<select id="likeFind" parameterType="java.lang.String" resultType="com.qcby.entity.User">
select * from user where username like '%${value}%'
</select>
截图如下:
说明:细心的朋友会发现,第36行后面的参数注入与之前的有些不同,不用着急,这是Mybatis的另一种参数注入方式,是我们后面要重点讲的。
然后,我们来写接口(不给你代码了),截图如下:
然后,我们来写测试方法,代码如下:
@Test
public void likeFind(){
List<User> users = mapper.likeFind("熊");
for (User user:users) {
System.out.println(user.toString());
}
}
截图如下:
在测试之前,我们先来做一些配置,主要是做日志信息的配置。我们在sqlMapConfig.xml文件里面加入如下代码:
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
截图如下:
最后,让我们看一下测试结果:
这就是完整的日志记录及运行结果。
2.Mybatis获取参数值的两种方式
2.1概述
Mybatis获取参数值得两种方式:${ } 和 #{ }
#{}的本质是占位符赋值;${ }的本质是字符串拼接;
${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;
#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段赋值时,可以自动添加单引号;
2.2具体分析
2.2.1#{}
首先,我们来分析 #{ }
我们的根据id进行查询的方法用的是#{ },我们来看一下它的输出情况:
这是我们的sql语句:
这是我们的输出日志及结果:
可以看见,在我鼠标选中的那一行,后面是 id = ?,说明什么?说明我们sql语句中的 id=#{id} 后面的id是起占位作用的。我先把位置给你占着,然后你传参,然后再把参数放到这个位置上,这就是#{ }的一个流程。
我们可以再换一个例子来看一下:
这是sql语句:
这是测试函数:
这是输出的日志及结果:
我们现在来仔细分析一下:
正常的,能够在Navicat上运行的sql语句是这样的:select * from user where username = '张3'
而我们在编辑器上运行的sql语句是如上图所示,也就是说,我们的前半部分 和 张3 合并起来的时候,他自动的给我们加上了 ' ' 。现在我们懂了,#{ }是一种占位赋值的方式,在赋值的过程中,如果需要 ' ' ,它会自动的帮我们加上 ' ',从而保证sql的正常运行。它的流程还是那样,我先帮你占着位置,你传参,然后把参数放在我的位置上,然后根据参数类型来决定加不加 ' ' (这里的“ 如果需要 ' ' ” 我个人的理解是 它是根据赋值的内容进行判断的,如果是字符串型,就加,如果是Integer型,就不加,可以对比两张图看一下)
2.2.2 ${}
现在,我们再来看一下${ }。
我们的sql语句如下图所示:
测试方法如下:
然后,我们再来看一下输出的日志及结果:
看我鼠标选中的那一行,它传输的是一条完整的sql语句,也就是直接将参数与sql语句拼接起来,然后传参。而#{ }是先传sql语句,再传参,最后拼接查询。这就二者的不同之处。
然后,我们将上述sql语句改一下,如图所示:
然后,测试方法写成这样:
然后,我们再来看一下输出日志及结果:
报错了,为什么?因为没有 ' ' ,这也说明了 ${ } 是一种拼接的方式,#{ } 是一种占位赋值的方式,其中拼接的方式有点笨,有点死板。
2.2.3 小拓展
如何用 #{ } 的方式来书写模糊查询?
答案如下,先看sql语句:
然后,我们看一下测试方法:
最后,让我们看一下输出日志和结果:
既然 #{ } 是占位赋值的方式,那我们就满足它它,用占位赋值的方式来打败它。
2.2.4 小注意
1. #{ value } 是占位赋值的方式,其中value是占位的字符,我们传的所有参数,不管什么样的,最终都是给了value。
2.${value } 是拼接的方式,我们的参数最终也是给了value,但是呢,如果有其他符合,比如%啊之类的,就要写在外面了。
3. #{value}与${value}我们可以把他们看成是一个整体,是不可拆分的,就是这样写的,是固定的,不能在里面加别的了
下面是我书写时范的一个错误,供大家参考:
毫无疑问,测试出现了问题。
2.3执行时对比分析
Mybatis获取参数的两种方式:#{ }占位赋值,${ } 字符串拼接
两者在运行时有什么区别?
#{ }是占位符:动态解析 -> 预编译 -> 执行
预编译可以类比java类的编译,java类被编译成class文件,载入虚拟机,载入虚拟机的字节码文件可以先被编译成机器吗,那么在执行某行代码的时候就可以直接执行编译后的机器码,而不用从字节码开始编译再执行,那么执行效率就高了。这也是为啥热机状态比冷机状态可以抗更多负载的原因。
sql的预编译也是一样的道理,在执行前就编译好,等执行时直接取编译结果去执行。省去编译时间。sql预编译后会在参数位置用占位符表示(也就是?表示)
预编译:数据库驱动在发送sql和参数到DBMS之前,先对sql语句进行编译处理,之后DBMS则可以直接对sql进行处理,不需要再次编译,提高了性能。这一点mybatis 默认情况下,将对所有的 sql 进行预编译处理。
预编译可以将多个操作步骤合并成一个步骤,一般而言,越复杂的sql,编译程度也会复杂,难度大,耗时,费性能,而预编译可以合并这些操作,预编译之后DBMS可以省去编译直接运行sql。
预编译语句可以重复利用。把一个 sql 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个sql,可以直接使用这个缓存的 PreparedState 对象。
${ }是拼接符:动态解析 -> 编译 -> 执行
什么是字符串拼接?字符串拼接就是在我们向数据库发送sql语句之前,我们的sql语句就已经拼接好了。所以${ }的执行过程是先动态解析,然后直接编译,最后执行。没有预编译这一步
2.4安全性的对比分析
从安全性的角度分析,#{ }是更加安全的,因为${ }会引起sql注入的问题。
什么是sql注入问题?我们来看一下下面这个例子:
比如,我们在UserDao.xml文件中写下如下的语句:
可是呢,我们在写测试方法时却写成下图这样:
这里入参入的是 "张三 or username = 李四"
这种通过传参就能改变SQL语句原本规则的操作就是SQL注入,这个在实际生产中当然是危险的,攻击者可以把SQL命令插入到Web表单的输入域或页面请求的查询字符串中,欺骗服务器执行恶意的SQL命令。
以上的情况就属于sql注入攻击。
可能有些同学还是不懂,那就说简单点,我们在模糊查询时,用户在搜索框中搜索到了不该搜索到的东西 就是sql注入(因为你入参出问题了啊,而#{ }会进行预编译,就能排除这个问题)
2.5什么时候使用${ }
$:可以替换表名或者列名,你能确定数据是安全的,可以使用$
如下图所示情况:
2.6如何选择使用#{ }还是${ }
下面给出我个人的几点建议:
1.能用 #{} 的地方就用 #{},尽量少用 ${}
2.表名作参数,或者order by 排序时用 ${}
3.传参时参数使用@Param("")注解,@Param注解的作用是给参数命名,参数命名后就能根据名字得到参数值(相当于又加了一层密), 正确的将参数传入sql语句中(一般通过#{}的方式,${}会有sql注入的问题)
2.7为什么#{ }可以防止sql注入
mybatis的#{ }之所以能够预防sql注入是因为底层使用了PrepardStatment类的setString()方法来设置参数, 此方法会获取参数传递过来的每个字符,然后进行循环对比,如果发现有敏感字符(如:单引号、双引号等), 则会在上边加一个‘/’代表转义此符号,让其变成一个普通的字符串,不参与SQL语句的生成,达到预防sql注入的效果。