MyBatis中#{}和${}的区别及底层原理、适用场景和安全风险

 更新时间:2026年04月25日 14:01:21   作者:想不明白的过度思考者  
本文详细解析了MyBatis中#{}和${}的区别及其背后的底层原理、适用场景和安全风险,#{}预编译安全自动转义,适用于业务参数;{**拼接不安全需要手动加引号,主要用于动态表名、列名、排序关键字,使用时须做白名单校验

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

一、 本质区别速览

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

二、 核心原理详解

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

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

  • 示例代码
@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 语句中。

  • 示例代码(报错预警):
@Select("select * from user_info where username = ${name}")
UserInfo queryByName(String name);

  • 运行结果:如果你传入 admin,生成的 SQL 是 where username = admin。因为缺少单引号,数据库会报错。
  • 正确写法:必须手动加引号:'${name}'

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

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

注入场景:免密登录

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

SELECT * FROM user WHERE username = '${name}' AND pwd = '${pwd}'

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

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

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

四、 SQL 注入场景演示

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

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

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

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 注入场景演示
接下来访问SQL注⼊的代码:
password设置为' or 1='1
拼接为:http://127.0.0.1:8080/login?name=admin&password=’ or 1='1
注意看网页地址!(百分号和空格进行了URL编码,因为URL不允许直接添加特殊字符)
 SQL 注入场景演示_图2

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

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

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

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

六、 开发规范口诀

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

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

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

到此这篇关于MyBatis中#{}和${}的区别及底层原理、适用场景和安全风险的文章就介绍到这了,更多相关MyBatis中#{}和${}的区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 用计算列实现移动加权平均算法

    用计算列实现移动加权平均算法

    昨天有人让我帮忙写个算移动加权平均的SQL语句,我想了半天终于写出来正确的了。现在发出来供大家参考、讨论。
    2009-09-09
  • SQL大量数据查询的优化及非用like不可时的处理方案

    SQL大量数据查询的优化及非用like不可时的处理方案

    这篇文章主要介绍了SQL大量数据查询的优化及非用like不可时的处理方案,需要的朋友可以参考下
    2015-07-07
  • 数据库系统结构详解之三级模式结构

    数据库系统结构详解之三级模式结构

    这篇文章主要为大家介绍了数据库系统的结构,文中通过图文的方式详细的解析了数据库系统结构的三级模式结构,有需要的朋友可以借鉴参考下
    2021-09-09
  • 一文详解在Hive中NULL的理解

    一文详解在Hive中NULL的理解

    在Hive中,NULL表示的是异常,这篇文章主要介绍了在Hive中NULL理解的相关资料,文中通过代码介绍的非常详细,对大家学习或者使用Hive具有一定的参考借鉴价值,需要的朋友可以参考下
    2025-11-11
  • 大数据时代的数据库选择:SQL还是NoSQL?

    大数据时代的数据库选择:SQL还是NoSQL?

    执行大数据项目的企业面对的关键决策之一是使用哪个数据库,SQL还是NoSQL?SQL有着骄人的业绩,庞大的安装基础;而NoSQL正在获得可观的收益,且有很多支持者。我们来看看两位专家对这个问题的看法
    2014-03-03
  • JDBC常用接口总结

    JDBC常用接口总结

    这篇文章主要介绍了JDBC常用接口总结,以及部分使用代码语句,需要的朋友可以参考下。
    2017-09-09
  • 一文解读 SQL 生成工具

    一文解读 SQL 生成工具

    SQL生成工具用于测试数据库产品的兼容性,通过解析YACC语法文件生成SQL语句,并执行以判断与其他数据库语法的兼容性,工具首先使用预处理脚本去除语法文件中的非必要内容,保留产生式,然后,根据参数使用工具生成SQL语句,
    2026-02-02
  • jdbc 数据库的连接(sqlserver oracle)

    jdbc 数据库的连接(sqlserver oracle)

    sql Server 和oracle 数据库的连接,供大家参考!
    2009-08-08
  • DBeaver执行外部sql文件详细图文教程

    DBeaver执行外部sql文件详细图文教程

    DBeaver最近才使用,以前使用的PL/SQL,有些不是很熟悉,记录下来,下面这篇文章主要给大家介绍了关于DBeaver执行外部sql文件的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2023-06-06
  • sql学习之CASE WHEN THEN ELSE END的用法

    sql学习之CASE WHEN THEN ELSE END的用法

    这篇文章主要介绍了sql学习之CASE WHEN THEN ELSE END的用法,需要的朋友可以参考下
    2014-06-06

最新评论