面试知识库
基础

MyBatis-#与$区别#

一句话答案#

#{} 是预编译参数(PreparedStatement,防 SQL 注入),${} 是字符串拼接(有注入风险),值用 #{} 结构用 ${}。

核心要点

#{}: PreparedStatement 占位符,会加引号,防 SQL 注入 ${}: 直接拼接字符串,不加引号,适合动态表名/列名/ORDER BY

规则: 值用 #{},结构(表名/列名/排序)用 ${} 但需白名单校验

面试回答(2分钟版)

MyBatis 中 #{} 和 ${} 的核心区别在于参数处理方式。#{} 使用 PreparedStatement 的参数占位符,MyBatis 会将参数值设置到预编译语句的 ? 位置,自动加引号并进行类型转换,从根本上防止了 SQL 注入,因为参数值不会参与 SQL 语法解析。${} 则是直接将参数值拼接到 SQL 字符串中,不做任何转义处理,存在 SQL 注入风险。实际开发中的原则是:传递数据值时一律用 #{},比如 WHERE id = #{id};但涉及 SQL 结构性元素如动态表名、列名和 ORDER BY 排序字段时必须用 ${},因为这些位置不能用预编译占位符。使用 ${} 时必须在代码层做白名单校验,比如只允许 create_time、update_time 等预定义字段名传入,绝不能直接拼接用户输入。底层实现上,#{} 对应 JDBC 的 PreparedStatement.setXxx 方法,${} 对应字符串替换后直接执行 Statement。

追问与易错

追问方向:

  • “ORDER BY 能用 #{} 吗?”→ 不能,#{} 会加引号变成字符串如 ORDER BY ‘create_time’,SQL 语法错误;排序字段属于 SQL 结构必须用 ${} 并做白名单校验
  • ”${} 怎么防注入?”→ 在代码层做白名单校验,只允许预定义的合法值(如枚举/Map 映射),绝不能直接拼接用户输入;也可用 MyBatis 的 标签替代动态拼接
  • ”#{} 底层怎么实现?”→ 对应 JDBC 的 PreparedStatement,MyBatis 将 #{} 替换为 ? 占位符,再通过 ParameterHandler 调用 setXxx 方法设置参数值,参数不参与 SQL 语法解析

易错点:

  • ❌ 所有地方都用 #{}——表名列名排序必须用 ${}
  • ❌ ${} 只有安全问题——还有引号处理问题