mobile wallpaper 1
mobile wallpaper 2
mobile wallpaper 3
mobile wallpaper 4
7749 字
21 分钟
Java 框架篇面试题:Spring、Spring Boot 与 MyBatis 核心解析
2026-08-22
TIP

💡 面试复习优先级与考察权重指引:

  • 🔥【超高频 · ★★★】(出现率 85% ~ 95%):大厂/中小厂核心淘汰八股,面试必背、必须能流利答出底层机制与常见追问。
  • ⚡【高频 · ★★☆】(出现率 60% ~ 80%):高频常规考察题,需讲清核心流程、架构设计与底层优势。
  • 📌【中频 / 低频 · ★☆☆】(出现率 30% ~ 50%):进阶与次要八股,侧重考察微服务架构下的取舍与底层原理。

⚡【高频 · ★★☆】Spring 框架中的单例 Bean 是线程安全的吗?#

一、这题在问什么#

面试官其实是在考你 3 个点:

  1. Spring Bean 默认作用域是不是单例
  2. 单例对象在多线程环境下会不会有并发问题
  3. 什么情况下安全,什么情况下不安全,怎么解决

重点不是只回答“安全”或“不安全”,而是要说清楚: 单例 Bean 本身不天然线程安全,是否线程安全取决于这个 Bean 里有没有共享的可变状态。


二、怎么理解#

你可以这样记:

1)Spring 默认是单例#

Spring 里 Bean 默认作用域是 singleton,也就是整个容器里通常只有一个对象实例。

2)单例 Bean 会被多个线程共享#

在 Web 项目里,很多请求会同时进来,这些请求线程拿到的可能都是同一个 Bean 对象。

3)真正的问题在“成员变量”#

如果这个 Bean:

  • 没有可变成员变量
  • 或者是无状态 Bean

那一般就是安全的。

如果这个 Bean 里定义了:

  • 会被修改的成员变量
  • 并且多个线程都会访问它

那就可能出现线程安全问题。


三、标准回答思路#

面试时推荐你按这个顺序答:

第一步:先给结论#

Spring 中的单例 Bean 不一定线程安全。

第二步:解释原因#

因为 Spring 默认单例 Bean 会被多个线程共享,如果 Bean 中存在共享的可变成员变量,就可能发生线程安全问题。

第三步:补充什么时候安全#

如果 Bean 是无状态的,只包含方法局部变量,不保存请求过程中的数据,那么通常是线程安全的。

第四步:说解决办法#

常见做法有:

  • 尽量设计成无状态 Bean
  • 把共享变量改成局部变量
  • 必要时加同步控制(如 synchronized、锁)
  • 或者改成 prototype / request 等合适作用域
    但一般核心思路还是:尽量不要在单例 Bean 里存可变状态

四、背诵版本(短版 · 适合 30 秒至 1 分钟回答)#

Spring 中的单例 Bean 不一定是线程安全的。因为 Spring 默认 Bean 是 singleton,多个线程会共享同一个 Bean 实例。如果这个 Bean 是无状态的,只使用方法内部局部变量,那通常就是线程安全的;但如果 Bean 中定义了可变的成员变量,并且在并发场景下被多个线程共同访问和修改,就会出现线程安全问题。解决方式一般是尽量把 Bean 设计成无状态,避免使用共享可变成员变量,必要时再通过加锁或调整作用域来处理。


五、背诵版本(长版 · 适合 1 至 2 分钟展开回答)#

Spring 框架中的单例 Bean 默认不是绝对线程安全的,是否线程安全要看这个 Bean 的实现方式。因为 Spring 默认作用域是 singleton,也就是整个 IOC 容器中通常只有一个 Bean 实例。在 Web 应用中,多个请求线程会同时访问这个单例对象。如果这个 Bean 本身是无状态的,比如只做业务处理,不保存中间数据,只使用方法中的局部变量,那么一般不会有线程安全问题。

但是如果这个 Bean 中包含可变的成员变量,比如把请求数据、计算中间结果放到成员变量里,那么多个线程同时访问时就可能发生数据覆盖、脏读等并发问题。所以在实际开发中,我们通常会把 Service、Controller 这类单例 Bean 设计成无状态,避免定义可变成员变量。如果确实需要共享状态,可以考虑加锁、使用线程安全的数据结构,或者调整 Bean 的作用域,但最推荐的方式还是无状态设计。


