java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > MyBatis 常用#{}

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

作者: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注入:程序员的午夜凶铃(代码级演示)

3.1 模拟黑客攻击场景

假设有个登录查询:

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

黑客输入:

生成的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>

安全建议

// 校验示例
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>

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

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

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

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

三条黄金法则

九、FAQ精选

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

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

Q:如何全局防止滥用?

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

附录:安全自查清单

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

您可能感兴趣的文章:
阅读全文