MyBatis 防止 SQL 注入的正确用法

 更新时间:2026年03月30日 09:19:04   作者:莫寒清  
文章主要讲述了MyBatis防止SQL注入的方法,强调了使用#{param}进行参数绑定,避免使用${param}拼接SQL,对于必须使用的${}需做白名单或使用<choose>枚举替代,并提出了编码规范、代码评审、审计与测试等多层防护建议,感兴趣的朋友一起看看吧

一句话总结

MyBatis 防 SQL 注入的核心是:参数一律使用 #{} 做“参数绑定”(PreparedStatement 的 ? 占位),让“SQL 结构”和“数据”彻底分离;对不得不用 ${} 的 表名/列名/排序 等 SQL 片段,必须做 白名单 或用 <choose> 枚举替代;同时配合代码规范、审计与测试实现多层防护。

1. 先搞清楚:SQL 注入的本质是什么?

SQL 注入并不是“没转义”,而是:把用户输入拼进 SQL 语法结构里,导致输入从“数据”变成“指令”。

因此最可靠的防护手段是:

  • 参数绑定(PreparedStatement):输入永远作为参数值传递,不参与 SQL 语法解析。

2. MyBatis 两种替换方式:#{}vs${}(核心)

写法本质是否安全适用场景
#{param}生成 ? 占位符 + JDBC 参数绑定(TypeHandler)安全WHERE/INSERT/UPDATE 的“值”
${param}文本原样拼接到 SQL(字符串替换)高风险表名/列名/ORDER BY 等“SQL 片段”

结论:

  • 能用 #{} 就绝不用 ${}
  • ${} 只在“无法用 ? 占位”的场景出现,并且必须白名单。

3. 正确用法 1:所有“值”都用#{}(参数绑定)

3.1 基本示例

<select id="findByName" resultType="User">
  SELECT * FROM user WHERE name = #{name}
</select>

SQL 形态:

SELECT * FROM user WHERE name = ?

此时即使输入是 admin' OR '1'='1,也只会作为一个字符串值去匹配,不会改变 SQL 结构。

3.2 关键点补充:TypeHandler / JDBC 类型处理

#{} 会走 MyBatis 的 TypeHandler,由它决定用 setString/setInt/... 绑定参数。

  • 这不是简单“字符串转义”,而是参数绑定
  • 防注入的关键在于“结构与数据分离”。