六、面试加分点#

你可以再补一句,面试官会觉得你理解比较到位:

  • 加分表达 1:所以不能简单地说“单例 Bean 线程安全”或者“单例 Bean 线程不安全”,更准确地说,是“默认单例 Bean 会被多线程共享,是否安全取决于是否存在共享的可变状态”。
  • 加分表达 2:像 Spring 里的 Service 一般推荐设计成无状态,这也是为什么大多数情况下虽然是单例,但实际使用中问题不大。

七、超简洁记忆版#

你可以直接背这一句:

Spring 单例 Bean 不一定线程安全;无状态则通常安全,有共享可变成员变量则不安全。


🔥【超高频 · ★★★】什么是 AOP 及其在项目中的实际应用?#

1. 什么是 AOP(核心概念与原理)#

  • 理解: AOP 就是面向切面编程,主要用来把和核心业务无关、但很多地方都会重复出现的公共功能抽出来统一处理,比如日志、事务、权限校验、异常处理等。这样可以在不改业务代码的前提下,对方法进行增强,减少重复代码,降低耦合。

  • 背诵版本: AOP 就是面向切面编程,它的核心思想是把日志、事务、权限校验这些和核心业务无关、但会被多个模块重复使用的公共功能抽取出来,统一进行处理。这样可以在不修改原有业务代码的情况下,对目标方法进行前置、后置或环绕增强,从而实现代码复用、降低耦合。


2. 你们项目中有没有使用到 AOP?(实战落地场景)#

  • 理解: 这题不能只回答“用过”,要说出具体场景。项目里最常见的 AOP 使用场景有:

    • 记录操作日志
    • Spring 声明式事务管理
    • 缓存切面处理(如防缓存穿透/击穿)
    • 统一权限校验与 Token 解析
    • 接口耗时统计与慢调用报警

    你可以重点说“操作日志”最容易展开:通过切点表达式找到需要拦截的方法,再通过环绕通知拿到方法信息、请求参数、执行结果、执行时间等,最后把这些日志异步保存到数据库或 Elasticsearch 系统里。

  • 背诵版本: 我们项目中使用过 AOP,比较典型的场景是操作日志记录和 Spring 事务管理。比如在操作日志这块,我会通过切点表达式拦截带有自定义注解的业务方法,然后在环绕通知中获取方法名、类名、请求参数、执行结果以及执行时间等信息,最后统一记录到数据库或者日志系统中。这样做的好处是不用在每个业务方法里重复写日志代码,维护起来也更方便。


🔥【超高频 · ★★★】Spring 事务实现原理与失效场景深度剖析#

1. Spring 中的事务是如何实现的?#

  • 理解: Spring 事务本质上就是基于 AOP + 代理模式 实现的。 当你在方法上加了 @Transactional,Spring 会为这个类生成一个代理对象。调用目标方法时,实际上先走代理逻辑:

    • 方法执行前:开启事务
    • 方法正常结束:提交事务
    • 方法出现异常:根据规则回滚事务 所以本质不是业务代码自己控制事务,而是 Spring 在方法前后帮你做了增强。
  • 背诵版本: Spring 中的事务本质上是通过 AOP 和代理模式实现的。通常我们在方法上加上 @Transactional 注解后,Spring 会为目标对象生成代理对象。当调用这个方法时,实际上会先进入代理逻辑,在方法执行前开启事务,方法执行成功后提交事务,如果执行过程中出现异常,就根据事务的回滚规则进行回滚。所以 Spring 事务本质上就是对目标方法前后进行增强,把事务控制逻辑统一交给框架处理。


2. Spring 事务失效的经典场景有哪些?如何解决?#

