Mybatis的$ 和 #的区别

111 阅读2分钟

描述

在MyBatis中,有两种方式可以在SQL语句中引入参数:使用$符号和使用#{}(占位符)。

$符号是一种简单的字符串替换方式,它会将参数直接拼接到SQL语句中,而不使用预编译的方式。这样做的问题是,如果使用不当,可能导致SQL注入的风险。

具体原因如下:

  1. 字符串拼接:使用$符号时,参数值直接替换到SQL语句中,形成字符串拼接。这意味着参数值中包含的任何SQL特殊字符也会被直接拼接到SQL语句中,从而导致潜在的安全风险。
  2. SQL注入攻击:如果应用程序接受用户输入并将其直接拼接到SQL语句中,恶意用户可以通过在参数中添加恶意SQL代码来执行非法操作。例如,恶意用户可以构造参数值以绕过输入验证,删除表格内容,获取敏感信息等。

相比之下,#{}占位符是一种预编译和安全的方式,参数值不会直接拼接到SQL语句中,而是通过预编译处理,将参数值作为参数传递给数据库驱动程序。这样可以有效地防止SQL注入攻击,因为参数值会被正确地进行转义和处理。

为了保证安全性,建议尽量使用#{}占位符来传递参数,在需要动态拼接SQL语句的情况下,可以使用动态SQL标签(如<if><choose>等)来完成灵活的条件判断和拼接,而不是直接使用$符号进行字符串拼接。另外,还应该对用户输入进行严格的输入验证和过滤,以防止恶意攻击。

例子

日志

2023-09-12 21:51:10.543 DEBUG 58170 --- [page/testdollar] c.g.s.d.B.selectByPrimaryKeyByDollar     : ==>  Preparing: select id, `uid`, qw_user_id, `name`, nickname, alias_name, mobile, avatar, sex, landline, email, work_status, system_status, tenant_id, position_id, account_non_locked, last_login_ip, illegal_login_count, illegal_login_time, id_employee_create, gmt_create, id_employee_modified, gmt_modified, delete_user, delete_date, deleted, data_range, name_pinyin, name_pinyin_head, `position` from bf_auth_employee where id = 504894
2023-09-12 21:51:10.553 DEBUG 58170 --- [page/testdollar] c.g.s.d.B.selectByPrimaryKeyByDollar     : ==> Parameters: 
2023-09-12 21:51:10.562 DEBUG 58170 --- [page/testdollar] c.g.s.d.B.selectByPrimaryKeyByDollar     : <==      Total: 1
2023-09-12 21:51:10.564 DEBUG 58170 --- [page/testdollar] c.g.s.d.B.selectByPrimaryKey             : ==>  Preparing: select id, `uid`, qw_user_id, `name`, nickname, alias_name, mobile, avatar, sex, landline, email, work_status, system_status, tenant_id, position_id, account_non_locked, last_login_ip, illegal_login_count, illegal_login_time, id_employee_create, gmt_create, id_employee_modified, gmt_modified, delete_user, delete_date, deleted, data_range, name_pinyin, name_pinyin_head, `position` from bf_auth_employee where id = ?;
2023-09-12 21:51:10.564 DEBUG 58170 --- [page/testdollar] c.g.s.d.B.selectByPrimaryKey             : ==> Parameters: 504894(Long)
2023-09-12 21:51:10.566 DEBUG 58170 --- [page/testdollar] c.g.s.d.B.selectByPrimaryKey             : <==      Total: 1
2023-09-12 21:51:10.566  INFO 58170 --- [page/testdollar] c.g.s.controller.TestPageController      : ===============================
2023-09-12 21:51:10.567 DEBUG 58170 --- [page/testdollar] c.g.s.dao.BfAuthEmployeeDao.count        : ==>  Preparing: select count(alias_name) from bf_auth_employee where sex = ?
2023-09-12 21:51:10.567 DEBUG 58170 --- [page/testdollar] c.g.s.dao.BfAuthEmployeeDao.count        : ==> Parameters: 1(Long)
2023-09-12 21:51:10.586 DEBUG 58170 --- [page/testdollar] c.g.s.dao.BfAuthEmployeeDao.count        : <==      Total: 1
2023-09-12 21:51:10.586  INFO 58170 --- [page/testdollar] c.g.s.controller.TestPageController      : count:2351

对于selectByPrimaryKeyByDollar方法,可以看到日志中的sql是直接把id的值放到了sql中。

image.png 对于selectByPrimaryKey方法,可以看到日志中的sql是把id作为了参数。

image.png

$的作用

$会导致sql不安全,但有一种情况是#无法替代的,那就是上面例子中的count方法,把字段名作为了参数传进了sql中,这样就能够做到动态去计算一个字段的数量。