2024-12-22  阅读(119)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/mianshi/baodian/detail/1543240893

回答

#{}${}是 MyBatis 两种不同的占位符,#{} 为预编译处理,而${} 是字符串替换。

#{} ${}
用途 传递参数,防止 SQL 注入 传递参数,不能防止 SQL 注入
实现机制 MyBatis 将#{}中的内容替换为 ?,并在运行时通过PreparedStatementset 方法设置参数值。 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());
}

Mybatis 支持动态 SQL 吗?

  • 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] ,回复【面试题】 即可免费领取。

阅读全文