在实际项目与面试中,最常见的几种导致事务失效的场景包括:

  1. 方法内部自调用(this.xxx())导致代理失效:同一个类内的一个非事务方法调用了带 @Transactional 的事务方法,由于走的是 this 本地对象调用而非代理对象,AOP 增强逻辑根本不会被触发。

    • 解决办法:将方法拆到另一个 Service 中注入调用,或者通过 AopContext.currentProxy() 获取当前代理对象调用。
  2. 方法内部捕获并吞掉了异常(try-catch):如果方法内部捕获了异常且没有向外抛出,Spring 无法感知到异常,事务就不会回滚。

  1. 抛出的是检查型异常(Checked Exception)未配置回滚:Spring 声明式事务默认只在遇到 RuntimeException 和 Error 时回滚。如果方法抛出的是检查异常(如 SQLException、IOException),默认不会触发回滚。
    • 解决办法:显式配置 @Transactional(rollbackFor = Exception.class)。
  1. 事务注解的方法不是 public 修饰:Spring 事务底层基于 AOP 动态代理,默认要求目标方法必须是 public,否则不会为其生成代理拦截逻辑。
  • 背诵总结版本: Spring 事务失效常见场景主要有四种: 第一,同类方法内部自调用(this.xxx()),没有经过 Spring 生成的 AOP 代理对象; 第二,方法内部把异常 try-catch 自己捕获处理了,没有继续抛出,导致 Spring 感知不到异常; 第三,抛出的是检查异常(Checked Exception),Spring 默认只对运行时异常回滚,必须显式指定 rollbackFor = Exception.class; 第四,事务方法不是 public 的,因为 AOP 代理默认只拦截 public 方法。

🔥【超高频 · ★★★】Spring Bean 的生命周期#

1. 核心生命周期完整流程(八步法)#

Spring 中 Bean 的完整生命周期主要包括以下 8 大核心阶段:

  1. 解析定义(BeanDefinition):Spring 解析配置类、XML 或 @Component 注解,生成 BeanDefinition 描述元数据。
  2. 实例化(Instantiation):通过反射调用构造函数在堆内存中为 Bean 分配内存空间(此时仅是裸对象,属性均为 null)。
  3. 属性注入(Populate Properties):为 Bean 填充属性,完成依赖注入(如处理 @Autowired、@Resource 或 setter 注入)。
  4. Aware 接口回调(Aware Callbacks):如果 Bean 实现了 Aware 接口,Spring 注入容器感知资源:
    • BeanNameAware:传入当前 Bean 的名称;
    • BeanFactoryAware:传入所属的 BeanFactory;
    • ApplicationContextAware:传入当前的 ApplicationContext 容器上下文。
  5. BeanPostProcessor 前置处理:执行所有已注册后置处理器的 postProcessBeforeInitialization() 方法(如 @PostConstruct 注解解析)。
  6. 初始化(Initialization):执行初始化逻辑:
    • 先执行 InitializingBean 的 afterPropertiesSet() 方法;
    • 再执行自定义配置的 init-method(如 @Bean(initMethod = "init"))。
  7. BeanPostProcessor 后置处理:执行所有已注册后置处理器的 postProcessAfterInitialization() 方法(AOP 动态代理通常在这一步生成!)。
  8. 注册销毁回调与容器销毁(Destruction):Bean 正常投入业务使用;当容器关闭时:
    • 执行 @PreDestroy 标注的方法;
    • 执行 DisposableBean 接口的 destroy() 方法;
    • 执行自定义配置的 destroy-method。

2. 核心追问 1:BeanPostProcessor 是做什么的?#

  • 面试官真实意图:考察你对 Spring 扩展机制(SPI)与底层拦截原理的理解。
  • 核心解答: BeanPostProcessor(Bean 后置处理器)是 Spring 框架中最核心的全局扩展点。它允许开发者在 Bean 初始化之前(Before) 和 初始化之后(After) 插入自定义逻辑,甚至替换或增强 Bean 实例。
  • 两大典型职责:
    1. 注解增强与属性篡改:例如处理 @PostConstruct、@PreDestroy、@Value 注解解析;
    2. 生成动态代理对象(AOP 核心机制):Spring 的 AbstractAutoProxyCreator(如事务切面、自定义切面)本质上就是一个 BeanPostProcessor,在它的后置处理阶段将目标原始对象包装为 JDK 或 CGLIB 代理对象。

