为什么MyBatis中常用#{}而不使用${}

 更新时间:2025年09月11日 08:32:13   作者:Leaton Lee  
本文主要介绍了为什么MyBatis中常用#{}而不使用${},文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言:一个真实的故事引发的技术思考

去年双十一凌晨2点,某公司用户数据被删,因为一个MyBatis的${}使用失误,导致黑客通过地址栏注入恶意SQL,直接清空了用户表...

这就是我今天要讲的:为什么MyBatis必须用#{},打死都不能用${}! 跟着我左手画个SQL,右手写个防注入,咱们用3个真实案例+6个代码演示+1个完整项目,彻底讲透这个价值百万的安全问题!

一、新手村装备:MyBatis参数传递基础

1.1 参数占位符是什么?

在MyBatis的XML映射文件中,我们经常看到这样的SQL:

SELECT * FROM users WHERE id = #{userId}

这里的#{}就是MyBatis的参数占位符,相当于JDBC中的PreparedStatement的 ‘?’

1.2 两种占位符对比表

特性#{}${}
参数类型任意类型只能字符串
SQL注入安全高危
预编译支持不支持
参数替换方式带引号替换直接拼接
使用场景值传递动态表/列名

二、#{}与${}的"孪生兄弟"之谜

2.1 表面相似的代码

先看两个看似相同的查询:

<!-- 使用#{} -->
<select id="getUserById" resultType="User">
    SELECT * FROM users WHERE id = #{userId}
</select>

<!-- 使用${} -->
<select id="getUserByIdUnsafe" resultType="User">
    SELECT * FROM users WHERE id = ${userId}
</select>

2.2 实际执行的SQL

假设传入userId=1:

  • #{}生成的SQL

    SELECT * FROM users WHERE id = ?

    参数1会被预编译处理

  • ${}生成的SQL

    SELECT * FROM users WHERE id = 1

    直接拼接参数值

三、SQL注入:程序员的午夜凶铃(代码级演示)

3.1 模拟黑客攻击场景

假设有个登录查询:

<!-- 危险写法! -->
<select id="login" resultType="User">
    SELECT * FROM users 
    WHERE username = '${username}'
    AND password = '${password}'
</select>

黑客输入:

  • 用户名:admin' --
  • 密码:随意填

生成的SQL

SELECT * FROM users 
WHERE username = 'admin' -- ' AND password = '123456'

效果:成功绕过密码验证,直接登录admin账号!

3.2 使用#{}的正确姿势

<select id="safeLogin" resultType="User">
    SELECT * FROM users 
    WHERE username = #{username}
    AND password = #{password}
</select>

此时无论输入什么,参数都会被正确处理,无法注入。

四、执行过程剖析:预编译 vs 字符串拼接

4.1 MyBatis执行流程对比图

graph TD
    A[接收参数] --> B{占位符类型}
    B -->|#{}| C[生成PreparedStatement]
    B -->|${}| D[拼接完整SQL]
    C --> E[预编译SQL]
    E --> F[安全执行]
    D --> G[创建Statement]
    G --> H[风险执行]

4.2 预编译的三大优势

  • 防注入:将参数视为数据而非代码
  • 性能提升:重复查询复用编译结果
  • 类型安全:自动处理数据类型转换

五、必须使用${}的三种特殊场景

虽然${}很危险,但在某些场景下不得不使用:

5.1 动态表名查询

<select id="selectByTable" resultType="map">
    SELECT * FROM ${tableName}
    WHERE id = #{id}
</select>

安全建议

  • 对tableName进行白名单校验
  • 使用正则表达式过滤非法字符
// 校验示例
if (!tableName.matches("[a-zA-Z_0-9]+")) {
    throw new IllegalArgumentException("Invalid table name");
}

5.2 动态排序字段

ORDER BY ${sortField} #{sortOrder}

安全方案

  • 使用枚举限制可排序字段
  • 参数映射转换
public enum SafeSortField {
    CREATE_TIME("create_time"),
    VIEW_COUNT("view_count");
    
    private final String columnName;
    // ...
}

六、综合实战:电商订单查询系统

6.1 需求分析

  • 根据动态条件查询订单
  • 支持多字段排序
  • 分页查询

6.2 安全实现方案

