【MyBatis 知识点解析】#{} 与 ${} 的区别及 SQL 注入实战演示


---JavaEE专栏---

【MyBatis 知识点解析】#{} 与 ${} 的区别及 SQL 注入实战演示

在 MyBatis 的面试和日常开发中,参数占位符 #{} ${} 的区别是绕不开的核心考点。很多同学只知道"#{} 安全,${} 不安全",但其背后的底层原理适用场景却一知半解。本文将通过底层分析、日志演示以及 SQL 注入实验,带你彻底搞定这个知识点。


一、 本质区别速览

特性 #{}(井号) ${}(刀乐/美元符号)
底层原理 JDBC 预编译占位符 ? 纯字符串拼接,直接替换
SQL 注入 安全,自动转义特殊字符 危险,容易被恶意篡改
单引号处理 自动添加,无需手动处理 不自动加 ,字符串必须手动加 ''
性能 高(预编译 SQL 可重复利用) 低(每次都需要重新解析)
使用场景 99% 的业务参数(Where/Set 值) SQL 结构关键字(表名、排序字段)

二、 核心原理详解

1. #{}:预编译模式(推荐)

当 MyBatis 遇到 #{xxx} 时,它会将 SQL 发送到数据库进行预编译 。在执行阶段,再通过 PreparedStatement 设置参数。

  • 示例代码
java 复制代码
@Select("select * from user_info where username = #{name}")
UserInfo queryByName(String name);
  • 打印日志(重点)
    通过日志可以观察到,SQL 中参数部分是 ? 占位符:
    PREPARE: select * from user_info where username = ?
  • 优势:由于 SQL 结构已固定,传入的参数只会被当作"值"处理,不会破坏 SQL 语义,从而彻底杜绝 SQL 注入。
2. ${}:字符串拼接模式(慎用)

${} 会在 SQL 执行前,直接把参数原封不动地替换进 SQL 语句中。

  • 示例代码(报错预警):
java 复制代码
@Select("select * from user_info where username = ${name}")
UserInfo queryByName(String name);
  • 运行结果 :如果你传入 admin,生成的 SQL 是 where username = admin。因为缺少单引号,数据库会报错。
  • 正确写法 :必须手动加引号:'${name}'

三、 SQL 注入(必考面试点)

SQL 注入是指攻击者通过在输入框中填入 SQL 片段,篡改原有逻辑的行为。

注入场景:免密登录

假设我们有一段使用 ${} 的危险代码:

xml 复制代码
SELECT * FROM user WHERE username = '${name}' AND pwd = '${pwd}'
  1. 正常操作 :传入用户名 admin,密码 123
  2. 攻击操作 :攻击者在用户名框输入 admin' -- ,密码随便写。
  3. 最终生成的 SQL
sql 复制代码
SELECT * FROM user WHERE username = 'admin' -- ' AND pwd = 'xxx'

在 SQL 中,-- 代表注释。这意味着 AND pwd = ... 的逻辑被直接注销掉了!攻击者无需密码即可直接以管理员身份登录。

结论绝不能使用 ${} 接收用户输入的参数!


四、 SQL 注入场景演示

控制层:UserTestController
注意自己所写的类位置以及包

java 复制代码
import com.example.demo.model.UserInfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserTestController {
    @Autowired
    private UserTestService userService;
    @RequestMapping("/login")
    public boolean login(String name, String password) {
        UserInfo userInfo = userService.queryUserByPassword(name, password);
        if (userInfo != null) {
            return true;
        }
        return false;
    }
}

业务层:UserTestService

java 复制代码
import com.example.demo.mapper.UserInfoMapper;
import com.example.demo.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserTestService {
    @Autowired
    private UserInfoTestMapper userInfoMapper;
    public UserInfo queryUserByPassword(String name, String password) {
        List<UserInfo> userInfos = userInfoMapper.queryUserByPassword(name, password);
        if (userInfos != null && userInfos.size() > 0) {
            return userInfos.get(0);
        }
        return null;
    }
}

数据层:UserInfoTestMapper

java 复制代码
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.*;
import java.util.List;

@Mapper
public interface UserInfoTestMapper {
    @Select("select username, `password`, age, gender, phone from user_info where username= '${name}' and password='${password}' ")
        List<UserInfo> queryUserByPassword(String name, String password);
}

启动服务,访问:http://127.0.0.1:8080/login?name=admin&password=admin
程序正常运行

接下来访问SQL注⼊的代码:

password设置为' or 1='1

拼接为:http://127.0.0.1:8080/login?name=admin&password=' or 1='1
注意看网页地址!(百分号和空格进行了URL编码,因为URL不允许直接添加特殊字符)

五、 ${} 的"唯一"合法使用场景

既然 ${} 这么危险,为什么不废掉它?因为它在处理 SQL 结构时无可替代:

  1. 动态表名
    SELECT * FROM ${tableName}#{} 无法用于表名,因为预编译不支持表名占位)。
  2. 动态排序字段
    ORDER BY ${column} ${orderType}(如按 idcreate_time 排序,且指定 ASC/DESC)。

安全建议 :在使用这些场景时,必须在 Service 层做白名单校验,确保传入的列名或表名是合法的。


六、 开发规范口诀

为了方便记忆,我们可以总结为一段口诀:

井号预编译安全自带引号,刀乐拼接危险手动加引号;
业务参数全用井号,结构关键字才用刀乐。

一句话总结:

平时开发无脑用 #{};只有在需要动态传递表名、列名、排序关键字且已经做好安全过滤的情况下,才考虑使用 ${}

相关推荐
丶小鱼丶1 小时前
数据结构和算法之【数组】
java·数据结构·算法
惊讶的猫1 小时前
maven介绍_1
java·maven
tongxh4231 小时前
MySQL Workbench菜单汉化为中文
android·数据库·mysql
小钻风33661 小时前
Java函数式编程-lambda表达式
java·开发语言·python
yc_xym1 小时前
Redis经典应用-分布式锁
数据库·redis·分布式
Han.miracle2 小时前
Spring IoC 与 DI 思想及实践详解
java
Irissgwe2 小时前
基础I/O
java·linux·前端
木易 士心2 小时前
Java中 synchronized 和 volatile 详解
java·开发语言·jvm
小码狐2 小时前
Spring相关知识【知识整理】
java·后端·spring