3. 核心追问 2:AOP 动态代理是在哪一步生成的?#

  • 正常流程: 在 Bean 生命周期的**第 7 步 —— 初始化之后(BeanPostProcessor 后置处理器)**生成。具体的实现类是 AnnotationAwareAspectJAutoProxyCreator 的 postProcessAfterInitialization() 方法。
  • 特殊例外(循环依赖场景): 如果发生了 循环依赖(例如 A 依赖 B,B 依赖 A),且 A 需要被 AOP 代理,此时 AOP 代理生成会被提前到实例化之后、依赖注入之前,由三级缓存中的 ObjectFactory(调用 SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference())提前生成代理对象,以保证注入给 B 的是 A 的代理对象而非原始对象。

4. 面试 1 分钟高分背诵版#

Spring Bean 的生命周期大致可以总结为四个核心阶段:实例化、属性赋值、初始化和销毁。

展开来说共有八个关键步骤:首先解析 BeanDefinition;然后通过构造函数完成实例化;接着进行属性依赖注入;随后处理 Aware 接口回调传入容器上下文;之后进入 BeanPostProcessor 的前置处理;再调用 InitializingBean 和自定义的 init-method 进行初始化;随后在 BeanPostProcessor 的后置处理中,Spring 会在此阶段为 Bean 生成 AOP 动态代理对象;最后在容器关闭时触发 DisposableBean 的销毁回调。

这里最核心的扩展点是 BeanPostProcessor,它负责在初始化前后对 Bean 进行拦截增强,比如注解解析和 AOP 代理包装。通常 AOP 代理是在初始化后的 BeanPostProcessor 中生成的;但如果存在循环依赖,则会由三级缓存提前在实例化后生成代理对象,以保证注入引用的唯一一致性。


🔥【超高频 · ★★★】Spring 循环依赖底层原理与三级缓存深度剖析#

1. 什么是循环依赖与三级缓存结构?#

循环依赖发生在两个或两个以上的 Bean 互相持有对方(例如 A 依赖 B,B 依赖 A),形成闭环依赖。Spring 框架允许单例 Bean 的循环依赖存在,并通过三级缓存机制进行解决:

  • 一级缓存(singletonObjects):单例池,存放已经完全初始化完成的可用 Bean 对象。
  • 二级缓存(earlySingletonObjects):存放尚未完成完整生命周期的早期原始/代理 Bean 对象(半成品)。
  • 三级缓存(singletonFactories):存放 ObjectFactory 函数式工厂,用于延迟生成早期 Bean 的代理引用。

2. 三级缓存解决循环引用的完整执行流程#

为什么一级缓存无法解决?#

单靠一级缓存无法区分“完整初始化好的 Bean”与“刚实例化的半成品 Bean”,并发场景下其他线程会拿到未完成初始化的脏对象。

为什么二级缓存还不够?必须要有第三级缓存?#

假设 Bean A 需要被 AOP 动态代理。按照 Bean 的标准生命周期,代理对象是在**初始化之后(BeanPostProcessor 后置处理器)**才生成的。如果只使用二级缓存,在实例化阶段就必须把原始对象暴露出去,导致后续注入的是原始对象,而最终容器生成的是代理对象,引发对象引用不一致问题。

  • 核心理解: 三级缓存的作用,就是在发生循环依赖时,通过工厂方法决定提前生成并暴露代理对象还是原始对象,从而既打破了循环死锁,又保证了全局对象引用的唯一一致性。

  • 背诵版本: Spring 解决循环依赖主要依赖三级缓存机制。一级缓存存放的是完整初始化后的单例 Bean,二级缓存存放的是提前暴露的早期半成品 Bean,三级缓存存放的是 ObjectFactory 工厂。当 A 依赖 B、B 又依赖 A 时,Spring 在实例化 A 后,会先将 A 的工厂放入三级缓存;当 B 实例化并需要注入 A 时,可以通过三级缓存提前获取 A 的早期引用(若 A 有 AOP 则生成代理对象),并放入二级缓存。从而打破循环依赖。三级缓存的核心价值就在于完美兼顾了循环依赖与 AOP 动态代理对象的一致性。


