浅谈一下MyBatis的#{param}和${param}
我们都知道,MyBatis是一个很灵活的ORM框架,这其中就离不开#{param}和${param}的使用。但是本文就不谈什么是ORM框架了,大家可以自己了解一下。
但是在讲#{param}和${param}和以前,我要先来谈谈Mysql的预编译技术,我们都知道,当我们的SQL语句发送给MySQL服务器后,MySQL会对我们的SQL语句进行校验、解析等操作。但是很多情况下,我们的SQL语句都是相同的,只是参数有所不同,如果每次都要进行一遍校验、解析等操作,无疑是十分浪费性能的,于是就有了预编译技术。
MySQL 的预编译机制
1. 预编译过程:
- 当客户端发送一个带有占位符(
?
)的 SQL 语句(使用PreparedStatement
)给 MySQL 时,MySQL 首先会对该 SQL 语句进行解析、语法检查、优化,然后将其编译为一个执行计划。 - 这个过程称为“预编译”。之后,预编译的 SQL 语句会被存储在 MySQL 的内部缓存中。
2. 如何缓存和识别已预编译的语句:
- MySQL 通过维护一个预编译语句缓存来存储已预编译的语句。这个缓存是一个内存结构,用于保存已经预编译过的 SQL 语句及其相应的执行计划。
- 当一个 SQL 语句(使用占位符的
PreparedStatement
)再次执行时,MySQL 会检查这个缓存,看看该 SQL 语句是否已经存在于缓存中。 - MySQL 根据 SQL 语句的文本和参数结构(不包括具体的参数值)生成一个哈希值。然后,它使用这个哈希值在缓存中查找是否已经存在对应的预编译语句。
3. 缓存命中和重用:
- 如果缓存中存在该 SQL 的执行计划,称之为“缓存命中”,MySQL 将直接使用缓存中的执行计划,而无需重新解析和编译。这样就大大提高了性能。
- 如果缓存中没有找到该 SQL 语句的执行计划(“缓存未命中”),MySQL 就需要重新解析和编译该 SQL 语句,然后将新的执行计划存入缓存中。
所以,在有了预编译技术后可以很大的提升SQL语句的执行性能。
MyBatis中#{param}和${param}的区别
#{param}
(占位符绑定):#{param}
是参数占位符,它会被 MyBatis 转换为使用PreparedStatement
的?
占位符,并在执行 SQL 时将参数值绑定到这些占位符。- 使用
#{param}
会启用数据库的预编译机制,因为参数的值是在执行时由数据库使用绑定变量进行替换的。 - 优点:这种方式能够防止 SQL 注入,同时利用数据库的预编译机制提高性能。
${param}
(字符串替换):${param}
会直接将参数的值插入到 SQL 语句中,像字符串拼接一样。这种方式会在 SQL 拼接时将参数的值直接插入到查询中,生成的 SQL 语句不再含有占位符。- 由于
${param}
是直接将值插入到 SQL 语句中,因此它不会使用数据库的预编译机制 - 风险:这种方式有 SQL 注入的风险,因为参数的值可能包含恶意的 SQL 代码。
所以,也就是说,在使用#{param}的地方,会将对应位置的参数作为一个整体替换掉对应位置的占位符'?'怎么理解这句话呢,比如说下面这条SQL语句
select user_name from user where id = #{userId}
由于使用的是#{},所以会预编译为
select user_name from user where id = ?
如果对应的参数是"1"(1才是参数内容,外面的""只是表示这是一个字符串),那么这条语句最终会变成
select user_name from user where id = '1'
又或者如果对应的参数是 "1;DROP TABLE user;"最终这条语句会变成
select user_name from user where id = '1;DROP TABLE user;'
也就是说,用户输入的参数,会作为一个整体替换掉原来的占位符,其实就是会把用户输入的参数用单引号包成一个整体传进去。
但是如果使用的是${},最终这条语句会变成
select user_name from user where id = 1;DROP TABLE user;
使用${}时会直接将内容拼接到对应的位置,如果是上面这种情况,最终user表会被删除,这其实就是我们所说的SQL注入的风险。
但是,有的情况又只能使用${},比如:
select * from ${tableName} where id = #{id}
假设tableName="user",id="1",那么上面的语句会变为
select * from user where id = '1'
但是如果是#{tableName}
select * from #{tableName} where id = #{id}
最终会变成下面的情况,不能提供MySQL的语法校验,最终会报错。
select * from 'user' where id = '1'
其实我们在使用的时候只要记住,使用#{}时会在参数的整体加上单引号,然后思考一下什么情况加上单引号会出错就好了,然后就是能用#{}的地方尽量都用#{},可以防止SQL注入,同时也会使用到预编译在性能上来说也会比${}更好。