引言
我们都知道在Mybatis中有两种获取参数的方式,#{}和${},面试中也是一个经常问的点,今天我们就来深究一下这两种方式背后的一些奥秘,我们会从表现形式和背后原理两个部分来分析,让你知道哪里不同以及为什么不同。
表现形式
1 #{}可以防止sql注入,${}会有sql注入问题
2 #{}使用的是占位符的形式,而${}采用的是字符串拼接的方式
背后原理
1 我们来实际看一个例子。
采用${}方式进行登录验证
模拟前端拿到的参数,这里我们假设黑客不知道密码,只使用用户名进行登录,我们看一下${}能不能拦住他。
mapper.xml这样写
结果如下
这里并没有出现sql注入问题,问题出在哪呢?仔细观察我们的mapper.xml文件中,取值的操作是’ ${arg0} ‘这样写的,两边有单引号,黑客输入的sql其实并没有执行,而是被当作字符串解析了。那么为什么我们要’${arg0}’ 这么写呢?因为我们取得值在mysql中是字符串存储的,加上’ '来查询才可以查出来。
我们改一下,取值的时候直接${arg0} 写,再进行一次测试我们看看结果。
显然黑客成功了,也就完成了我们所说的sql注入的攻击。
所以我们说${}确实会有sql注入问题,但并不一定会发生,很大程度上取决与程序员怎么写sql。
而对于#{}来说,因为是使用占位符的方式,mybatis会将上面的sql下面这样子,然后把两个参数填进去,所有避免了sql注入的问题。
2 从mybatis的官方文档上我们可以找到,当使用#{} 参数语法时,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样),而使用${}使用的底层创建的是Statment对象,我们来复习一下这两个对象是怎么使用的。
使用statement
Statement stmt = connect.createStatement();
String sql= "SELECT * FROM 表名 WHERE 字段=值";
//去执行sql语句
ResultSet rs = stmt.executeUpdate(sql);
使用preparstatement
String Sql = "INSERT INTO qiuer(sName,sAge,sClass) VALUES (?,?,?)";
// 预先执行指令
PreparedStatement prmt = connection.prepareStatement(Sql);
// 用来向prmt中的sql语句放入 数据 可以是从页面来的 可以是从后台来的
prmt.setString(1,qiuer.getsName());
prmt.setInt(2,qiuer.getsAge());
prmt.setString(3,qiuer.getsClass());
// 执行prmt指令 增删改 语句
prmt.executeUpdate();
// 查询语句
// prmt.executeQuery()
不知道看到这里大家有没有明白为什么#{}使用的是占位符的形式,而${}采用的是字符串拼接的方式,因为它们的底层就是这样调用的对象就是这样写的。
再思考
说到这里可能有人回问,看起来#{}比${}要好用很多呀,而且实际开发中也是用#{},老师也说能用#{}就不用${},那么${}存在的意义是什么呢?
因为有的查询使用#{}是不行的,举个例子
需求,通过传入的参数作为表名去查询所有的数据
mapper.xml这样写
解析后就变成 select * from “test”;
执行就会报错
而改成这样
解析后就变成 select * from test; 才可以执行成功
小结
上述就是Mybatis中#{}和${}的不同点,这是”十万个为什么“的第二篇文章,写这篇文章也是验证了实践出真知,下次再有人说${}会导致sql注入就把这篇文章扔给他,自己动手试一试便知。