3. 构造方法出现循环依赖的解决方案#

  • 理解: 构造方法循环依赖,Spring 默认解决不了。因为构造器注入发生在 Bean 实例化最开始的阶段,A 实例化时必须先拿到 B,B 实例化时又必须先拿到 A,此时两个 Bean 均未分配堆内存,Spring 无法像属性注入那样先实例化半成品再提前暴露,所以三级缓存无能为力。

  • 解决策略:

    1. 改用 Setter / @Autowired 属性注入;
    2. 在构造器参数上添加 @Lazy 注解:通过动态代理生成代理垫片对象注入,等到真正调用方法时再触发真实对象的实例化。
  • 背诵版本: 构造器循环依赖 Spring 默认无法解决,因为构造器发生在实例化阶段,两个 Bean 都未实例化完成,三级缓存无法提前暴露对象。解决方案通常是将构造器注入改为属性注入(Setter 或 @Autowired),或者在其中一个构造参数前加上 @Lazy 注解,利用延迟代理打破实例化死锁。


⚡【高频 · ★★☆】Spring MVC 核心执行流程与原理#

Spring MVC 的核心请求处理链路:

  1. 用户发送请求:到达前端控制器 DispatcherServlet。
  2. 查找处理器:DispatcherServlet 调用 HandlerMapping 处理器映射器,根据请求 URL 找到对应的 Handler(Controller)及拦截器链。
  3. 适配执行器:DispatcherServlet 调用 HandlerAdapter 处理器适配器。
  4. 执行业务方法:HandlerAdapter 经过参数解析与数据绑定后,执行 Controller 业务方法。
  5. 处理返回结果:
    • 若返回视图:Controller 返回 ModelAndView,由 ViewResolver 视图解析器解析为具体视图并渲染呈现;
    • 若前后端分离(常见):使用 @ResponseBody 注解,通过 HttpMessageConverter(如 Jackson)直接序列化为 JSON 字符串写入 HTTP 响应体。

⚡【高频 · ★★☆】介绍 Spring Boot 及其优势(解决的痛点)#

一、这题在问什么#

面试官其实在考察三个层次:

  1. 你知不知道 Spring Boot 是做什么的;
  2. 你能不能说出它相比传统 Spring 解决了哪些开发痛点;
  3. 你是不是停留在概念表面,还是理解了自动配置与起步依赖的关键设计。

二、怎么理解 Spring Boot#

Spring Boot 是基于 Spring 框架的快速开发脚手架,用于简化 Spring 应用的搭建、配置和部署。

在传统 Spring 开发中,痛点非常明显:

  • 需要编写大量繁琐的 XML 或 JavaConfig 配置;
  • 各种组件(数据源、事务、Web、缓存)需要手动装配;
  • Maven 依赖版本复杂,容易出现 Jar 包依赖冲突;
  • 部署需要单独安装并配置外部 Tomcat 服务器。

Spring Boot 便是为解决上述痛点而生,其核心特点归纳为四大支柱:

  1. 约定大于配置(Convention over Configuration):提供成熟的默认配置规则,开发者无需从零配置。
  2. 自动配置(Auto-Configuration):根据引入的依赖自动装配对应的 Bean 与环境(“带什么原料,就配什么厨房”)。
  3. 起步依赖(Starter):聚合常用依赖并统一锁定版本,避免依赖地狱。
  4. 内嵌 Web 容器:内置 Tomcat / Jetty / Undertow,支持 java -jar 一键启动与云原生容器化部署。

三、背诵版本(1 分钟速记)#

Spring Boot 是基于 Spring 的快速开发框架,核心目标是简化 Spring 应用的搭建、配置和部署。 相比传统 Spring,它最核心的优势有三点: 第一是约定大于配置与自动配置,它会根据项目中引入的 Starter 依赖自动装配相关的 Bean 和运行环境,省去了大量繁琐的 XML 和配置类编写; 第二是起步依赖(Starter),它把常用模块依赖进行了标准化整合并统一管理版本,彻底解决了 Jar 包冲突; 第三是内嵌 Tomcat 容器,支持直接打包为可执行 Jar 包运行,极大提升了开发调试和运维部署效率。 总结来说,Spring Boot 不是替代 Spring,而是站在 Spring 的肩膀上,让开发者将核心精力聚焦在业务逻辑开发上。


🔥【超高频 · ★★★】Spring Boot 自动配置底层原理深度剖析#

