在 MyBatis 中#{}和${}都是用来传递参数的但它们在处理方式、安全性和使用场景上有本质区别。⚙️ 核心区别预编译 vs. 直接替换这是两者最根本的不同理解这一点就理解了其他所有区别。#{}预编译处理安全MyBatis 会将#{}解析为 SQL 预编译语句中的占位符?。执行时再通过PreparedStatement将参数值安全地设置到该占位符上。可以理解为一种“参数化查询”。${}字符串直接替换不安全MyBatis 会直接进行字符串替换将${}内的内容直接拼接到 SQL 语句中。可以理解为一种“字符串拼接”。下面是两种方式在处理同一个查询时的对比特征#{}(井号)${}(美元符)处理方式预编译作为占位符?直接字符串替换SQL 示例WHERE id #{userId}WHERE id ${userId}最终 SQLWHERE id ?WHERE id 1主要风险无SQL 注入风险高风险易受 SQL 注入攻击性能更高可缓存预编译 SQL更低每次需重新编译引号处理自动为字符串等类型加引号不自动加引号需手动处理适用场景绝大多数参数传递如WHERE条件值少数需动态替换表名、列名、ORDER BY等 SQL 结构时⚠️ 安全性SQL 注入风险是关键这是两者最重要的区别直接关系到应用的安全。#{}是安全的因为它使用预编译将参数视为数据而非代码的一部分。即使参数中包含 SQL 语句也只会被当作普通字符串处理从而有效防止 SQL 注入。${}是不安全的因为它直接进行字符串拼接将用户输入作为代码的一部分嵌入 SQL。如果参数被恶意构造如 OR 11就可能被注入恶意 SQL 代码导致数据泄露或被篡改。 性能差异#{}采用预编译SQL 语句只需编译一次后续执行可复用效率更高。而${}每次执行都需要重新解析、编译性能相对较低。 使用场景建议优先使用#{}这是默认且推荐的做法适用于 99% 的场景如传递WHERE条件中的值、INSERT语句中的字段值等。谨慎使用${}仅在无法使用#{}时使用且必须确保参数来源绝对安全比如来自系统内部常量而非用户输入。动态表名或列名例如SELECT * FROM ${tableName}。ORDER BY或GROUP BY后的排序字段例如ORDER BY ${columnName}。 总结为了便于记忆可以这样理解#{}是安全卫士负责预编译和防止 SQL 注入是日常开发的首选。${}是万能胶能直接拼接任何文本虽然灵活但风险极高仅在无法使用#{}的特殊场景下使用且必须确保数据安全。除了我们刚才讨论的#{}和${}MyBatis 面试中还有几个出现频率非常高的经典问题。我为你梳理了其中最重要的几个并附上了简要的解答思路你可以参考一下。 核心原理与组件核心组件有哪些面试官想考察你是否了解 MyBatis 的整体架构你可以从以下几个核心组件回答SqlSessionFactoryBuilder负责解析配置构建SqlSessionFactory。SqlSessionFactory核心工厂类用于创建SqlSession对象。SqlSession数据库会话对象代表一次连接提供CRUD操作API。注意它是线程不安全的。Mapper接口自定义的DAO接口MyBatis会通过动态代理生成其实现类。Executor执行器MyBatis的核心调度引擎负责SQL解析、参数处理和结果映射。Mapper接口的工作原理核心是动态代理Mapper接口没有实现类。MyBatis会使用JDK动态代理为接口生成代理对象。定位SQL代理对象会拦截接口方法调用通过“接口全限定名 方法名”作为唯一Key去定位对应的MappedStatement即XML中的SQL并执行。方法重载因为定位SQL的Key是“接口名方法名”不包含参数所以Mapper接口中的方法不能重载。执行流程是怎样的这是一个考察完整流程的问题可以按以下步骤回答加载配置SqlSessionFactoryBuilder读取全局配置和映射文件创建SqlSessionFactory。创建会话通过SqlSessionFactory打开一个SqlSession。获取代理通过SqlSession的getMapper方法获取 Mapper 接口的动态代理对象。执行SQL调用 Mapper 方法代理对象会委托给Executor执行。返回结果Executor调用StatementHandler等组件完成SQL执行和结果映射最终返回。与Hibernate的区别可以从ORM类型、SQL控制度、学习成本、适用场景等维度对比MyBatis半自动ORMSQL手动编写灵活可控适合复杂SQL、对性能要求高的互联网项目。Hibernate全自动ORMSQL自动生成难以优化复杂SQL适合简单CRUD、快速开发的企业级应用。 高级特性与优化动态SQL支持哪些标签这题考察你对常用标签的熟悉程度可以分为两类回答:常用标签if,choose (when, otherwise),foreach,where,set,trim。其他标签resultMap,sql(定义可重用SQL片段),include(引用SQL片段),selectKey(用于不支持自增主键的数据库)。分页原理是什么MyBatis的分页主要有两种方式:RowBounds逻辑分页在内存中对查询结果集进行截取数据量大时性能差。分页插件物理分页最推荐的方式。原理是基于MyBatis的插件Interceptor机制。在SQL执行前拦截根据数据库方言重写SQL添加对应的物理分页语句如LIMIT。缓存机制是怎样的MyBatis有两级缓存:一级缓存默认开启SqlSession级别。在同一个会话中执行相同SQL会直接从缓存返回结果。二级缓存需手动开启Mapper(NameSpace) 级别。作用范围更大可以在多个SqlSession之间共享。可通过cache标签配置。如何解决 SqlSession 线程不安全问题核心原则每个线程都应使用独立的SqlSession实例。最佳实践在 Web 应用中通常结合ThreadLocal或 Spring 框架的事务管理来保证每个线程拥有自己的SqlSession从而避免线程安全问题。 其他高频考点谈谈对ORM的理解即对象关系映射Object Relational Mapping用于解决面向对象与关系数据库不匹配的问题。MyBatis的启动过程可以描述为加载配置 - 创建SqlSessionFactory- 加载映射文件 - 初始化 Mapper 接口等步骤。插件Interceptor运行原理MyBatis允许在Executor、StatementHandler等核心组件处进行拦截。原理是使用责任链模式通过动态代理层层包装目标对象。面试时除了说清楚概念如果能结合一两个你实际项目中的使用场景比如用动态SQL解决过多条件查询或用分页插件优化过接口性能来回答会让你的表现更出彩。上面整理的这些高频题里有哪个是你目前最没把握、需要我再详细展开讲讲原理的吗或者你想针对其中某一个看看具体的代码示例