Administrator 12 阅读 面试

Spring + MyBatis 高频面试题(含口述版回答)


1. 什么是 MyBatis?和 Hibernate 有什么区别?

口述版:

MyBatis 是一个半自动的 ORM 框架,重点在于你自己写 SQL,它负责把结果映射成 Java 对象。

和 Hibernate 的主要区别:

对比点 MyBatis Hibernate
SQL 控制 自己写 SQL,灵活 自动生成,复杂 SQL 难优化
学习成本 较低 较高
性能优化 直接优化 SQL,直观 需要理解缓存、抓取策略等
映射方式 结果映射为主 全对象关系映射
适用场景 SQL 复杂、需要精细控制 业务标准、快速开发

国内互联网公司更常用 MyBatis(或 MyBatis-Plus),因为 SQL 可控性更强,优化起来直观。


2. #{} 和 ${} 有什么区别?

口述版:

这是必考题。

  • #{}:预编译,参数会以占位符 ? 的形式传入,MyBatis 会做预处理,有效防止 SQL 注入。优先使用。
  • ${}:直接字符串拼接,没有预编译,存在 SQL 注入风险。一般只用于传入表名、列名等动态字段,且这些值不能来自用户输入。

简单记:能用 #{} 就用 #{},只有在必须动态拼接 SQL 结构时才用 ${},并做好白名单校验。


3. MyBatis 的一级缓存和二级缓存是什么?

口述版:

一级缓存(Local Cache):

  • 默认开启,作用域是 SqlSession。
  • 同一个 SqlSession 里,相同的查询会走缓存。
  • SqlSession 关闭或清空后缓存就没了。
  • Spring 集成后,通常每次请求一个 SqlSession,所以一级缓存作用有限。

二级缓存(Second Level Cache):

  • 作用域是 Mapper 级别(同一个 namespace)。
  • 需要手动开启(全局配置 + Mapper 上配置)。
  • 可以跨 SqlSession 共享。
  • 默认是不开启的,实际项目中用得相对少,更多用 Redis 做业务缓存。

注意:二级缓存有脏读风险,尤其是在多表关联更新时,使用要谨慎。


4. MyBatis 执行一次查询的完整流程是什么?

口述版:

大致流程:

  1. 读取配置文件 / 注解,创建 SqlSessionFactory。
  2. 通过 SqlSessionFactory 打开一个 SqlSession。
  3. 从 SqlSession 中获取 Mapper 接口的代理对象。
  4. 调用 Mapper 方法,代理对象根据方法签名找到对应的 MappedStatement。
  5. 通过 Executor 执行(根据配置可能是 Simple、Reuse、Batch)。
  6. 参数映射(ParameterHandler)把 Java 对象转成 JDBC 参数。
  7. 执行 SQL,得到 ResultSet。
  8. 结果映射(ResultSetHandler)把结果转成 Java 对象。
  9. 返回结果,关闭资源。

核心组件:SqlSessionFactory、SqlSession、Executor、MappedStatement、ParameterHandler、ResultSetHandler。


5. Mapper 接口是怎么工作的?为什么没有实现类也能运行?

口述版:

Mapper 接口是通过 JDK 动态代理 实现的。

启动时 MyBatis 会为每个 Mapper 接口创建一个代理对象。调用接口方法时,代理对象会:

  1. 根据方法名和参数找到对应的 XML 或注解里的 SQL
  2. 把调用转发给 SqlSession 去执行真正的数据库操作

所以我们只写接口,不需要写实现类,运行时 MyBatis 自动生成代理完成工作。这也是为什么方法签名必须和 XML 里的 id 对应上。


6. resultType 和 resultMap 有什么区别?什么时候用 resultMap?

口述版:

  • resultType:直接指定返回结果的类型(实体类全限定名或别名)。字段名和属性名能对应上(或开启驼峰转换)时使用,简单方便。
  • resultMap:自定义结果映射规则,可以处理:
    • 字段名和属性名不一致
    • 一对一、一对多关联查询
    • 复杂映射、鉴别器等

使用建议:

  • 单表简单查询优先用 resultType
  • 多表关联、字段映射复杂时用 resultMap

7. MyBatis 如何获取自增主键?

口述版:

常用两种方式:

1. 使用 useGeneratedKeys(推荐,适合 MySQL):