Spring Boot 自动配置的核心驱动注解是启动类上的 @SpringBootApplication,它是一个复合注解,底层由 3 个核心注解组成:

  1. @SpringBootConfiguration:标记当前类为配置类(本质上就是 @Configuration)。
  2. @ComponentScan:指定组件扫描范围(默认扫描主启动类所在包及其子包)。
  3. @EnableAutoConfiguration(核心驱动):
    • 内部通过 @Import(AutoConfigurationImportSelector.class) 导入自动配置选择器;
    • 读取类路径下的配置文件:
      • Spring Boot 2.x:读取 META-INF/spring.factories;
      • Spring Boot 3.x:读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports;
    • 获取所有候选自动配置类的全限定类名列表;
    • 条件注解按需装配:结合 @ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty 等条件注解进行过滤,只有满足特定依赖或配置时,对应的 AutoConfiguration 类才会被激活并注入 IOC 容器。

⚡【高频 · ★★☆】Spring Boot 完整启动原理与 refresh 核心链路#

一、启动流程核心脉络#

Spring Boot 的启动入口是 SpringApplication.run(Main.class, args),整体过程可以拆解为 7 大关键步骤:

  1. 创建 SpringApplication 实例:推断应用类型(Reactive / Servlet / NONE)、加载配置的 ApplicationContextInitializer 与 ApplicationListener、推断主启动类。
  2. 准备运行环境 Environment:加载系统环境变量、JVM 参数、application.yml / properties 配置文件及命令行入参。
  3. 创建容器 ApplicationContext:Web 项目默认创建 AnnotationConfigServletWebServerApplicationContext。
  4. 准备容器上下文 prepareContext:应用环境变量、执行 Initializer 初始化器。
  5. 执行容器刷新 refresh()(最核心):
    • 解析配置类与组件扫描;
    • 注册 BeanDefinition;
    • 实例化并初始化所有非懒加载单例 Bean;
    • 执行 BeanPostProcessor;
    • 创建并启动内嵌 Web 容器(如 Tomcat)。
  6. 刷新后扩展回调:执行 CommandLineRunner 与 ApplicationRunner。
  7. 返回容器:启动完成,对外提供 HTTP/RPC 服务。

二、面试背诵版本#

Spring Boot 启动过程以 SpringApplication.run() 为入口,核心流程可总结为:准备环境 →\to 创建容器 →\to 刷新容器(refresh) →\to 启动内嵌服务器。 其中最核心的步骤是容器的 refresh() 方法。在这一阶段,Spring 完成了配置类解析、组件扫描、BeanDefinition 注册、单例 Bean 的实例化与依赖注入,并在 Web 环境下同步启动内嵌的 Tomcat 服务器。


⚡【高频 · ★★☆】Spring / Spring MVC / Spring Boot 常见核心注解总结#

1. Spring 常见核心注解#

  1. 组件声明:@Component、@Service、@Repository、@Controller。
  2. 依赖注入:
    • @Autowired:Spring 原生,默认按类型(byType)注入,可搭配 @Qualifier 按名称匹配;
    • @Resource:JDK 规范,默认按名称(byName)注入,找不到再按类型匹配。
  3. 配置与作用域:@Configuration、@Bean、@ComponentScan、@Scope。
  4. 切面编程(AOP):@Aspect、@Pointcut、@Before、@After、@Around。

2. Spring MVC 常见注解#

  • 路径映射:@RequestMapping、@GetMapping、@PostMapping、@PutMapping、@DeleteMapping。
  • 请求参数绑定:
    • @RequestParam:接收 Query 查询参数或表单数据;
    • @PathVariable:接收 URL 路径变量(如 /users/{id});
    • @RequestBody:接收 HTTP 请求体中的 JSON 数据并反序列化为 Java 对象;
    • @RequestHeader:获取 HTTP 请求头信息。
  • 响应体转换:@ResponseBody(将返回值转为 JSON 格式写入响应体)。

3. Spring Boot 常见注解#

  • 主启动注解:@SpringBootApplication(组合 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan)。
  • REST 控制器:@RestController(组合 @Controller + @ResponseBody)。
  • 属性配置绑定:@ConfigurationProperties、@Value。
  • 条件装配注解:@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty 等。

