1.1 java面试题: mybatis-plus相关

在 MyBatis 中,#{}${} 都是用来传递参数的,但它们在处理方式、安全性和使用场景上有本质区别。

⚙️ 核心区别:预编译 vs. 直接替换

这是两者最根本的不同,理解这一点就理解了其他所有区别。

  • #{}:预编译处理(安全)

    • MyBatis 会将 #{} 解析为 SQL 预编译语句中的占位符 ?
    • 执行时,再通过 PreparedStatement 将参数值安全地设置到该占位符上。
    • 可以理解为一种"参数化查询"。
  • ${}:字符串直接替换(不安全)

    • MyBatis 会直接进行字符串替换,将 ${} 内的内容直接拼接到 SQL 语句中。
    • 可以理解为一种"字符串拼接"。

下面是两种方式在处理同一个查询时的对比:

特征 #{} (井号) ${} (美元符)
处理方式 预编译,作为占位符 ? 直接字符串替换
SQL 示例 WHERE id = #{userId} WHERE id = ${userId}
最终 SQL WHERE id = ? WHERE id = 1
主要风险 SQL 注入风险 高风险,易受 SQL 注入攻击
性能 更高,可缓存预编译 SQL 更低,每次需重新编译
引号处理 自动为字符串等类型加引号 不自动加引号,需手动处理
适用场景 绝大多数 参数传递(如 WHERE 条件值) 少数 需动态替换表名、列名、ORDER BY 等 SQL 结构时

⚠️ 安全性:SQL 注入风险是关键

这是两者最重要的区别,直接关系到应用的安全。

  • #{} 是安全的 :因为它使用预编译,将参数视为数据而非代码的一部分。即使参数中包含 SQL 语句,也只会被当作普通字符串处理,从而有效防止 SQL 注入。

  • ${} 是不安全的 :因为它直接进行字符串拼接,将用户输入作为代码 的一部分嵌入 SQL。如果参数被恶意构造(如 ' OR '1'='1),就可能被注入恶意 SQL 代码,导致数据泄露或被篡改。

🚀 性能差异

#{} 采用预编译,SQL 语句只需编译一次,后续执行可复用,效率更高。而 ${} 每次执行都需要重新解析、编译,性能相对较低。

🎯 使用场景建议

  • 优先使用 #{} :这是默认且推荐的做法,适用于 99% 的场景,如传递 WHERE 条件中的值、INSERT 语句中的字段值等。

  • 谨慎使用 ${} :仅在无法使用 #{} 时使用,且必须确保参数来源绝对安全,比如来自系统内部常量,而非用户输入。

    • 动态表名或列名 :例如 SELECT * FROM ${tableName}
    • ORDER BYGROUP 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接口中的方法不能重载
  • 执行流程是怎样的?

    • 这是一个考察完整流程的问题,可以按以下步骤回答:
      1. 加载配置SqlSessionFactoryBuilder 读取全局配置和映射文件,创建 SqlSessionFactory
      2. 创建会话 :通过 SqlSessionFactory 打开一个 SqlSession
      3. 获取代理 :通过 SqlSessiongetMapper 方法,获取 Mapper 接口的动态代理对象
      4. 执行SQL :调用 Mapper 方法,代理对象会委托给 Executor 执行。
      5. 返回结果Executor 调用 StatementHandler 等组件完成SQL执行和结果映射,最终返回。
  • 与Hibernate的区别?

    • 可以从 ORM类型、SQL控制度、学习成本、适用场景 等维度对比:
      • MyBatis半自动ORM,SQL手动编写,灵活可控,适合复杂SQL、对性能要求高的互联网项目。
      • Hibernate全自动ORM,SQL自动生成,难以优化复杂SQL,适合简单CRUD、快速开发的企业级应用。

🚀 高级特性与优化

  • 动态SQL支持哪些标签?

    • 这题考察你对常用标签的熟悉程度,可以分为两类回答:
      • 常用标签<if>, <choose> (when, otherwise), <foreach>, <where>, <set>, <trim>
      • 其他标签<resultMap>, <sql> (定义可重用SQL片段), <include> (引用SQL片段), <selectKey> (用于不支持自增主键的数据库)。
  • 分页原理是什么?

    • MyBatis的分页主要有两种方式:
      1. RowBounds 逻辑分页 :在内存中对查询结果集进行截取,数据量大时性能差。
      2. 分页插件(物理分页) :最推荐的方式。原理是基于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允许在 ExecutorStatementHandler 等核心组件处进行拦截。原理是使用责任链模式,通过动态代理层层包装目标对象。

面试时,除了说清楚概念,如果能结合一两个你实际项目中的使用场景(比如用动态SQL解决过多条件查询,或用分页插件优化过接口性能)来回答,会让你的表现更出彩。

上面整理的这些高频题里,有哪个是你目前最没把握、需要我再详细展开讲讲原理的吗?或者你想针对其中某一个,看看具体的代码示例?