深入分析mybatis中#{}和${}的区别

知其然要知其所以然,探索每一个知识点背后的意义,你知道的越多,你不知道的越多,一起学习,一起进步,如果文章感觉对您有用的话,关注、收藏、点赞,有困惑的地方请评论,我们一起交流!


深入分析MyBatis中#{}和${}的区别

1. 核心区别概述

特性 #{} ${}
处理方式 预编译参数(占位符) 直接文本替换(字符串拼接)
安全性 防止SQL注入 存在SQL注入风险
适用场景 动态参数值(如WHERE条件) 动态SQL片段(如表名、列名)
数据类型处理 自动类型转换(如String→VARCHAR) 原样替换(需手动处理类型)

2. 底层原理剖析

(1)#{}的工作流程
  1. 解析阶段 :
    MyBatis将#{}转换为JDBC的PreparedStatement占位符(?)。

  2. 执行阶段 :
    通过ps.setXxx()方法安全地设置参数值,例如:

    java 复制代码
    // 生成的JDBC代码
    PreparedStatement ps = conn.prepareStatement("SELECT * FROM user WHERE id = ?");
    ps.setInt(1, 5);  // 自动类型安全处理
(2)${}的工作流程
  1. 解析阶段 :
    MyBatis直接替换${}为字符串内容(无占位符)。

  2. 执行阶段 :
    生成静态SQL语句,例如:

    java 复制代码
    // 假设${tableName}的值为"user"
    Statement stmt = conn.createStatement("SELECT * FROM user");  // 直接拼接

3. 安全性与SQL注入

  • #{}示例(安全):

    xml 复制代码
    <select id="getUser" resultType="User">
        SELECT * FROM user WHERE name = #{name}
    </select>
    • 输入name = "admin' OR '1'='1"时,会被转义为:

      sql 复制代码
      SELECT * FROM user WHERE name = 'admin'' OR ''1''=''1'
  • ${}示例(危险):

    xml 复制代码
    <select id="getUser" resultType="User">
        SELECT * FROM user WHERE name = '${name}'
    </select>
    • 输入name = "admin' OR '1'='1"时,直接拼接为:

      sql 复制代码
      SELECT * FROM user WHERE name = 'admin' OR '1'='1'  -- 返回所有数据!

4. 性能对比

维度 #{} ${}
SQL解析 需预编译(首次稍慢) 直接替换(无预编译开销)
数据库缓存 可复用执行计划(高效) 每次生成新SQL(无法复用)
网络传输 仅传输参数(体积小) 传输完整SQL(体积大)

5. 适用场景与反例

✅ #{}的正确使用
  • 动态条件值 :

    xml 复制代码
    <select id="findUsers" resultType="User">
        SELECT * FROM user 
        WHERE status = #{status} AND create_time > #{createTime}
    </select>
✅ ${}的合理使用
  • 动态表名/列名 (需手动过滤危险字符):

    xml 复制代码
    <select id="queryFromTable" resultType="map">
        SELECT * FROM ${tableName} 
        WHERE ${columnName} = #{value}  <!-- 混合使用 -->
    </select>
❌ ${}的错误使用
  • 直接拼接用户输入 :

    xml 复制代码
    <!-- 危险!可能引发SQL注入 -->
    <select id="login" resultType="User">
        SELECT * FROM user 
        WHERE username = '${username}' AND password = '${password}'
    </select>

6. 特殊场景处理

(1)LIKE查询
  • 安全写法 (使用#{} + CONCAT):

    xml 复制代码
    <select id="searchUsers" resultType="User">
        SELECT * FROM user 
        WHERE name LIKE CONCAT('%', #{keyword}, '%')
    </select>
(2)IN语句
  • 动态参数列表 (结合MyBatis的foreach):

    xml 复制代码
    <select id="getUsersByIds" resultType="User">
        SELECT * FROM user 
        WHERE id IN 
        <foreach item="id" collection="ids" open="(" separator="," close=")">
            #{id}
        </foreach>
    </select>

7. 最佳实践总结

  1. 默认使用#{} :
    所有动态参数值必须用#{},避免SQL注入。

  2. 谨慎使用${} :
    仅用于动态SQL片段(如表名、ORDER BY子句),且需手动过滤危险字符:

    java 复制代码
    // 在Java代码中校验表名合法性
    if (!isValidTableName(tableName)) {
        throw new IllegalArgumentException("Invalid table name");
    }
  3. 混合使用策略 :
    在需要动态SQL结构时组合两者:

    xml 复制代码
    ORDER BY ${sortColumn} #{sortDirection}

相关推荐
captain3762 分钟前
MyBatis操作数据库(入门2)
java·数据库·spring boot·后端·spring·mybatis
心中有你021423 分钟前
Java实现Dijkstra最短路径算法(邻接表 + 优先队列)
java·数据库·算法
步行cgn1 小时前
Spring MVC + Thymeleaf 模板解析失败:从 500 错误到页面正常显示
java·spring·mvc
IT_陈寒1 小时前
Java序列化踩坑记:对象循环引用让我加班两小时
前端·人工智能·后端
每天进步一点点km1 小时前
自用-牛客网-华为机试编程模拟题1-python
华为od·华为·面试
lgcgkCQ2 小时前
【AI 转型实战 1】SpringAI 入门:搭建基础大模型对话能力
java·ai·agent·rag·spring ai·mcp·langchain4j
泡海椒2 小时前
05-雷达图多维画像-信用评级多维画像
后端
王中阳Go2 小时前
5.27 分钟干完后端和全部测试:一场三小时笔试的五个失分点,四个不在代码上
大数据·数据库·人工智能·后端·大语言模型
大哥43092 小时前
Modbus 的"字序":一个配错就变成天文数字,或者干脆变成 0
后端
Wang's Blog2 小时前
Java 项目实战:交友项目 - 用户体系集成总结
java·开发语言·交友