<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    insert into user(name, age) values(#{name}, #{age})
</insert>

插入后主键会自动回填到对象的 id 属性上。

2. 使用 <selectKey>(兼容性更好,适合 Oracle 等):

<insert id="insertUser">
    <selectKey keyProperty="id" resultType="long" order="BEFORE">
        SELECT SEQ_USER.NEXTVAL FROM DUAL
    </selectKey>
    insert into user(id, name) values(#{id}, #{name})
</insert>

8. 动态 SQL 有哪些常用标签?

口述版:

MyBatis 动态 SQL 是它的强项,常用标签:

  • <if>:条件判断
  • <where>:自动处理 WHERE 和多余的 AND/OR
  • <set>:用于更新语句,自动去掉末尾逗号
  • <choose> <when> <otherwise>:类似 switch-case
  • <foreach>:遍历集合,常用于 IN 查询和批量插入
  • <trim>:更灵活的前缀后缀处理
  • <sql> + <include>:SQL 片段复用

其中 <foreach> 和 <where> 使用频率最高。


9. Spring 是怎么整合 MyBatis 的?

口述版:

主流整合方式(以 Spring Boot 为例):

  1. 引入 mybatis-spring-boot-starter 依赖。
  2. 配置数据源(application.yml)。
  3. 配置 MyBatis 相关属性(mapper 位置、类型别名、驼峰等)。
  4. 在启动类或配置类上加 @MapperScan("com.xxx.mapper"),扫描 Mapper 接口。
  5. 或者在每个 Mapper 接口上加 @Mapper。

底层关键是:

  • Spring 管理 SqlSessionFactory 和数据源
  • 通过 MapperFactoryBean 或扫描方式,把 Mapper 接口注册成 Spring Bean
  • 利用 Spring 的事务管理器(@Transactional)统一管理事务

10. @Mapper 和 @MapperScan 有什么区别?

口述版:

  • @Mapper:加在单个 Mapper 接口上,声明这是一个 Mapper,Spring 会为它创建代理 Bean。
  • @MapperScan:加在配置类或启动类上,批量扫描指定包下的所有 Mapper 接口,一次性注册。

项目中 Mapper 多的时候,用 @MapperScan 更方便,不用每个接口都加 @Mapper。两者可以同时存在。


11. MyBatis 的懒加载是什么?怎么开启?

口述版:

懒加载(延迟加载)主要用于关联查询:先查主表,等真正访问关联对象时才去查从表。

开启方式:

  1. 全局配置 lazyLoadingEnabled=true
  2. 在关联映射上设置 fetchType="lazy"(association / collection)

注意:

  • 懒加载依赖动态代理,序列化时可能出问题
  • 容易引发 N+1 查询问题
  • 实际项目中很多团队会关掉懒加载,用手动关联或业务层控制

12. MyBatis 插件(Interceptor)原理是什么?

口述版:

MyBatis 插件采用责任链 + JDK 动态代理实现。

可以拦截的四大对象:

  • Executor
  • ParameterHandler
  • ResultSetHandler
  • StatementHandler

实现步骤:

  1. 实现 Interceptor 接口
  2. 使用 @Intercepts + @Signature 标注要拦截的方法
  3. 在 intercept 方法里做逻辑(可调用 invocation.proceed() 放行)
  4. 在配置文件或 Spring 中注册插件

常见用途:分页插件(PageHelper)、SQL 性能监控、数据权限过滤、审计日志等。


13. 批量插入/更新怎么做比较高效?

口述版:

几种常见方式:

  1. <foreach> 拼接多值插入(适合数据量不是特别大):

    insert into user(name, age) values
    <foreach collection="list" item="item" separator=",">
        (#{item.name}, #{item.age})
    </foreach>
    
  2. ExecutorType.BATCH: 通过 SqlSessionFactory.openSession(ExecutorType.BATCH) 获取 Batch 模式的 SqlSession,多次执行后统一 flush。在 Spring 里可以用 SqlSessionTemplate 或 @Transactional + 特殊配置。

  3. MyBatis-Plus 的 saveBatch:底层也是基于批量执行,使用方便。

注意控制每批数量(比如 500~1000 条),避免 SQL 过长或事务过大。


14. MyBatis 中如何处理一对一、一对多关联查询?

口述版:

主要用 resultMap 配合:

  • 一对一:<association> 标签(或嵌套 select / 嵌套结果)
  • 一对多:<collection> 标签

两种实现方式:

  1. 嵌套结果(nested result):一次 SQL 查出所有数据,MyBatis 负责组装(注意重复数据问题)。
  2. 嵌套查询(nested select):先查主表,再根据关联字段查从表(容易 N+1,可配合懒加载)。

实际项目中更推荐业务层分次查询或自己写好关联 SQL,用嵌套结果映射,可控性更强。


15. Spring 事务和 MyBatis 是怎么配合的?

口述版:

Spring 通过 @Transactional 管理事务,底层用的是 PlatformTransactionManager(通常是 DataSourceTransactionManager)。

整合 MyBatis 后:

  • MyBatis 的 SqlSession 会参与到 Spring 的事务中
  • 同一个事务里多次操作共用同一个 Connection
  • 事务提交或回滚由 Spring 统一控制

注意事项:

  1. @Transactional 默认只对 RuntimeException 回滚,受检异常需要指定 rollbackFor
  2. 方法必须是 public,且从外部调用才会生效(自调用失效)
  3. 多数据源时要明确指定事务管理器

面试加分点

  1. 能说清 #{} 与 ${} 的区别和安全问题。
  2. 理解 Mapper 接口的动态代理原理。
  3. 知道一级缓存、二级缓存的作用域和实际使用建议。
  4. 能讲清楚 Spring 整合 MyBatis 的核心步骤。
  5. 实际用过 PageHelper、批量操作、动态 SQL,能结合项目讲。
  6. 了解 MyBatis-Plus 的常用功能(条件构造器、自动填充、逻辑删除等)是加分项。

快速记忆框架:

Spring + MyBatis 面试核心:

基础概念 → #{} vs ${} → 缓存 → 执行流程与动态代理 → 结果映射 → 动态 SQL → 主键回填 → Spring 整合 → 事务 → 插件与批量

把「动态代理」和「缓存」讲清楚,再结合自己项目里写过的复杂 SQL 和批量操作,基本就能应对大部分面试。