高 基础
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 语法解析
易错点:
- ❌ 所有地方都用 #{}——表名列名排序必须用 ${}
- ❌ ${} 只有安全问题——还有引号处理问题