【1】#和$使用对比
① #和$详解
在JDBC中,主要使用的是两种语句:
一种是支持参数化和预编译的PrepareStatement,能够支持原生的Sql,也支持设置占位符的方式,参数化输入的参数,防止Sql注入
一种是支持原生Sql的Statement,有Sql注入的风险。
#{key}:获取参数的值,预编译到SQL中。安全。
${key}:获取参数的值,拼接到SQL中。有SQL注入问题。ORDER BY ${name}
在使用Mybatis进行开发过程中,隐藏了底层具体使用哪一种语句的细节,我们通过使用#和$告诉Mybatis,我们实际上进行的是怎么样的操作,需要对语句进行参数化还是说直接保持原生状态就好。
在Mybatis的mapper.xml中经常看到这两个符号,其中 # 频率最高。
如下:
<insert id="insertUSer" parameterType="User" > insert into c_user (name,age) values(#{name},#{age}) </insert>
或者如下:
//... <if test="SORTNAME !=null and SORTNAME !='' "> order by ${SORTNAME} <if test="SORTORDER !=null and SORTORDER !='' "> ${SORTORDER} </if> </if> limit ${(page-1)*pagesize},${pagesize}
下面说明一下二者的用法与区别
默认情况下,使用 #{ }
格式的语法会导致 MyBatis 创建预处理语句属性并以它为背景设置安全的值(比如 ?
)。
这样做很安全,很迅速,也是首选的做法 !
但有时你只是想直接在SQL语句中插入一个不改变的字符串。比如,像 ORDER BY , 你可以这样来使用:
ORDER BY ${columnName}
这里 MyBatis 不会修改或转义字符串。如果sql语句中使用动态的表或者列,请使用$符号
!
接受从用户输出的内容并提供给语句中不变的字符串,这样做是不安全的,这会导致潜在的SQL注入攻击。因此你不应该允许用户输入这些字段,或者通常自行转义并检查。
② 使用#或者说PrepareStatement如何防止SQL攻击?
比如说根据学生姓名查学生信息,会传入一个name的参数,假设学生姓名是方方,那么Sql就是:
SELECT id,name,age FROM student WHERE name = '方方';
在没有做防Sql注入的时候,我们的Sql语句可能是这么写的
<select id="fetchStudentByName" parameterType="String" resultType="entity.StudentEntity"> SELECT id,name,age FROM student WHERE name = '${value}' </select>
但如果我们对传入的姓名参数做一些更改,比如改成anything' OR 'x'='x
,那么拼接而成的Sql就变成了:
SELECT id,name,age FROM student WHERE name = 'anything' OR 'x'='x'
库里面所有的学生信息都被拉了出来,是不是很可怕。原因就是传入的anything’ OR ‘x’='x
和原有的单引号,正好组成了 ‘anything’ OR ‘x’='x’
,而OR后面恒等于1,所以等于对这个库执行了查所有的操作。
防范Sql注入的话,就是要把整个anything’ OR ‘x’='x
中的单引号作为参数的一部分,而不是和Sql中的单引号进行拼接!
使用了#
即可在Mybatis中对参数进行转义
:
<select id="fetchStudentByName" parameterType="String" resultType="entity.StudentEntity"> SELECT id,name,age FROM student WHERE name = #{name} </select>
我们看一下发送到数据库端的Sql语句长什么样子。
SELECT id,name,age FROM student WHERE name = 'anything\' OR \'x\'=\'x'
从上述代码中我们可以看到参数中的所有单引号统统被转义了,这都是JDBC中PrepareStatement的功劳。如果在数据库服务端开启了预编译,则是服务端来做了这件事情。
【2】使用动态表或列
有时候会不可避免使用动态表或者列进行业务处理。下面学习几种动态表/列的使用方式(其本质核心是使用$获取表名或列名
):
① 使用预编译
即,默认值。
<select id="hisNumber" parameterType="hashmap" resultType="hashmap" > select number from ${oldTableName} <!--这里使用"$"!!!--> where name=#{name} and date = #{date} <!--这里使用"#"--> <select>
预编译,即首先会生成select number from ? where name=? and date=? 这样使用”?”作为占位符的语句,然后进行参数解析。
② 使用非预编译
<select id="hisNumber" parameterType="hashmap" resultType="hashmap" statementType="STATEMENT" > select number from ${oldTableName} <!--这里使用"$"!!!--> where name='${name,jdbcType=VARCHAR}' and date = '${date,jdbcType=TIMESTAMP}' <select>
注意后面name和date的取值,使用了'${name}'
格式,这样会对参数进行数据类型转换,有助于mysql查询时提升性能。
仍旧使用非预编译
<select id="hisNumber" parameterType="hashmap" resultType="hashmap" statementType="STATEMENT" > select number from ${oldTableName} <!--这里使用"$"!!!--> where name=${name} and date = ${date} <select>
注意后面name和date的取值,使用了${name}
格式,将会直接取参数值,不进行数据类型转换。当参数为数值类型且格式如“00124”时,将会出现数据错读(会将0124、124等都读出来)。