《目录》
《内容》
1.mybatis中#{}和${}
1.1 #{}和${}区别
案例:
delete from tab_user where uid = #{uid},jdbcType=INTEGER
sql解析分析:
- 上述案例中使用的是#{},sql在解析的时候会加上 " " ,当成字符串来解析 : uid= " uid ";
- 如果用${},传入数据直接显示在生成的sql中,如上述案例,用uid = ${uid,jdbcType=INTEGER},那么sql在解析的时候值为uid = uid,执行时会报错;
具体区别:
#{}:
- 使用预编译的方式将参数设置到sql语句当中(相当于占位符 ?)
- 底层使用的是原生jdbc中的prepareStatrment
- 能够在一定程度上防止sql注入的风险(无法避免%的问题)
${}:
- 不适用预编译模式
- 去除相应的值直接拼装到sql语句当中
- 会有sql注入的安全问题
1.2 #{}和${}应用场景
- 大多数情况(如CRUD语句)使用 #{};
- 在一些原生jdbc不支持占位符的地方,我们就不能使用#{var}的形式去进行取值,这样会导致执行sql的时候报错,比如传入数据库对象(表名,列名),都需要我们使用${var}的形式直接取值进行字符串的拼接
select XXX from ${year}_${month}_XXX where id = XXX order by ${columnName}
2.sql注入
2.1 sql注入的原因
用于攻击数据驱动的应用,恶意的SQL语句被插入到执行的实体字段中(例如,为了转储数据库内容给攻击者),大家都不陌生,是一种常见的攻击方式。攻击者在界面的表单信息或URL上输入一些奇怪的SQL片段(例如“or ‘1’=’1’”这样的语句),有可能入侵参数检验不足的应用程序。
2.2 #{}防止sql注入的底层原理
假设mapper文件为:<select id="getNameByUserId" resultType="String">
SELECT name FROM user where id = #{userId}
</select>
对应的java文件为:
public interface UserMapper{
String getNameByUserId(@Param("userId") String userId);
}
可以看到输入的参数是String类型的userId,当我们传入userId="34;drop table user;"后,打印的语句是这样的(不管输入何种userID,他的sql语句都是这样的):
select name from user where id = ?
底层原理:这就得益于mybatis在底层实现时使用PreparedStatement类进行预编译语句。预编译sql语句后,再用传入的userId替换占位符?就去运行了。不存在先替换占位符?再进行编译的过程,因此SQL注入也就没有了生存的余地了。
另外,PreparedStaement类不但能够避免SQL注入,因为已经预编译,当N次执行同一条sql语句时,节约了(N-1)次的编译时间,从而能够提高效率。
2.3 避免sql注入的方法
- 采用#{},但是在模糊查询时,用concat(’%’, #{}, ‘%’)
- 对传入参数进行预处理检查,比如对于上面的userId参数;或者代码白名单的方式限制传入参数(限制 sortBy 允许的值,如只能为 name, email 字段,异常情况则设置为默认值 name
)
<select id="getUserListSortBy" resultType="org.example.User">
SELECT * FROM user
<choose>
<when test="sortBy == 'name' or sortBy == 'email'">
order by ${sortBy}
</when>
<otherwise>
order by name
</otherwise>
</choose>
</select>
- 存储过程。存储过程(Stored Procedure)是一组完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过调用存储过程并给定参数(如果该存储过程带有参数)就可以执行它,也可以避免SQL注入攻击