4. 正确用法 2:动态 SQL 标签是安全的(前提是内部仍用#{})

MyBatis 的动态 SQL(<if> / <where> / <trim> / <choose>)只是动态组装 SQL 结构,并不等于把用户输入拼进 SQL。

只要条件值仍用 #{},就是安全的:

<select id="query" resultType="User">
  SELECT * FROM user
  <where>
    <if test="name != null and name != ''">
      AND name = #{name}
    </if>
    <if test="age != null">
      AND age = #{age}
    </if>
  </where>
</select>

5. 常见攻击面与正确写法

5.1LIKE模糊查询

推荐(仍用 #{}):

WHERE name LIKE CONCAT('%', #{name}, '%')

不要用:

WHERE name LIKE '%${name}%'

注入后果示例:
若用户输入 name' OR '1'='1,生成的 SQL 将变为:
WHERE name LIKE '%' OR '1'='1'%'
由于 '1'='1' 恒成立,该查询会绕过名称过滤,直接返回全表数据,导致敏感信息泄露。

5.2IN查询(批量参数)

使用 <foreach> 生成多个 #{}

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

不要把整段 ids 字符串塞进 ${}

风险/后果:
如果把用户输入的 ids 直接拼成 IN (${ids}),攻击者可构造如 1) OR 1=1 --,最终 SQL 可能变为:
WHERE id IN (1) OR 1=1 --)
从而导致 IN 条件被绕过、返回全表数据,甚至在支持多语句的配置下进一步造成更严重的破坏。

5.3 分页参数

分页参数也属于“值”,用 #{}

SELECT * FROM user LIMIT #{offset}, #{pageSize}

6. 不得不用${}的场景:必须白名单/枚举替代

6.1 为什么这些场景不能用#{}?

JDBC 的 ? 占位符只能替换,不能替换:

  • 表名、列名
  • ORDER BY 的字段
  • ASC/DESC 关键字

因此这些场景可能出现 ${}

6.2 推荐做法:用<choose>枚举排序字段(避免透传${sortField})

<select id="findUsers" resultType="User">
  SELECT * FROM user
  <choose>
    <when test="sortField == 'age'">ORDER BY age</when>
    <when test="sortField == 'name'">ORDER BY name</when>
    <otherwise>ORDER BY id</otherwise>
  </choose>
  <choose>
    <when test="sortOrder == 'DESC'">DESC</when>
    <otherwise>ASC</otherwise>
  </choose>
</select>

6.3 业务侧白名单映射(如果必须用${})

  • 输入只允许来自固定集合(枚举/Map 映射),严禁直接使用用户原始字符串。
  • 对排序字段、表名、列名做正向校验(white list),不要做“黑名单过滤”。

7. 常见误区(纠错点)

  1. 误区:#{} 是“自动转义”所以安全
    • 更准确:#{}参数绑定(结构与数据分离),不是靠转义。
  2. 误区:动态 SQL 一定安全
    • 动态标签本身安全,但如果你在标签里用 ${} 拼接用户输入,一样会注入。
  3. 误区:把 '${name}' 包上引号就安全
    • 仍是拼接,依然可能被构造输入突破。

8. 多层防护(生产建议)

仅靠 ORM 习惯不够,建议组合:

  • 编码规范:禁止 ${} 透传用户输入;代码评审强制检查
  • 参数校验:对排序字段、表名等做枚举/白名单校验
  • 审计与测试:单测/集成测试加入注入 payload(如 "' OR '1'='1"
  • 最小权限:DB 账号权限最小化(禁止高危权限)
  • 可选:WAF/SQL 防火墙/数据库审计(兜底)

9. 面试速答

  • Q:MyBatis 如何防 SQL 注入?
    • A:对值使用 #{} 走 PreparedStatement 参数绑定;对必须拼接的 SQL 片段(表名/列名/排序)使用白名单或 <choose> 枚举,禁止 ${} 透传用户输入。
  • Q:${} 为什么危险?
    • A:它是文本替换,输入会进入 SQL 结构,可能变成指令。
  • Q:IN / LIKE 怎么写才安全?
  • A:IN<foreach> + #{}LIKECONCAT('%', #{x}, '%')

到此这篇关于MyBatis 防止 SQL 注入的正确用法的文章就介绍到这了,更多相关MyBatis 防止 SQL 注入内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java 开启多线程常见的4种方法

    Java 开启多线程常见的4种方法

    本文主要介绍了Java 开启多线程常见的4种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • SpringBoot集成XXL-JOB实现灵活控制的分片处理方案

    SpringBoot集成XXL-JOB实现灵活控制的分片处理方案

    因为需要并行处理同一张数据表里的数据,所以比较自然地想到了分片查询数据,可以利用对 id 取模的方法进行分片,避免同一条数据被重复处理,所以本文给大家介绍了SpringBoot集成XXL-JOB实现灵活控制的分片处理方案,需要的朋友可以参考下
    2024-09-09
  • 编写android拨打电话apk应用实例代码

    编写android拨打电话apk应用实例代码

    这篇文章主要介绍了编写android拨打电话apk应用实例代码,十分的实用,这里分享给大家,有需要的小伙伴可以参考下
    2015-04-04
  • IDEA中创建maven项目webapp目录无法识别即未被标识的解决办法

    IDEA中创建maven项目webapp目录无法识别即未被标识的解决办法

    在学习SpringMVC课程中,基于IDEA新建maven项目模块后,webapp目录未被标识,即没有小蓝点的图标显示,所以本文给大家介绍了IDEA中创建maven项目webapp目录无法识别即未被标识的解决办法,需要的朋友可以参考下
    2024-03-03
  • 使用开源项目JAVAE2 进行视频格式转换

    使用开源项目JAVAE2 进行视频格式转换

    这篇文章主要介绍了使用开源项目JAVAE 进行视频格式转换,帮助大家更好的利用Java处理视频,完成自身需求,感兴趣的朋友可以了解下
    2020-11-11
  • 浅谈Java接口响应速度优化

    浅谈Java接口响应速度优化

    在Java开发中,接口响应速度直接影响用户体验和系统吞吐量,优化接口性能需要从代码、数据库、缓存、架构等多个维度综合考量,下面就来具体了解一下
    2025-08-08
  • Java使用字节流复制文件的方法

    Java使用字节流复制文件的方法

    这篇文章主要为大家详细介绍了Java使用字节流复制文件的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-05-05
  • 在Mac系统上安装Java和IntelliJ IDEA完整笔记

    在Mac系统上安装Java和IntelliJ IDEA完整笔记

    IntelliJ IDEA的智能代码辅助功能堪称一绝,它具备强大的代码自动补全能力,能依据上下文精准推测开发者的意图,提供极为准确的代码建议,这篇文章主要介绍了在Mac系统上安装Java和IntelliJ IDEA的相关资料,需要的朋友可以参考下
    2026-02-02
  • Jmeter命令行执行脚本如何设置动态参数

    Jmeter命令行执行脚本如何设置动态参数

    这篇文章主要介绍了Jmeter命令行执行脚本如何设置动态参数,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • Spring Cache 集成 Caffeine实现项目缓存的示例

    Spring Cache 集成 Caffeine实现项目缓存的示例

    本文主要介绍了Spring Cache 集成 Caffeine实现项目缓存的示例,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12

最新评论