前言
我们在使用mybatis开发的过程中,经常会用到动态SQL,用到动态SQL就会用到占位符,$ {} 和 # {}是 mybatis中的两种占位符,其最大的区别就是 # {} 可以防止SQL注入,但是${}不可以 ,首先了解以下什么是SQL注入。
什么是SQL注入
SQL 注入是一种常见的安全漏洞,攻击者利用该漏洞可以通过构造特定的输入,向数据库中插入恶意代码或者修改数据库中的数据,甚至可以获取敏感数据。
SQL 注入通常发生在 Web 应用程序中,攻击者可以在用户输入的数据中注入 SQL 代码。当应用程序没有正确地过滤和转义用户输入的数据时,就会存在 SQL 注入的风险。例如,一个简单的用户登录页面,如果没有对用户输入的用户名和密码进行正确的过滤和转义,攻击者就可以在用户名或密码中注入 SQL 代码,例如:
SELECT * FROM users WHERE username='admin' AND password='XXXXX' OR 1=1;
上述代码中的 OR 1=1 表示条件永远成立,这样就可以绕过密码验证直接登录到系统中。
为了避免 SQL 注入攻击,开发人员应该对用户输入的数据进行正确的过滤和转义。在 MyBatis 中,可以使用 #{} 占位符来自动预编译处理传入参数,从而有效地防止 SQL 注入攻击。此外,还可以使用转义函数等方式来对用户输入的特殊字符进行处理,从而保证 SQL 语句的安全和可靠。
什么是预编译
预编译是指在程序执行 SQL 语句之前,数据库会对 SQL 语句进行编译和优化,生成一个可执行的执行计划,并缓存这个执行计划。这个执行计划包含了 SQL 语句的语法分析、表结构分析、索引分析等信息,能够在执行 SQL 语句时快速定位到数据的存储位置,从而提高查询的效率。
预编译就是将SQL语句中的值用一个占位符来进行替代,将SQL语句进行模板化,实现一次编译,多次运行。
预编译的过程一般在程序运行前执行,只需进行一次,不必每次执行 SQL 语句时都进行编译。这可以有效地减少数据库的运算量和网络传输的数据量,提高程序的执行效率。
Mybatis底层使用 PreparedStatement ,默认情况下,将对所有的 SQL进行预编译,将#{}替换为?,然后将带有占位符 ? 的SQL模板发送至MySQL服务器,由服务器对此无参数的SQL进行编译后,将编译结果缓 存,然后直接执行带有真实参数的SQL。
预编译的好处:
- 提高执行的效率:预编译将SQL语句和参数分离开来,将SQL语句编译成一个执行计划,参数只需要传递即可,不需要每次使用这个SQL语句的时候都要重新进行编译,减少了执行时间和资源的消耗,减少了数据库的负载。
- 提高安全性,防止SQL注入:预编译可以防止SQL注入,因为其将会无视参数进行编译,所以参数不会对SQL的编译结果产生任何的影响,从而避免了恶意代码的注入。
- 预编译语句可以重复利用:当预编译之后,会将其存入缓存中,当有相同的语句执行的时候,会直接使用缓存中的执行计划,提高了执行效率。
#{}的实现原理
#{} 占位符的使用非常简单,在 SQL 语句中可以使用 #{} 来代替需要传入的参数。每次#{}会被解析成预编译的语句,预编译之后可以直接执行,不需要重新编译SQL。例如,可以使用以下语法来查询用户表中的某个用户:
SELECT * FROM user WHERE username = #{username}
其会先预编译成:
SELECT * FROM user WHERE username = ?
其中,#{username} 会被 MyBatis 解析为一个占位符,表示从 Java 对象中获取 username 属性的值,并将该值转换为 SQL 语句中的参数。
在实际使用中,#{} 占位符还支持一些特殊的语法,例如:
- 支持类型转换:可以使用 #{} 将传入参数进行类型转换,例如 #{age,jdbcType=INTEGER} 表示将传入的 age 参数转换为 INTEGER 类型。
- 支持表达式:可以使用 #{} 来编写表达式,例如 #{age+1} 表示将传入的 age 参数加 1。
- 支持特殊字符转义:在 #{} 中使用转义字符 \,可以对特殊字符进行转义,例如 #{‘Tom’s book’} 表示将单引号进行转义,从而避免 SQL 注入攻击。
${}的实现原理
${}仅仅为一个字符串替换,它将占位符替换成一个文本字符串,然后生成SQL语句,在每次执行SQL的时候都会重新进行编译,有SQL注入的问题。
select * from user where username = '${username}'
当传递的参数为"cuisenghe"的时候,其会将上面的语句解析成以下形式,然后给数据库进行编译,然后执行。
select * from user where username = "cuisenghe"
也就是说,当使用此种方式的时候,因为没有进行预编译,所以如果用户传来的参数也是一个SQL语句的话,其会拼接在之前的SQL语句后面,再进行编译的时候,会将其也视作SQL语句来进行执行,从而引发了SQL注入的风险。
总结
总的来说,${} 的使用应该尽量避免,只在需要动态拼接 SQL 语句时才使用,并且需要特别注意安全性问题。在普通的 SQL 语句中,建议使用 #{} 占位符,以提高 SQL 语句的可读性、可维护性和安全性。
欢迎大家访问我的个人博客!
博客地址:个人博客