回答
#{}
和${}
是 MyBatis 两种不同的占位符,#{}
为预编译处理,而${}
是字符串替换。
#{} | ${} | |
---|---|---|
用途 | 传递参数,防止 SQL 注入 | 传递参数,不能防止 SQL 注入 |
实现机制 | MyBatis 将#{} 中的内容替换为 ? ,并在运行时通过PreparedStatement 的 set 方法设置参数值。 |
MyBatis 将${} 中的内容直接替换为参数值,即将参数直接拼接到 SQL 字符串中。 |
安全性 | 安全性高,可以有效防止 SQL 注入。 | 存在安全隐患,直接拼接 SQL 字符串容易导致 SQL 攻击。 |
使用场景
#{}
用在动态 SQL 参数赋值,防止 SQL 注入。
@Select("SELECT * FROM t_user where id = #{id}")
User selectUserById(@Param("id") Long id);
// #{} 解析替换为 ? ===> SELECT * FROM t_user where id = ?
而${}
用在需将参数直接拼接在 SQL 的场景(如动态表名、列名、排序)。如按月对订单表拆分后的查询场景
SELECT * FROM ${table_name} where create_time >= #{createTime}
// 当订单按月分表后,可根据日期拼接表名(如 order_202407)再查询数据。
扩展
#{}和 ${}如何被解析?
- SqlSource 接口及其实现
DynamicSqlSource
:处理动态 SQL 语句(一是包含$
占位符的表达式,二是包含9种动态标签中的任何一个)。
public BoundSql getBoundSql(Object parameterObject) {
// 动态上下文
DynamicContext context = new DynamicContext(configuration, parameterObject);
// 动态SQL标签处理,解析静态SQL
rootSqlNode.apply(context);
// sql源解析器
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
// 解析为StaticSqlSource(生成预处理SQL)
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
// 参数token处理器(通过方法handleToken将#{}替换为?)
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
// 定义通用解析器
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql;
// 动态 SQL 解析,替换 #{} 为 ?
if (configuration.isShrinkWhitespacesInSql()) {
sql = parser.parse(removeExtraWhitespaces(originalSql));
} else {
sql = parser.parse(originalSql);
}
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
RawSqlSource
:不是DynamicSqlSource,便会被解析为 RawSqlSource。
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
// sql源解析器
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
// 解析为StaticSqlSource(生成预处理SQL)
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}
- PropertyParser:处理
${}
的变量替换。 - Mybatis 将 XML 定义的 SQL 包装
XNode
。解析时,Mybatis 调用了内部函数parseAttributes()
和parseBody()
,这两个方法内部调用了静态方法PropertyParser#parse
来解析每一个 Node 节点属性。
private Properties parseAttributes(Node n) {
Properties attributes = new Properties();
// 遍历 Node 节点的属性
NamedNodeMap attributeNodes = n.getAttributes();
if (attributeNodes != null) {
for (int i = 0; i < attributeNodes.getLength(); i++) {
Node attribute = attributeNodes.item(i);
// 使用 PropertyParser 解析占位符值
String value = PropertyParser.parse(attribute.getNodeValue(), variables);
attributes.put(attribute.getNodeName(), value);
}
}
return attributes;
}
public static String parse(String string, Properties variables) {
// 内部类实现 tokenHandler,支持 ${key:default} 形式的解析
VariableTokenHandler handler = new VariableTokenHandler(variables);
// 使用 GenericTokenParser 来解析占位符属性
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。