<select id="searchOrders" resultType="Order">
    SELECT * FROM orders
    <where>
        <if test="userId != null">
            user_id = #{userId}
        </if>
        <if test="startDate != null">
            AND create_time >= #{startDate}
        </if>
        <!-- 更多条件... -->
    </where>
    <!-- 安全排序方案 -->
    <if test="sortBy != null">
        ORDER BY 
        <choose>
            <when test="sortBy == 'TIME'">create_time</when>
            <when test="sortBy == 'AMOUNT'">total_amount</when>
            <otherwise>id</otherwise>
        </choose>
        ${sortOrder}
    </if>
    LIMIT #{pageSize} OFFSET #{offset}
</select>

七、灵魂拷问:你真的懂了吗?

  • 当需要动态指定查询列时,应该用哪种占位符?
  • 为什么说使用${}就像在SQL中开eval()?
  • 如何安全地实现动态表头导出功能?

把你的答案写在评论区,抽3位同学送《MyBatis深度解析》电子书!

八、终极总结(保存这张图!)

pie
    title 占位符使用准则
    "#{} 常规参数" : 95%
    "${} 动态表/列" : 4%
    "${} 其他场景" : 1%

三条黄金法则

  • 能用#{}坚决不用${}
  • 必须用${}时要做好过滤
  • 所有用户输入必须使用#{}处理

九、FAQ精选

Q:为什么框架不直接禁用${}?
A:因为确实存在需要SQL动态拼接的场景,框架需要保持灵活性,但安全责任在开发者。

Q:使用#{}会导致性能问题吗?
A:恰恰相反,预编译语句通常会提升性能,因为可以复用执行计划。

Q:如何全局防止滥用?

A:可以通过自定义MyBatis插件,在发现${}时发出警告(需要团队规范配合)。

附录:安全自查清单

  • 项目中没有直接使用${}接收用户输入的案例
  • 所有动态表/列操作都有白名单校验
  • 关键SQL语句都经过安全审核
  • 定期进行SQL注入漏洞扫描

到此这篇关于为什么MyBatis中常用#{}而不使用${}的文章就介绍到这了,更多相关MyBatis 常用#{}内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java Git Commit Message使用规范

    Java Git Commit Message使用规范

    这篇文章主要介绍了Java Git Commit Message使用规范,文章围绕主题展开详细的内容介绍,具有一定的参考价值,感兴趣的小伙伴可以参考一下,希望对你的学习有所帮助
    2022-08-08
  • 如何解决创建maven工程时,产生“找不到插件的错误”问题

    如何解决创建maven工程时,产生“找不到插件的错误”问题

    这篇文章主要介绍了如何解决创建maven工程时,产生“找不到插件的错误”问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • 关于springboot配置文件密文解密方式

    关于springboot配置文件密文解密方式

    这篇文章主要介绍了关于springboot配置文件密文解密方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • 配置tjxCold(idea效率插件)的模版教程详解

    配置tjxCold(idea效率插件)的模版教程详解

    这篇文章主要介绍了配置tjxCold(idea效率插件)的模版教程详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-02-02
  • java代码获取数据库表里数据的总数操作

    java代码获取数据库表里数据的总数操作

    这篇文章主要介绍了java代码获取数据库表里数据的总数操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • Java与Http协议的详细介绍

    Java与Http协议的详细介绍

    这篇文章主要介绍了Java与Http协议的详细介绍的相关资料,这里提供实例来帮助大家学习理解这部分内容,需要的朋友可以参考下
    2017-09-09
  • 浅谈JVM内存溢出原因和解决思路

    浅谈JVM内存溢出原因和解决思路

    本文主要介绍了浅谈JVM内存溢出原因和解决思路,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12
  • java如何判断一个对象是否为空对象

    java如何判断一个对象是否为空对象

    本文主要介绍了java如何判断一个对象是否为空对象,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • Java 图表类库详解

    Java 图表类库详解

    本文主要介绍了Java图表类库的相关知识。具有很好的参考价值,下面跟着小编一起来看下吧
    2017-01-01
  • IDEA教程之Activiti插件图文详解

    IDEA教程之Activiti插件图文详解

    这篇文章主要介绍了IDEA教程之Activiti插件图文详解,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12

最新评论