📌【中频 · ★★☆】MyBatis 底层核心执行流程#

MyBatis 的底层执行流程围绕四大核心对象展开:

  1. 读取配置:解析 mybatis-config.xml 以及 Mapper XML,构建 Configuration 对象。
  2. 构建工厂:通过 SqlSessionFactoryBuilder 构建线程安全的 SqlSessionFactory 会话工厂。
  3. 创建会话:通过工厂创建 SqlSession(线程不安全,单次数据库交互上下文)。
  4. 执行器调度:SqlSession 将具体的 CRUD 任务委托给 Executor 执行器(如 SimpleExecutor、ReuseExecutor)。
  5. 核心组件协作:
    • StatementHandler:负责创建 JDBC Statement 并与数据库交互;
    • ParameterHandler:负责将 Java 入参映射设置到 SQL 预编译语句中;
    • ResultSetHandler:负责将 JDBC 查询结果集映射并组装为 Java 实体对象。

📌【低频 · ★☆☆】MyBatis 是否支持延迟加载及其底层原理#

1. 延迟加载概念与使用场景#

  • 概念:延迟加载(Lazy Loading)即按需加载。在执行主查询时,仅查询主表数据,关联表(如 <association> 或 <collection>)的数据不会立即查询;只有在业务代码中真正调用关联对象的 getter 方法(如 user.getOrders())时,MyBatis 才会触发并发送第二条 SQL 查询关联数据。
  • 配置方式:在配置文件中开启 lazyLoadingEnabled=true。
  • 现状与冷门原因:在现代主流微服务与高并发互联网开发规范中,通常严禁使用级联联合查询与延迟加载,普遍推荐采用“单表单查 + 业务层在应用内存中组装”,以避免不可控的 N+1 查询风暴与数据库长事务,因此该题在实战面试中考频较低。

2. 延迟加载的底层原理(CGLIB 动态代理)#

延迟加载底层基于 CGLIB 动态代理 实现:

  1. 主查询执行完毕后,MyBatis 创建并返回目标实体类的 CGLIB 代理对象。
  2. 当调用代理对象的 getter 方法(例如 getOrders())时,进入代理拦截器的 intercept() 方法。
  3. 拦截器检测到关联属性尚未加载(当前为 null),立即根据预存的 MappedStatement 触发执行关联 SQL。
  4. 获得查询结果后,通过反射将属性填充到对象中,并返回给调用方。

📌【中频 · ★★☆】MyBatis 一级缓存与二级缓存机制#

1. 一级缓存与二级缓存对比#

缓存类型作用域默认开关存储实现适用与失效机制
一级缓存SqlSession 级别(本地会话)默认开启基于 PerpetualCache 的 HashMap 本地内存同一 SqlSession 内相同 SQL 会命中缓存;当该 Session 执行了 commit、close 或任何 insert/update/delete 增删改操作后,缓存立即清空。
二级缓存Namespace / Mapper 级别(跨会话全局)需手动开启基于 PerpetualCache,支持装饰器(LRU、FIFO)多个 SqlSession 共享同一个命名空间的缓存;必须等 SqlSession commit() 提交后,数据才会从暂存区刷入二级缓存。

2. 缓存清理时机与微服务实践思考#

  • 何时清理数据: 当当前作用域(一级缓存对应的 SqlSession 或二级缓存对应的 Namespace)内执行了任何新增(INSERT)、修改(UPDATE)、删除(DELETE)并提交后,该作用域下的所有 SELECT 查询缓存将被全量清空,以防止脏读。
  • 为什么微服务分布式架构中严禁开启二级缓存? MyBatis 二级缓存是单 JVM 进程内的本地缓存。在多实例分布式或微服务集群环境下,节点 A 执行了更新操作只会刷新本地缓存,节点 B 无法感知并依然返回旧的本地二级缓存数据,极易产生严重的分布式数据脏读。因此现代架构中通常直接禁用 MyBatis 二级缓存,统一使用 Redis 分布式缓存实现集中式数据一致性管理。
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Java 框架篇面试题:Spring、Spring Boot 与 MyBatis 核心解析
https://xinrose.top/posts/interview/spring-framework/
作者
xinRose
发布于
2026-08-22
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录