MyBatis面试题库

适用场景:面试刷题、笔试备考、春招秋招、社招复盘、期末复习

题库说明:全网最全MyBatis精准题库,无废话、标准答案、可直接背诵,覆盖基础、原理、源码、动态SQL、缓存、插件、高阶架构、生产调优全部考点


1. MyBatis是什么?

标准答案

(1)MyBatis 是一款优秀的半自动ORM持久层框架,封装了原生JDBC繁琐操作,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程;

(2).MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集;

(3).通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中MySQL的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。

2. ORM是什么?

标准答案

ORM(Object Relational Mapping),对象关系映射,是一种为了解决关系型数据库数据与简单Java对象(POJO)的映射关系的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系型数据库中。

3. 为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?

标准答案(完整版·面试满分)

一、为什么MyBatis是「半自动ORM」?

ORM(对象关系映射)核心是完成 Java对象 ↔ 数据库表 的自动映射。

MyBatis 被称为半自动ORM,核心原因是:框架只帮我们完成「结果集映射、参数映射、资源管理」,但核心的SQL语句需要开发者手动编写

它不会自动生成SQL,不会屏蔽数据库底层,只是帮我们简化JDBC冗余操作,属于「人工写SQL + 框架做映射」的半自动化模式。

二、全自动ORM代表(Hibernate / Spring Data JPA)

全自动ORM 完全不需要手写SQL ,开发者只需操作对象、调用方法,框架根据实体映射关系、方法名规则,自动动态生成SQL、执行SQL、完成映射,全程屏蔽数据库细节。

三、半自动 & 全自动 核心完整区别

  1. SQL控制权不同

半自动(MyBatis):手动写SQL,SQL完全可控,支持复杂联表、分组、子查询、定制化优化;

全自动(JPA/Hibernate):框架自动生成SQL,开发者无法精准控制,复杂SQL极易生成低效语句。

  1. 性能与优化空间不同

MyBatis:SQL自主优化、可加索引、可分页优化、适配高并发大数据量,性能优异;

全自动ORM:SQL不可控,容易出现全表扫描、冗余关联、N+1等性能问题,高并发场景短板明显。

  1. 数据库移植性不同

MyBatis:SQL适配具体数据库,存在函数、语法差异,跨数据库移植性差;

JPA/Hibernate:屏蔽数据库方言,底层适配多数据库,切换数据库基本无需改代码。

  1. 开发效率不同

MyBatis:简单CRUD稍繁琐,需要维护XML/SQL;

全自动ORM:零SQL开发,简单业务CRUD效率极高。

  1. 业务适配场景不同

MyBatis:适配互联网项目、高并发、复杂业务、需要SQL调优的生产场景;

全自动ORM:适配传统OA、后台管理系统、业务简单、低并发项目。

四、面试总结金句(必背)

MyBatis半自动ORM的核心优势是 取舍平衡 :舍弃了全自动的极致开发效率,换取了SQL可控性、高性能、高灵活性,这也是互联网企业首选MyBatis的根本原因。

4. 为什么使用Mybatis代替JDBC?传统JDBC开发存在的问题

标准答案(完整版·面试满分)

在Java原生JDBC开发中,存在大量硬编码、资源冗余、性能缺陷、维护性差的核心问题,这也是项目中普遍使用MyBatis替代原生JDBC的核心原因,具体痛点与适配优势如下:

一、传统JDBC开发存在的核心问题

  1. 代码极度冗余、重复性高:每次数据库操作都需要手动加载驱动、创建Connection连接、创建Statement、手动设置参数、遍历ResultSet封装结果集、手动关闭资源,模板代码大量重复,开发效率极低。

  2. SQL硬编码,耦合严重:SQL语句直接写在Java代码中,Java代码与数据库SQL强耦合。如果需要修改SQL,必须改动Java代码、重新编译部署,不利于维护和DBA优化。

  3. 数据库资源频繁创建销毁,性能差:原生JDBC每次操作都需要新建、关闭数据库连接,频繁的IO创建销毁操作,开销极大,高并发场景极易造成资源瓶颈。

  4. 参数赋值繁琐、易出错:需要手动根据参数下标逐个set参数,参数量大时容易出现下标错位、参数类型不匹配等问题,BUG率高。

  5. 结果集封装繁琐冗余:需要手动遍历ResultSet,逐个获取字段值、封装为Java实体对象,代码量大且重复,字段变更需同步修改封装逻辑。

  6. 无缓存机制,查询性能低效:原生JDBC无任何缓存,相同条件的重复查询会频繁访问数据库,浪费数据库资源,响应速度慢。

  7. 无事务、日志、插件统一管理:原生JDBC需要手动编写事务提交、回滚逻辑,没有统一的日志打印、SQL监控、扩展机制,功能简陋。

二、MyBatis针对性完美解决所有痛点

  1. 消除冗余代码:框架自动封装驱动加载、连接创建、资源关闭、参数赋值、结果集封装等通用逻辑,开发者只需专注业务SQL编写。

  2. 解耦SQL与代码:将SQL剥离到XML/注解中,与Java代码分离,SQL可独立维护、方便DBA调优,修改SQL无需改动业务代码。

  3. 整合连接池,优化资源:支持整合第三方连接池,统一管理数据库连接,复用连接、避免频繁创建销毁,大幅提升并发性能。

  4. 自动参数映射:通过#{占位符自动绑定参数,无需手动下标赋值,彻底杜绝参数错位、类型不匹配问题。

  5. 自动结果集封装:通过resultType/resultMap自动将JDBC结果集映射为Java实体对象,无需手动遍历封装。

  6. 内置两级缓存:默认一级缓存、可开启二级缓存,减少重复查库,大幅提升高频查询性能。

  7. 功能完善、扩展性强:内置事务管理、SQL日志监控,支持插件拦截机制,可自定义分页、脱敏、数据权限等扩展功能。

三、面试总结金句

原生JDBC只提供了最基础的数据库操作能力,繁琐低效、耦合严重;MyBatis在不丢失SQL可控性的前提下,封装所有JDBC底层冗余操作,兼顾开发效率、性能、可维护性、扩展性,是替代原生JDBC的最优持久层方案。

5. JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?

标准答案(完整版·面试满分)

原生JDBC是最基础的数据库操作API,存在硬编码严重、资源不可控、极易出错、无法扩展、无性能优化等大量短板,MyBatis针对性全部解决,详细对比如下:

一、JDBC核心不足之处

1. 代码冗余、模板代码泛滥

每次操作数据库都必须手动:加载驱动、创建Connection、创建Statement、设置参数、处理ResultSet、手动close关闭资源。大量重复样板代码,开发低效、维护麻烦。

2. SQL硬编码,维护性极差

SQL语句直接写在Java代码中,Java逻辑与数据库SQL强耦合。后续SQL优化、字段变更、逻辑改动都需要改Java代码、重新编译、重启服务,完全不符合开闭原则。

3. 资源频繁创建销毁,并发性能差

JDBC默认每次操作都创建新连接、关闭连接,数据库连接创建是重量级IO操作。高并发场景下频繁创建销毁会造成数据库连接震荡、端口占用、性能瓶颈

4. 参数赋值繁琐、极易引发BUG

JDBC需要根据索引下标setInt(1)、setString(2) 逐个赋值,参数一多很容易出现下标错位、类型不匹配、漏参数问题,隐性BUG多。

5. 结果集封装极其繁琐

需要手写while循环遍历ResultSet,逐个get字段、set到实体属性中,字段一多代码巨冗余,数据库字段变更需要同步改Java封装代码。

6. 无缓存机制,数据库压力大

原生JDBC无任何缓存,相同SQL、相同参数重复查询会重复访问数据库,浪费数据库CPU、IO、连接资源。

7. 无统一事务管理、异常繁琐

需要手动开启事务、手动commit、手动rollback,异常捕获繁琐,极易出现事务未回滚、数据不一致问题。

8. 无扩展能力、无法统一增强

原生JDBC没有拦截、插件机制,无法实现全局分页、SQL日志、数据权限、字段脱敏、性能监控等统一增强功能,每个业务只能手写。

二、MyBatis 针对性解决方案(一一对应)

1. 消灭冗余代码:MyBatis底层完全封装驱动加载、连接获取、Statement创建、资源关闭等通用逻辑,开发者只需要关注业务SQL。

2. SQL与代码完全解耦:将SQL抽离到XML/注解,Java代码只调用方法,修改SQL无需改业务代码、无需重启服务,方便DBA调优。

3. 连接池复用,提升并发性能:整合主流连接池,实现连接复用,避免频繁创建销毁连接,彻底解决高并发资源瓶颈。

4. 自动参数绑定,杜绝下标错误 :通过 #{} 占位符自动匹配参数名称、自动类型转换,无需下标赋值,代码更健壮、零BUG。

5. 自动结果集映射:通过resultType/resultMap自动将ResultSet映射为Java实体,无需手动遍历封装,字段映射统一管理。

6. 内置两级缓存,减轻数据库压力:默认一级缓存、支持二级缓存,相同查询复用内存数据,大幅减少DB访问次数。

7. 统一事务管理:整合Spring事务,支持声明式事务,自动提交、回滚,无需手动编码控制事务。

8. 插件化扩展,全局统一增强:提供四大核心拦截器,可无侵入实现分页、脱敏、数据权限、SQL监控、慢日志统计等通用功能,一次配置全局生效。

三、面试高分总结

JDBC只提供了最原始的数据库访问能力,开发低效、耦合严重、性能差、无法扩展;MyBatis在保留SQL可控性的前提下,解决了JDBC所有原生痛点,兼顾开发效率、系统性能、可维护性与扩展性,是企业级持久层首选方案。

6. MyBatis与Hibernate的区别

标准答案(完整版·面试满分)

MyBatis与Hibernate是Java两大主流ORM持久层框架,核心区别在于ORM自动化程度、SQL控制权、性能特性、适用场景,也是互联网项目和传统项目技术选型的核心差异点,详细对比如下:

一、核心定位与ORM类型不同

  1. MyBatis:半自动ORM框架

不会自动生成SQL,需要开发者手动编写SQL语句,框架仅负责参数映射、结果集封装、资源管理,保留完整SQL控制权,轻量化、灵活度极高。

  1. Hibernate:全自动ORM框架

完全屏蔽SQL底层,无需手写SQL,开发者只需操作实体对象、调用CRUD方法,框架根据实体映射关系自动生成、执行SQL,实现全自动化持久化。

二、SQL控制权与可控性差异

  1. MyBatis:SQL完全由开发者自定义,支持复杂联表、子查询、分组统计、复杂事务SQL,可随时优化索引、优化执行计划,适配复杂业务。

  2. Hibernate:SQL由框架自动生成,开发者无法精准控制,复杂业务极易生成低效SQL、冗余关联查询、全表扫描,SQL调优难度极大。

三、性能与并发适配差异

  1. MyBatis:轻量无冗余,SQL可手动优化,无多余查询开销,缓存机制简单高效,高并发、大数据量场景性能优异

  2. Hibernate:框架层级重、封装厚重,存在多级缓存、持久化上下文机制,容易产生脏数据、N+1查询、冗余SQL,高并发场景性能瓶颈明显。

四、数据库移植性差异

  1. MyBatis:SQL绑定具体数据库方言,MySQL、Oracle、SQL Server语法不同,切换数据库需要修改大量SQL,移植性差。

  2. Hibernate:通过Dialect方言适配多数据库,框架屏蔽数据库语法差异,几乎无需改代码即可跨数据库迁移,移植性极强。

五、开发效率与维护成本

  1. MyBatis:简单CRUD需要手动写SQL/XML,初期开发稍慢;但SQL清晰透明、问题定位快、DBA可独立调优,后期维护成本低。

  2. Hibernate:零SQL开发,简单业务CRUD效率极高;但SQL黑盒化,线上慢查询、性能问题难以定位,复杂业务维护成本极高。

六、缓存机制差异

  1. MyBatis:仅有两级简单缓存(会话级、命名空间级),轻量化、无脏数据风险,可控性强。

  2. Hibernate:拥有一级缓存、二级缓存、查询缓存,缓存体系复杂,多表关联、多数据源场景极易出现缓存脏数据、数据不一致问题。

七、业务适用场景(核心选型标准)

  1. MyBatis适用:互联网高并发项目、复杂业务系统、需要SQL调优、分库分表、读写分离、大数据量查询的生产场景。

  2. Hibernate适用:传统企业OA、CRM、后台管理系统、低并发、业务简单、需要频繁切换数据库的项目。

八、面试高分总结金句

Hibernate以开发效率 为核心,牺牲了SQL可控性和性能;MyBatis以灵活性、可控性、高性能为核心,牺牲了部分开发效率,这也是目前互联网企业清一色抛弃Hibernate、首选MyBatis的核心原因。

7. Mybatis的优缺点

标准答案(完整版·面试满分)

MyBatis 作为互联网主流半自动ORM框架,优缺点非常鲜明,适配高并发复杂业务场景,同时也存在一定的局限性,具体详细解析如下:

一、核心优点(企业面试高频)

1. SQL与代码彻底解耦,可维护性极强

将SQL语句从Java代码中抽离到XML/注解中,业务代码专注逻辑,SQL独立维护。支持DBA单独优化SQL、调整索引、改造执行计划,修改SQL无需改动Java代码、无需重启服务,完全符合开闭原则。

2. 轻量无侵入、上手简单、依赖少

MyBatis框架层级轻薄、无冗余依赖、无需额外加载大量配置,对原有项目代码无侵入,学习成本低,对比Hibernate厚重的封装体系更轻量化,适配绝大多数Java项目。

3. SQL完全可控,性能可精细化调优

开发者手动编写SQL,完全掌控查询逻辑,支持联表查询、子查询、分组统计、复杂事务SQL,可根据业务场景优化Limit分页、索引、查询字段,避免全自动框架生成低效SQL的问题,高并发场景性能优异。

4. 动态SQL能力强大,适配复杂多变业务

内置if、where、set、foreach、choose等全套动态SQL标签,可根据入参条件动态拼接、裁剪SQL,完美适配多条件模糊查询、批量操作、动态更新字段等复杂业务场景,避免编写大量重复SQL。

5. 内置两级缓存,有效降低DB压力

默认开启SqlSession一级缓存,支持手动开启Mapper二级缓存,针对高频重复查询减少数据库访问次数,有效降低数据库CPU、IO压力,提升接口响应速度。

6. 插件扩展机制丰富,可无侵入增强功能

提供四大核心拦截器扩展点,可自定义插件实现物理分页、SQL日志打印、慢SQL监控、字段脱敏、数据权限、乐观锁等通用功能,一次配置全局生效,扩展性极强。

7. 自动参数与结果映射,杜绝底层BUG

通过#{预编译占位符自动绑定参数、防SQL注入,通过resultType/resultMap自动封装结果集,彻底解决JDBC参数下标错位、手动封装冗余、类型不匹配等问题,代码健壮性更高。

二、核心缺点(生产真实痛点)

1. 需要手动编写SQL,重复性工作较多

相比JPA全自动零SQL开发,MyBatis需要手动维护XML/SQL语句,简单CRUD场景开发效率偏低,字段变更时需要同步维护SQL和映射关系。

2. 数据库移植性差,高度依赖数据库方言

SQL语句为数据库专属语法,MySQL、Oracle、SQL Server的函数、分页、语法存在差异,项目切换数据库时,需要大规模修改SQL语句,迁移成本极高。

3. 二级缓存存在脏数据风险

二级缓存是Mapper命名空间级别,若多张Mapper操作同一张数据表,增删改操作只会清空当前namespace缓存,其他namespace缓存不会刷新,极易出现缓存脏数据,实时业务场景通常禁用二级缓存。

4. 关联查询易出现N+1性能问题

在一对一、一对多嵌套子查询场景下,若使用不当会触发N+1查询问题,导致数据库请求量翻倍,需要开发者手动优化为联表查询,对开发人员技术要求更高。

5. 封装程度低,部分通用逻辑需自行实现

没有内置通用CRUD方法,分页、排序、批量操作、条件查询需要手动编写SQL,不如MyBatis-Plus、JPA便捷,需要借助工具类或插件补足通用能力。

三、面试高分总结金句

MyBatis的核心优势是牺牲部分开发效率,换取SQL可控性、高性能、高灵活性,完美适配互联网高并发、复杂SQL、需要精细化调优的生产场景;缺点主要是手动写SQL工作量大、跨库移植差、二级缓存有脏数据风险,也是生产开发中需要重点规避的问题。

简洁:

优点

  1. SQL与代码解耦,便于维护、DBA优化;

  2. 轻量无侵入、学习成本适中;

  3. 动态SQL强大,适配复杂业务场景;

  4. 内置缓存、插件机制,扩展性极强;

  5. 性能优异,适配高并发、大数据量场景。

缺点

  1. 需要手动编写SQL,开发CRUD有一定工作量;

  2. SQL依赖数据库,跨数据库移植性差;

  3. 半自动特性,相比JPA全自动开发效率略低。

8. MyBatis框架适用场景

标准答案(完整版·面试满分)

MyBatis 主打SQL可控、高性能、高灵活、可精细化调优的特性,适配互联网主流业务架构,同时有明确的适用与不适用场景,是技术选型的核心依据,具体细分如下:

一、核心适用场景(面试高频)

1. 互联网高并发、大流量业务系统

适用于电商、支付、短视频、社区等QPS高、接口响应要求严苛的项目。MyBatis支持手动优化SQL、精简查询字段、优化执行计划,无框架冗余开销,能有效支撑高并发访问,规避全自动ORM框架的性能短板。

2. 业务复杂、SQL繁琐的系统

针对多表联查、子查询、分组统计、复杂事务、多条件动态查询的业务场景,MyBatis动态SQL能力极强,开发者可完全掌控SQL逻辑,灵活适配复杂业务,解决全自动框架SQL生成僵化、无法定制的问题。

3. 需要SQL调优、性能迭代的生产项目

项目需要DBA介入优化、慢SQL治理、索引优化、执行计划调整的场景。MyBatis SQL透明可见,便于线上问题排查、性能优化、日志监控,可针对性做精细化性能治理,维护性远高于黑盒ORM框架。

4. 分布式、读写分离、分库分表架构项目

适配微服务分布式架构,完美兼容读写分离、分库分表、多数据源切换架构。开发者可手动区分读写SQL、适配分片规则,结合插件实现数据分片、路由切换,是分布式系统持久层首选框架。

5. 数据量大、批量操作频繁的系统

支持BatchExecutor批量执行器、foreach批量语法,适配批量新增、修改、数据迁移、数据同步等场景,可自主优化批量SQL,减少数据库IO交互,适配大数据量业务。

6. 快速迭代、需要灵活扩展的业务系统

框架轻量无侵入、插件体系完善,可无侵入实现分页、数据权限、字段脱敏、慢SQL监控等通用功能,适配互联网项目快速迭代、灵活扩展的开发模式。

二、不适用场景(面试加分项)

1. 简单低并发、纯CRUD后台管理系统

业务逻辑简单、无复杂SQL、无需性能调优的OA、CRM、后台管理项目,使用MyBatis需要手动写SQL,开发效率偏低,更适合使用Spring Data JPA快速开发。

2. 需要频繁跨数据库迁移的项目

MyBatis SQL强依赖数据库方言,MySQL、Oracle语法差异大,切换数据库需要大规模改写SQL,跨库移植成本极高,此类场景更适合Hibernate、JPA。

三、面试高分总结金句

简单来说,凡是需要性能优化、SQL可控、业务复杂、高并发、分布式架构的项目,首选MyBatis;业务简单、低并发、追求极致开发效率、需要跨库迁移的项目,不推荐MyBatis

9. MyBatis编程步骤是什么样的?

标准答案(完整版·面试满分)

MyBatis标准开发步骤分为原生独立开发步骤Spring整合开发步骤,面试优先答原生完整流程,体现底层掌握度,具体详细步骤如下:

一、MyBatis原生完整编程步骤(底层核心·必背)

1. 引入核心依赖

项目导入MyBatis核心依赖、对应数据库驱动依赖,提供框架支撑和数据库连接能力。

2. 编写全局核心配置文件(mybatis-config.xml)

配置环境参数,包含数据库数据源、事务管理、运行环境、全局参数(驼峰映射、日志、默认执行器)、映射文件注册等全局配置,是MyBatis启动的核心配置。

3. 编写数据映射基础类

根据数据库表结构,创建对应实体POJO类,定义属性与字段映射关系,用于数据库数据封装。

4. 编写Mapper接口与XML映射文件

① 定义Mapper接口,声明CRUD抽象方法,无需手写实现类;

② 编写Mapper XML映射文件,通过namespace绑定对应Mapper接口,配置对应方法的SQL语句、参数映射、结果集映射。

5. 加载配置文件,构建SqlSessionFactory工厂

通过SqlSessionFactoryBuilder读取全局配置文件,解析所有配置信息与映射规则,构建全局唯一的SqlSessionFactory工厂对象,工厂全局单例、常驻内存。

6. 获取SqlSession数据库会话

通过SqlSessionFactory开启SqlSession会话,SqlSession是数据库操作的核心入口,包含事务管理、CRUD执行能力,默认手动提交事务。

7. 获取Mapper动态代理对象,执行数据库操作

通过SqlSession获取Mapper接口的动态代理对象,MyBatis底层自动生成代理实现类,调用接口方法即可执行对应XML中的SQL语句,自动完成参数绑定、SQL执行、结果集封装。

8. 事务提交与资源释放

执行增删改操作后手动commit提交事务,异常时rollback回滚;操作完成后必须关闭SqlSession,释放数据库连接资源,避免连接泄露。

二、SpringBoot整合MyBatis简化步骤(企业实际开发)

  1. 引入mybatis-spring-boot-starter启动器、数据库驱动;

  2. application.yml/application.properties配置数据源参数;

  3. 编写实体类、Mapper接口、Mapper XML文件;

  4. 通过@MapperScan注解扫描Mapper包,自动注册Mapper;

  5. 业务层注入Mapper,直接调用方法,Spring自动管理SqlSession、连接、事务、资源关闭。

三、面试高分总结金句

原生MyBatis开发核心流程为配依赖→配全局配置→写实体→写Mapper映射→建工厂→开会话→代理执行→释放资源;SpringBoot整合后屏蔽了底层工厂、会话创建与资源释放细节,大幅简化开发,但底层原生执行流程不变。

简洁:

  1. 引入MyBatis、数据库驱动依赖;

  2. 编写MyBatis全局配置文件;

  3. 编写实体类、Mapper接口、Mapper XML映射文件;

  4. 加载配置文件,创建SqlSessionFactory工厂;

  5. 获取SqlSession数据库会话;

  6. 获取Mapper代理对象,调用方法执行SQL;

  7. 关闭SqlSession,释放资源。

10. 请说说MyBatis的工作原理

标准答案(完整版·面试满分)

MyBatis的核心工作原理可分为启动初始化阶段(一次性加载)运行执行阶段(每次请求执行),全程基于配置解析、动态代理、组件调度完成数据库操作,底层规避了JDBC所有冗余操作,具体完整流程如下:

一、启动初始化阶段(项目启动只执行一次)

1. 加载解析配置文件

项目启动时,MyBatis通过SqlSessionFactoryBuilder加载全局核心配置文件(mybatis-config.xml)和所有Mapper映射文件,逐一解析配置节点、全局参数、映射规则、动态SQL节点、缓存配置等信息。

2. 构建全局Configuration配置对象

将所有解析后的配置信息、SQL语句、映射关系、插件规则统一封装到Configuration 全局单例对象中,常驻内存;同时将每一条SQL语句封装为MappedStatement对象,以namespace+方法名为唯一标识缓存,实现SQL预加载。

3. 创建全局SqlSessionFactory工厂

基于Configuration配置,构建全局唯一、单例常驻的SqlSessionFactory工厂类,该工厂是MyBatis的核心入口,负责生产每一次数据库会话SqlSession,全程复用、无需重复创建。

二、运行执行阶段(每次数据库请求执行)

1. 获取SqlSession数据库会话

业务请求操作数据库时,通过SqlSessionFactory开启SqlSession会话,SqlSession是数据库操作的顶层入口,内置事务管理、缓存机制、执行器调度能力,默认手动事务提交。

2. JDK动态代理生成Mapper对象

开发者调用Mapper接口方法时,MyBatis底层通过JDK动态代理为Mapper接口生成代理实现类,拦截接口方法,无需开发者手写DAO实现类。

3. 匹配并获取预加载的MappedStatement

代理对象根据当前接口的全类名(namespace)+ 方法名,精准匹配内存中预加载的MappedStatement,获取对应的完整SQL、参数映射、结果集映射规则。

4. 执行器调度,处理SQL执行全流程

SqlSession调用内部Executor执行器(默认SimpleExecutor),调度四大核心处理器完成SQL预处理与执行:

① ParameterHandler:接收方法入参,完成参数绑定、类型转换、预编译赋值,杜绝SQL注入;

② StatementHandler:创建JDBC预编译对象PreparedStatement,处理动态SQL、拼接完整可执行语句;

③ 执行底层JDBC操作,提交SQL至数据库执行;

④ ResultSetHandler:接收数据库返回的ResultSet结果集,通过反射机制结合映射规则,自动封装为Java实体对象/集合。

5. 缓存机制生效

查询操作优先走缓存:优先查询二级缓存(namespace级别),再查询一级缓存(session级别),无缓存才访问数据库;增删改操作会自动清空对应缓存,保证数据一致性。

6. 事务提交与资源释放

增删改操作执行完成后,手动/自动提交事务,异常则回滚;请求结束后关闭SqlSession,释放数据库连接资源,避免连接泄露。

三、面试高分总结金句

简言之,MyBatis工作原理就是启动加载配置缓存SQL,运行动态代理拦截方法,核心组件调度完成参数处理、SQL执行、结果封装,结合两级缓存优化性能,彻底封装JDBC底层冗余,兼顾SQL可控性与开发效率。

简洁:

  1. 启动加载全局配置、Mapper映射文件,解析生成全局Configuration配置对象;

  2. 通过SqlSessionFactoryBuilder创建全局单例SqlSessionFactory;

  3. 工厂创建SqlSession数据库会话;

  4. SqlSession获取Mapper动态代理对象;

  5. 代理对象根据namespace+方法名匹配MappedStatement,执行SQL;

  6. 完成参数预处理、SQL执行、结果集自动映射封装;

  7. 返回Java对象,关闭会话、释放数据库连接。

11. MyBatis功能架构

标准答案(完整版·面试满分)

MyBatis整体功能架构采用分层架构设计 ,自上而下分为接口调用层、核心处理层、基础支撑层三层结构,职责单一、层层解耦,架构清晰、扩展性强,是MyBatis高灵活、高可扩展的核心原因,各层完整功能与职责详解如下:

一、接口层(对外访问入口)

该层是开发者与MyBatis框架交互的唯一入口,负责接收业务层数据库操作请求,屏蔽底层复杂实现逻辑,对外提供极简调用方式。

核心组件与功能

  1. SqlSession:顶层数据库会话接口,提供select、insert、update、delete通用CRUD方法,负责调度底层执行流程,管理事务与缓存;

  2. Mapper代理接口:开发者自定义的Mapper接口,通过动态代理绑定XML/注解SQL,面向接口编程,无需手写DAO实现类,是企业主流调用方式。

核心作用:统一对外暴露数据库操作能力,屏蔽底层SQL解析、执行、资源调度等复杂细节,简化开发者使用成本。

二、核心处理层(框架核心业务逻辑层)

该层是MyBatis的核心中枢,承接接口层请求,完成SQL解析、动态拼接、参数处理、执行调度、缓存管理、结果映射、事务控制所有核心业务逻辑,是框架最核心的功能模块。

核心功能模块详解

  1. SQL解析与动态处理:启动时解析XML/注解SQL,运行时通过SqlNode、OGNL表达式动态拼接、裁剪SQL,适配动态查询、动态更新等复杂场景;

  2. 参数映射处理:通过ParameterHandler完成方法入参绑定、类型转换、特殊字符转义,基于PreparedStatement预编译,杜绝SQL注入;

  3. SQL执行调度:通过Executor执行器(Simple/Reuse/Batch/Caching)统一调度SQL执行,管理一级、二级缓存,优化执行性能;

  4. 语句预处理:通过StatementHandler创建、预处理、执行JDBC语句,处理分页、排序、动态SQL拼装;

  5. 结果集映射封装:通过ResultSetHandler结合反射机制,自动将数据库ResultSet结果集映射为Java实体、集合、Map等对象;

  6. 缓存机制:实现会话级一级缓存、命名空间级二级缓存,控制缓存新增、失效、清空逻辑,减少数据库访问;

  7. 事务管理:统一管理数据库事务的提交、回滚、隔离级别,支持手动事务与Spring声明式事务整合。

三、基础支撑层(底层通用支撑)

该层为上层核心逻辑提供底层通用能力支撑,保障框架稳定运行、可扩展、可监控,是整个框架的基石。

核心功能模块详解

  1. 数据源与连接管理:整合第三方连接池,统一管理数据库连接的获取、复用、释放,避免连接泄露;

  2. 插件拦截机制:基于动态代理与责任链模式,支持拦截四大核心对象,可扩展实现分页、脱敏、数据权限、慢SQL监控等通用功能;

  3. 日志监控:内置日志适配,打印执行SQL、参数、耗时、结果,方便线上问题排查与性能监控;

  4. 类型转换支撑:内置大量TypeHandler,支持数据库字段与Java基本类型、枚举、自定义类型的双向转换;

  5. 配置解析支撑:解析全局配置、映射配置,构建Configuration全局单例,统一管理所有框架配置元数据。

四、面试高分总结金句

MyBatis三层架构各司其职:接口层负责对外调用、核心处理层负责SQL全流程处理、基础支撑层负责底层通用能力支撑,分层解耦的设计让框架兼顾易用性、高性能、高扩展性,适配各类企业级业务场景。

极简速背版(面试口述)

MyBatis功能架构分为三层:第一层接口层,通过SqlSession和Mapper接口提供数据库调用入口;第二层核心处理层,完成SQL解析、参数处理、执行调度、缓存管理、结果映射与事务控制;第三层基础支撑层,提供连接池、插件、日志、类型转换、配置解析等底层支撑,三层解耦,保障框架灵活、高性能、可扩展。

12. MyBatis的框架架构设计是怎么样的

标准答案(完整版·面试满分)

MyBatis的整体框架架构核心由七大核心组件构成,组件各司其职、层层协作、责任链调度,完整支撑MyBatis从配置加载、SQL解析、执行、参数处理、结果封装、缓存管理的全流程,所有组件基于单例、责任链、动态代理设计,耦合度极低、扩展性极强,七大组件完整详解与协作流程如下:

一、七大核心组件完整详解

1. SqlSessionFactoryBuilder(配置构建器)

核心职责:专门用于解析MyBatis全局配置文件与Mapper映射文件,读取配置流,构建全局Configuration配置对象,最终创建SqlSessionFactory工厂。

生命周期:一次性临时对象,仅项目启动构建工厂时使用,构建完成立即销毁,无需常驻内存。

2. SqlSessionFactory(会话工厂)

核心职责:MyBatis全局核心工厂,全局单例常驻内存,是整个框架的入口,主要负责创建每一次数据库操作的SqlSession会话,同时维护全局配置、映射关系、缓存规则。

生命周期:全局单例,项目启动创建、项目销毁销毁,全程复用,不重复创建。

3. SqlSession(数据库会话)

核心职责:MyBatis数据库操作顶层入口,封装所有CRUD方法,管理本次会话的一级缓存、事务、数据库连接,调度底层执行器完成SQL执行。

生命周期:单次请求级别,一次数据库请求对应一个SqlSession,请求结束、事务提交后立即关闭,释放资源。

4. Executor(SQL执行器·核心调度)

核心职责:MyBatisSQL执行的总指挥,SqlSession所有操作最终都会交由Executor执行,负责调度三大处理器、管理一二级缓存、控制SQL执行方式、维护事务状态。

四种实现:SimpleExecutor、ReuseExecutor、BatchExecutor、CachingExecutor(缓存装饰器),适配不同业务场景。

5. StatementHandler(语句处理器)

核心职责:负责JDBC语句的创建、预处理、执行、超时控制,是对接JDBC底层的核心组件。

主要工作:创建PreparedStatement、解析动态SQL、拼接完整执行语句、执行SQL、处理分页排序逻辑,同时也是插件拦截的核心点位。

6. ParameterHandler(参数处理器)

核心职责:专门处理SQL参数绑定,获取方法入参,完成参数类型转换、特殊字符转义、预编译赋值,基于PreparedStatement实现参数预编译,彻底杜绝SQL注入

7. ResultSetHandler(结果集处理器)

核心职责:SQL执行完成后,接收数据库返回的ResultSet结果集,结合反射机制与映射规则(resultType/resultMap),自动封装为Java实体、集合、Map等对象,完成数据库数据与Java对象的双向映射。

二、七大组件完整协作执行流程

  1. 项目启动:SqlSessionFactoryBuilder解析所有配置,构建Configuration,创建全局单例SqlSessionFactory;

  2. 业务请求:SqlSessionFactory创建SqlSession数据库会话;

  3. 会话调度:SqlSession创建Executor执行器,作为SQL执行总指挥;

  4. 语句处理:Executor调用StatementHandler预处理、创建、执行SQL语句;

  5. 参数赋值:ParameterHandler完成参数预编译绑定与类型转换;

  6. 结果封装:SQL执行完毕,ResultSetHandler自动解析结果集、封装为Java对象;

  7. 资源释放:返回结果,关闭SqlSession,释放数据库连接。

三、面试高分总结金句

MyBatis架构由七大核心组件组成,各司其职:Builder负责构建配置工厂、Factory负责生产会话、Session负责对外调用、Executor负责调度执行、三大Handler分别处理语句、参数、结果集,层层解耦、链式协作,支撑了MyBatis高性能、高扩展的核心特性。

极简速背版(面试口述)

MyBatis框架架构核心是七大组件:SqlSessionFactoryBuilder构建工厂,SqlSessionFactory全局生产会话,SqlSession是数据库操作入口,Executor调度SQL执行与缓存,StatementHandler处理SQL语句,ParameterHandler完成参数绑定防注入,ResultSetHandler自动封装结果集,各组件分层协作,完成整套数据库操作流程。

13. 为什么Mybatis需要预编译

标准答案(完整版·面试满分)

MyBatis默认对普通参数查询采用SQL预编译机制,底层基于JDBC的PreparedStatement实现,是框架保障安全性、提升性能、规避语法问题的核心机制,也是企业开发强制规范,具体核心作用与原理详解如下:

一、核心原理

MyBatis使用#{}占位符时,不会直接拼接字符串,而是将SQL模板与参数完全分离。项目启动或首次请求时数据库会对SQL模板进行语法解析、编译、生成执行计划,后续相同模板请求直接复用预编译结构,参数单独赋值,实现SQL模板与参数解耦执行。

二、预编译三大核心价值

1. 彻底杜绝SQL注入攻击(最核心作用)

预编译采用参数化查询,将SQL语句结构和用户输入参数完全隔离,所有用户传入的参数都会被数据库视为纯数据处理,不会被解析为SQL关键字、指令。即便用户输入恶意SQL语句,也只会作为普通参数值转义存储,无法篡改原有SQL逻辑,从底层彻底杜绝SQL注入漏洞,保障系统数据安全。

2. 提升数据库执行性能,减少编译开销

普通未预编译的SQL,每次请求都需要数据库重新解析语法、编译语句、生成执行计划,频繁请求会产生大量数据库编译开销。而预编译SQL模板只需编译一次,支持多次参数复用执行,大幅减少数据库重复编译的IO与CPU消耗,尤其适配高频重复查询场景,有效提升接口吞吐量与响应速度。

3. 自动处理参数转义与类型适配,规避语法异常

预编译机制会自动完成参数类型转换、特殊字符转义,比如单引号、空格、特殊符号等敏感字符自动转义处理,避免字符串拼接导致的SQL语法报错、语句错乱问题。同时能自动适配Java类型与数据库字段类型,减少类型不匹配引发的运行异常,提升代码健壮性。

三、补充面试考点(加分项)

  1. 预编译仅针对**#{}** 生效,${} 为字符串直接拼接,无预编译、无防注入能力,严禁用于接收用户可控参数;

  2. MyBatis预编译是数据库级别的安全优化,而非框架简单字符串转义,防护更彻底、更安全;

  3. 批量操作场景下,预编译SQL模板可批量复用,大幅提升批量增删改的执行效率。

四、面试高分总结金句

MyBatis预编译的核心意义是解耦SQL结构与参数 ,既从底层杜绝SQL注入、保障系统安全,又能复用SQL执行计划、提升数据库性能,同时自动规避语法与类型异常,是兼顾安全、性能、稳定性的核心底层机制。

极简速背版(面试口述)

MyBatis预编译基于PreparedStatement实现,一是彻底隔离SQL语句与参数,杜绝SQL注入;二是复用SQL编译执行计划,减少数据库重复编译开销、提升性能;三是自动转义特殊字符、适配参数类型,避免SQL语法错误,保障系统安全与稳定。

简洁:

  1. 防止SQL注入攻击:预编译使用PreparedStatement,参数占位符隔离SQL逻辑与参数,杜绝注入;

  2. 提升性能:SQL模板编译一次,可多次复用,减少数据库编译开销;

  3. 自动参数类型转换、特殊字符转义,避免语法报错。

14. Mybatis都有哪些Executor执行器?它们之间的区别是什么?

标准答案(完整版·面试满分)

MyBatis的Executor是SQL执行的核心调度器,负责管理Statement创建、SQL执行、缓存调度、事务交互,MyBatis内置四种Executor执行器,包含三种基础执行器、一种缓存装饰器执行器,核心差异集中在Statement复用机制、执行方式、适用场景,具体详解如下:

一、SimpleExecutor(简单执行器·默认执行器)

核心机制 :MyBatis默认执行器,每执行一条SQL,都会全新创建Statement对象,SQL执行完毕后立即关闭Statement,不做任何复用。

核心特点

  1. 无状态、无缓存、无复用逻辑,执行逻辑简单直接;

  2. 每次SQL请求都要经历「创建Statement→执行SQL→关闭Statement」完整流程;

  3. 无资源复用,频繁执行同类SQL会产生大量对象创建、销毁开销。

适用场景:绝大多数普通业务场景、SQL语句零散、重复执行率低的业务,是项目默认通用选择。

二、ReuseExecutor(可复用执行器)

核心机制 :基于SqlSession会话级别缓存Statement对象,以SQL语句为key缓存预处理后的Statement,同一会话中重复执行相同SQL时,直接复用缓存的Statement,无需重复创建。

核心特点

  1. 仅在同一个SqlSession内复用Statement,会话关闭后缓存清空;

  2. 大幅减少频繁执行相同SQL时的Statement创建、销毁开销,提升执行效率;

  3. 占用少量内存缓存Statement对象,会话内SQL重复度越高,性能收益越明显。

适用场景 :同一会话中需要重复执行相同SQL的业务场景,如循环查询、重复条件检索。

三、BatchExecutor(批量执行器)

核心机制 :专门适配批量操作的执行器,不会立即执行SQL,而是缓存多条增删改SQL语句,等待事务提交或主动触发刷新时,统一批量提交执行。

核心特点

  1. 延迟执行SQL,攒批统一提交,大幅减少数据库IO交互次数;

  2. 底层复用同一个Statement批量执行多条数据,极大提升批量增删改性能;

  3. 不支持实时返回数据,执行后无法立即获取主键、受影响行数,需提交事务后获取;

  4. 仅对增删改操作生效,查询操作无优化效果。

适用场景:大批量数据新增、修改、更新、数据迁移、批量同步等业务场景。

四、CachingExecutor(缓存执行器·装饰器)

核心机制 :不属于独立执行器,是装饰器模式 实现,用于包装以上三种基础执行器,为普通执行器增强二级缓存能力

15. Mybatis是如何进行分页的?分页插件的原理是什么?(含完整实现代码)

标准答案(完整版·面试满分)

MyBatis本身不自带物理分页功能,仅支持逻辑分页,生产环境均通过自定义分页插件实现物理分页。核心基于MyBatis拦截器插件机制,拦截SQL执行流程,动态拼接分页SQL、统计总条数,实现无侵入、全局通用的分页能力,是企业开发标准分页方案,下面分原生分页、插件原理、完整落地代码逐层解析。

一、MyBatis两种分页方式(核心区别)

1. 逻辑分页(RowBounds,官方自带、生产禁用)

MyBatis原生提供RowBounds对象实现分页,属于内存逻辑分页,并非数据库物理分页。

实现原理 :执行完整查询SQL查询出全部数据,加载到JVM内存中,再通过RowBounds的offset、limit参数在内存中截取对应分页数据。

致命缺陷

① 数据量大时会查询全表数据,占用大量数据库IO、JVM内存,极易导致OOM;

② 无总条数、总页数返回,无法实现前端分页组件;

③ 性能极差,生产环境绝对禁止使用,仅适用于极小数据量场景。

2. 物理分页(插件分页,企业生产首选)

通过MyBatis拦截器插件,在SQL执行前动态拦截查询语句,根据分页参数自动拼接 LIMIT 分页语句,同时自动执行count统计语句,精准获取总数据量,仅查询当前页数据,数据库IO极小、性能优异,是互联网项目标准分页方案。

二、MyBatis分页插件核心原理(面试高频必背)

分页插件底层依托MyBatis四大核心拦截器 机制,基于动态代理+责任链模式实现,核心拦截点位为StatementHandler,完整执行原理如下:

1. 核心拦截对象

拦截 StatementHandler.prepare() / parameterize() 方法,在SQL预编译、执行前完成拦截修改,不会影响增删改逻辑。

2. 完整执行流程

① 业务层传入分页参数(pageNum、pageSize),存入ThreadLocal线程上下文;

② 插件拦截Mapper查询方法,获取原始执行SQL;

③ 解析SQL类型,判断为SELECT查询语句;

④ 动态拼接数据库分页语法(MySQL拼接LIMIT、Oracle拼接ROWNUM),生成分页SQL;

⑤ 自动生成COUNT统计SQL,查询数据总条数、总页数;

⑥ 执行分页SQL获取当前页数据,执行统计SQL获取总数;

⑦ 封装分页结果集(页码、页大小、总数、总页数、当前页数据);

⑧ 清空线程上下文分页参数,防止线程复用导致参数污染。

3. 核心设计亮点

无侵入式增强:无需修改任意Mapper、XML SQL,一次配置全局所有查询自动分页;

线程隔离:基于ThreadLocal存储分页参数,多线程场景安全无冲突;

数据库适配:可根据数据库类型动态适配分页语法,支持多数据库切换。

三、原生手动物理分页(基础写法,理解原理)

不依赖插件,手动在XML中写LIMIT分页SQL,适合新手理解物理分页本质:

1. Mapper接口

java 复制代码
// 分页查询用户列表
List<User> selectUserByPage(@Param("offset") Integer offset, @Param("pageSize") Integer pageSize);
// 查询总条数
Integer selectUserTotal();

2. Mapper XML

java 复制代码
<!-- 分页查询 -->
<select id="selectUserByPage" resultType="com.entity.User">
SELECT id,username,phone FROM user LIMIT #{offset},#{pageSize}
</select>
<!-- 统计总条数 -->
<select id="selectUserTotal" resultType="java.lang.Integer">
SELECT COUNT(*) FROM user
</select>

3. 业务层调用

java 复制代码
// 页码、页大小
int pageNum = 1;
int pageSize = 10;
// 计算偏移量
int offset = (pageNum - 1) * pageSize;
// 查询分页数据
List<User> userList = userMapper.selectUserByPage(offset, pageSize);
// 查询总条数
Integer total = userMapper.selectUserTotal();
// 封装分页结果
PageResult<User> pageResult = new PageResult<>(pageNum, pageSize, total, userList);

缺点:每个查询都要手动写分页SQL、统计SQL,代码冗余、维护成本高,仅适合学习,生产统一使用插件。

四、自定义MyBatis分页插件(完整可落地生产代码)

无需依赖MyBatis-Plus,原生自定义分页插件,轻量无依赖、适配所有SpringBoot+MyBatis项目,全局自动分页。

1. 分页参数实体类(ThreadLocal存储)

java 复制代码
/**
* 分页参数上下文
*/
public class PageContext {
private static final ThreadLocal<PageParam> PAGE_PARAM = new ThreadLocal<>();

// 设置分页参数
public static void setPage(Integer pageNum, Integer pageSize) {
PAGE_PARAM.set(new PageParam(pageNum, pageSize));
}

// 获取分页参数
public static PageParam getPageParam() {
return PAGE_PARAM.get();
}

// 清空参数,防止线程污染
public static void clear() {
PAGE_PARAM.remove();
}

// 分页参数内部类
public static class PageParam {
private Integer pageNum;
private Integer pageSize;
private Integer offset;

public PageParam(Integer pageNum, Integer pageSize) {
this.pageNum = pageNum;
this.pageSize = pageSize;
this.offset = (pageNum - 1) * pageSize;
}
// getter、setter省略
public Integer getOffset() { return offset; }
public Integer getPageSize() { return pageSize; }
}

2. 分页结果统一返回实体

java 复制代码
import java.util.List;
/**
* 分页统一返回结果
*/
public class PageResult<T> {
// 当前页码
private Integer pageNum;
// 每页条数
private Integer pageSize;
// 总条数
private Long total;
// 总页数
private Integer pages;
// 当前页数据
private List<T> list;

public PageResult(Integer pageNum, Integer pageSize, Long total, List<T> list) {
this.pageNum = pageNum;
this.pageSize = pageSize;
this.total = total;
this.pages = (int) Math.ceil((double) total / pageSize);
this.list = list;
}
// getter、setter省略
}

3. 核心分页拦截器(MyBatis插件)

java 复制代码
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

/**
* 自定义MyBatis分页插件
* 拦截StatementHandler,动态拼接分页SQL、统计总条数
*/
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class PageInterceptor implements Interceptor {

@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取当前线程分页参数
PageContext.PageParam pageParam = PageContext.getPageParam();
// 无分页参数,直接放行,不拦截
if (pageParam == null) {
return invocation.proceed();
}

// 获取StatementHandler,解析原始SQL
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
String originalSql = (String) metaObject.getValue("delegate.boundSql.sql");

// 仅拦截查询语句
if (!originalSql.trim().toLowerCase().startsWith("select")) {
PageContext.clear();
return invocation.proceed();
}

// 1. 拼接分页SQL(MySQL语法)
String pageSql = originalSql + " LIMIT " + pageParam.getOffset() + "," + pageParam.getPageSize();
// 替换原始SQL为分页SQL
metaObject.setValue("delegate.boundSql.sql", pageSql);

// 2. 执行count统计SQL,获取总条数
Connection connection = (Connection) invocation.getArgs()[0];
String countSql = "SELECT COUNT(*) FROM (" + originalSql + ") t";
PreparedStatement countStatement = connection.prepareStatement(countSql);
ResultSet resultSet = countStatement.executeQuery();
Long total = 0L;
if (resultSet.next()) {
total = resultSet.getLong(1);
}
resultSet.close();
countStatement.close();

// 3. 将总条数存入上下文,供业务层获取
PageTotalContext.setTotal(total);
// 执行分页SQL
Object result = invocation.proceed();
// 清空线程参数,避免污染
PageContext.clear();
return result;
}

@Override
public Object plugin(Object target) {
// 对StatementHandler对象生成代理
return Plugin.wrap(target, this);
}

@Override
public void setProperties(Properties properties) {
// 配置参数初始化,无需实现
}
}

4. 总条数上下文存储类

java 复制代码
/**
* 分页总条数上下文
*/
public class PageTotalContext {
private static final ThreadLocal<Long> TOTAL = new ThreadLocal<>();

public static void setTotal(Long total) { TOTAL.set(total); }
public static Long getTotal() { return TOTAL.get(); }
public static void clear() { TOTAL.remove(); }
}

5. 注册插件(mybatis-config.xml)

java 复制代码
<plugins>
<!-- 注册自定义分页插件 -->
<plugin interceptor="com.plugin.PageInterceptor"/>
</plugins>

6. 业务层极简调用(无需改Mapper、XML)

java 复制代码
@Service
public class UserService {
@Autowired
private UserMapper userMapper;

public PageResult<User> getUserPage(Integer pageNum, Integer pageSize) {
    // 开启分页
    PageContext.setPage(pageNum, pageSize);
    // 直接查询原始列表,插件自动分页
    List<User> userList = userMapper.selectAllUser();
    // 获取总条数
    Long total = PageTotalContext.getTotal();
    // 封装分页结果
    PageTotalContext.clear();
    return new PageResult<>(pageNum, pageSize, total, userList);
 }
}

五、面试高分总结金句

MyBatis原生仅支持内存逻辑分页,性能极差不适合生产;企业级分页基于MyBatis插件拦截机制实现,通过拦截StatementHandler预处理SQL,动态拼接分页语句、自动统计总条数,实现无侵入全局物理分页,核心优势是代码零冗余、性能高、扩展性强,是互联网项目标准分页实现方案。

极简速背版(面试口述)

MyBatis分页分两种,原生RowBounds是内存逻辑分页,查询全量数据后内存截取,性能差;生产用插件物理分页,基于MyBatis拦截器拦截StatementHandler,执行前动态拼接LIMIT分页SQL、统计总条数,仅查询当前页数据,无侵入、高性能,适配所有分页场景。

简洁版

1. 原生分页:RowBounds逻辑分页,加载全量数据后内存截取,大数据量性能差、生产禁用。

2. 插件分页原理:基于MyBatis插件机制拦截SQL预处理流程,动态拼接分页语法、自动统计数据总量,实现全局无侵入物理分页。

3. 核心优势:仅查询当前页数据、IO开销小、无需改造业务SQL、适配所有复杂查询。

核心特点

  1. 仅负责缓存调度,不改变原有SQL执行逻辑;

  2. 查询前优先校验二级缓存,命中则直接返回缓存数据,无需访问数据库;

  3. 增删改操作时自动清空对应namespace二级缓存,保证数据一致性;

  4. 项目开启二级缓存后,框架自动为基础执行器包装CachingExecutor。

适用场景:开启二级缓存、读多写少、静态数据居多的业务场景。

五、四大执行器核心区别总结(面试高频)

  1. 复用逻辑不同:Simple无复用、Reuse按SQL复用Statement、Batch攒批复用执行、Caching增强缓存能力;

  2. 执行时机不同:Simple/Reuse实时执行SQL、Batch延迟批量执行;

  3. 性能侧重不同:适配普通查询、重复SQL、批量操作、缓存查询四类不同业务;

  4. 依赖条件不同:CachingExecutor依赖二级缓存开启,其余三种为基础原生执行器。

六、面试高分总结金句

MyBatis四种Executor各司其职:默认SimpleExecutor适配通用场景,ReuseExecutor优化同会话重复SQL性能,BatchExecutor专为批量增删改设计、减少IO交互,CachingExecutor通过装饰器增强二级缓存能力,开发中可根据业务场景灵活切换执行器优化性能。

极简速背版(面试口述)

MyBatis有四种执行器:SimpleExecutor是默认执行器,每次SQL新建关闭Statement;ReuseExecutor同会话复用相同SQL的Statement;BatchExecutor延迟攒批、批量执行SQL,适配批量操作;CachingExecutor是缓存装饰器,为其他执行器增强二级缓存功能,四类执行器分别适配通用、重复查询、批量操作、缓存查询场景。

简洁:

  1. SimpleExecutor:默认执行器,每次请求新建Statement,用完关闭;

  2. ReuseExecutor:复用Statement对象,减少创建销毁开销;

  3. BatchExecutor:批量执行器,缓存多条SQL,事务提交后统一执行,适合批量操作;

  4. CachingExecutor:缓存装饰执行器,包装以上执行器,实现一二级缓存逻辑。

15. Mybatis中如何指定使用哪一种Executor执行器?

标准答案(完整版·面试满分)

MyBatis支持全局默认指定单次会话临时指定SpringBoot配置指定 三种方式设置Executor执行器,优先级:代码会话级 > 全局配置,企业开发根据通用场景全局配置,特殊场景动态覆盖,完整配置方式详解如下:

一、全局统一指定(mybatis-config.xml 核心常用)

在MyBatis全局配置文件中,通过defaultExecutorType标签设置项目全局默认执行器,全局生效、所有SqlSession默认复用该配置。

可选值

  1. SIMPLE:默认值,SimpleExecutor普通执行器;

  2. REUSE:ReuseExecutor可复用Statement执行器;

  3. BATCH:BatchExecutor批量执行器。

配置示例

XML 复制代码
<settings>
  <!-- 全局指定批量执行器 -->
  <setting name="defaultExecutorType" value="BATCH"/>
</settings>

适用场景:项目通用场景统一配置,比如批量业务较多的项目全局配置BATCH,普通业务默认SIMPLE。

二、代码动态指定(单次SqlSession级别·优先级最高)

可在代码中开启SqlSession时,手动传入执行器类型,仅当前会话生效,覆盖全局配置,灵活适配临时特殊场景。

代码示例

java 复制代码
// 获取指定批量执行器的SqlSession
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);

核心特点

  1. 仅当前会话有效,不影响全局配置;

  2. 优先级高于mybatis-config.xml全局配置;

  3. 适合临时批量操作、单次重复SQL查询等特殊场景。

三、SpringBoot项目配置方式(企业主流)

SpringBoot整合MyBatis时,无需xml配置,直接在yml/properties中全局指定默认执行器。

yml配置示例

XML 复制代码
mybatis:
  configuration:
    default-executor-type: BATCH

四、关键面试考点(加分项)

  1. CachingExecutor无需手动配置:只要开启二级缓存,MyBatis自动装饰原有执行器,无需手动指定;

  2. 执行器切换只对当前SqlSession生效,会话关闭后失效;

  3. BatchExecutor仅优化增删改,查询操作不生效,且需要手动commit提交才能批量执行;

  4. 生产通用规范:全局默认SIMPLE,批量接口单独代码指定BATCH,按需优化性能。

五、面试高分总结金句

MyBatis指定执行器有三种方式:全局配置统一默认执行器,代码动态指定实现单次会话个性化配置,SpringBoot通过配置文件简化全局设置;代码配置优先级最高,二级缓存开启后自动包装为CachingExecutor,无需手动指定。

极简速背版(面试口述)

可以通过三种方式指定Executor:一是mybatis-config.xml全局配置defaultExecutorType;二是代码开启SqlSession时动态指定,优先级更高;三是SpringBoot配置文件直接配置。CachingExecutor无需手动设置,开启二级缓存自动生效,生产中全局默认SIMPLE,批量场景临时指定BATCH。

简洁版

  1. 全局配置:通过mybatis-config.xml的defaultExecutorType设置SIMPLE/REUSE/BATCH;

  2. 代码动态配置:openSession时传入ExecutorType,局部覆盖全局配置;

  3. SpringBoot通过yml配置default-executor-type实现全局指定;

  4. 二级缓存开启后自动启用CachingExecutor,无需手动指定。

16. Mybatis是否支持延迟加载?延迟加载的基本原理是什么?

标准答案(完整版·面试满分)

MyBatis完全支持延迟加载(懒加载) ,专门用于优化一对一、一对多关联查询的性能,核心是按需加载关联数据、避免无效查询,是解决MyBatis N+1性能问题的重要优化手段,完整原理、配置、流程、考点详解如下:

一、核心定义

延迟加载指:查询主表数据时,只加载主表本身字段,不主动查询关联表数据;只有当业务代码主动调用主表实体的关联属性getter方法时,MyBatis才会触发额外的SQL查询,加载对应的关联数据,实现「用则查、不用则不查」的按需加载机制。

二、延迟加载底层实现原理

  1. 核心技术:CGLIB动态代理

MyBatis启动加载关联映射配置后,查询主表数据时,不会直接封装关联实体/集合对象,而是为关联属性生成CGLIB动态代理占位对象,暂存主表信息、关联查询参数、SQL语句等元数据,不执行关联查询SQL。

2.触发加载机制

当程序首次调用关联属性的getter方法时,CGLIB代理拦截方法调用,触发MyBatis内部延迟加载逻辑,通过保存的元数据执行关联查询SQL,获取关联数据并赋值到代理对象中,后续再次调用getter方法直接复用已加载数据,不会重复查库。

  1. 数据缓存机制

延迟加载的数据会存入当前SqlSession一级缓存,同一会话内重复访问关联属性,直接读取缓存,避免重复SQL查询。

三、延迟加载核心配置(企业必备)

MyBatis默认关闭延迟加载,需要手动在全局配置开启,核心配置参数:

  1. lazyLoadingEnabled=true:全局开启延迟加载功能,默认false关闭;

  2. aggressiveLazyLoading=false:关闭积极懒加载(默认true),设置为false后,仅调用关联属性才触发查询,避免全部关联数据一次性加载。

四、完整执行流程

  1. 执行主表查询SQL,加载主表基础数据,关联字段封装为CGLIB代理对象;

  2. 返回主表实体,此时关联数据为空,未执行关联查询;

  3. 业务代码调用关联属性getter方法,代理拦截触发延迟加载;

  4. 执行关联查询SQL,获取从表数据并封装赋值;

  5. 缓存关联数据,后续访问直接复用,完成按需加载。

五、优缺点分析(面试加分项)

优点

  1. 按需加载数据,避免加载无用关联数据,大幅减少无效数据库查询;

  2. 优化关联查询性能,适配关联数据多、按需使用的业务场景;

  3. 有效缓解N+1查询带来的数据库压力。

缺点

  1. 若业务代码批量遍历所有关联属性,会触发多次延迟查询,反而产生大量N+1 SQL,性能更差;

  2. 依赖SqlSession会话,会话关闭后无法触发延迟加载,会报空指针或代理异常;

  3. 调试难度略高,代理对象容易造成日志打印、序列化异常。

六、适用与不适用场景

适用场景:关联数据量大、业务中大概率只使用主表数据、极少访问关联属性的场景。

不适用场景 :业务必须使用关联数据、批量遍历关联属性的场景,此类场景优先使用联表嵌套结果查询

七、面试高分总结金句

MyBatis支持延迟加载,核心基于CGLIB动态代理实现,通过按需触发关联查询减少无效DB访问,优化关联查询性能;开发中需按需开启、规避批量遍历导致的N+1问题,结合业务场景选择懒加载或联表查询,实现性能最优。

极简速背版(面试口述)

MyBatis支持一对一、一对多关联延迟加载,默认关闭,需手动配置开启。底层基于CGLIB动态代理,查询主表时只加载主数据,关联属性生成代理对象,调用关联属性getter方法时才触发SQL查询,实现按需加载,减少无效数据库访问,优化关联查询性能。

简洁版

  1. MyBatis支持延迟加载,用于优化一对一、一对多关联查询;

  2. 底层依靠CGLIB动态代理,实现按需加载关联数据;

  3. 主表查询只加载主数据,关联属性调用时才执行子查询;

  4. 可减少无效查库,但批量遍历会触发N+1问题,需合理使用。

17. #{}和${}的区别

标准答案(完整版·面试满分)

#{} 和 ${} 是MyBatis两种参数占位符,核心本质、执行机制、安全性、性能、适用场景完全不同,是面试高频基础必考题型,也是生产开发必须遵守的编码规范,完整区别详解如下:

一、核心本质与底层执行机制差异

1. #{}:预编译参数占位符

底层基于JDBC PreparedStatement 实现参数化查询。执行时不会直接拼接SQL字符串,而是先发送不带参数的SQL模板 到数据库完成预编译、生成执行计划,再单独传入参数进行赋值,实现SQL结构与参数完全隔离

2. ${}:纯字符串直接拼接

底层无预编译机制,等价于Java字符串拼接。MyBatis直接将参数内容原样拼接到SQL语句中,整体形成一条完整SQL后发送给数据库执行,SQL结构和参数完全糅合在一起

二、五大核心区别(面试必背)

1. 安全性差异(最核心)

#{}:彻底杜绝SQL注入。所有用户传入参数只会被数据库识别为纯数据,不会解析为SQL关键字、逻辑语句,恶意参数会自动转义,无法篡改原有SQL逻辑,安全可靠。

${}:存在严重SQL注入漏洞。参数直接拼接进SQL,恶意输入的or、delete、drop等关键字会被当作SQL逻辑执行,极易造成数据泄露、数据删除、库表篡改等重大安全问题。

2. 性能差异

#{}:支持SQL模板预编译复用,相同SQL模板只需编译一次,多次传参复用执行计划,减少数据库编译开销,查询性能更优。

${}:每次拼接生成全新SQL字符串,数据库每次都需要重新解析、编译、生成执行计划,无任何复用,高频场景性能较差。

3. 自动转义与语法处理差异

#{}:自动完成特殊字符转义、参数类型适配、自动拼接单引号。例如传入带单引号、空格、特殊符号的参数,会自动转义处理,同时自动适配数据库字段类型,不会出现SQL语法错误。

${}:无任何转义、无类型适配、无单引号包裹。参数原样拼接,若参数含特殊字符极易导致SQL语法报错,需要开发者手动处理拼接符号与转义逻辑。

4. 参数适配规则差异

#{}:适配普通查询条件、字段值、数值参数,自动识别Java基础类型、实体属性、集合参数。

${}:仅适配动态SQL结构参数,无法用于普通值查询,适合表名、字段名、排序字段、排序规则这类非值类型参数。

5. 解析时机差异

#{}:运行时动态参数赋值,依赖预编译机制解析。

${}:启动或运行时直接字符串替换,无底层编译解析流程。

三、严格适用场景(生产编码规范)

1. 一律使用 #{} 的场景(99%业务)

所有用户输入参数、查询条件、更新值、主键、数值、字符串等参数值传递场景,必须使用#{},保障安全与性能。

2. 只能使用 ${} 的场景(无法用#{}替代)

MyBatis预编译不支持动态SQL结构替换,以下场景必须用${}:

① 动态表名(分表查询、动态切换数据表);

② 动态排序字段、排序规则(order by {column} {sort});

③ 动态字段查询、动态分组字段。

四、生产避坑要点(面试加分项)

  1. ${} 严禁接收用户可控参数 ,若业务必须使用,需手动增加参数白名单校验,过滤恶意SQL关键字;

  2. #{} 自动加单引号,不能用于表名、字段名,会导致语法报错;

  3. 模糊查询、in查询优先用#{}搭配bind标签、foreach标签实现,杜绝${}拼接。

五、面试高分总结金句

#{}是预编译占位符,安全防注入、性能优、自动转义适配参数值场景;{}是字符串直接拼接,存在注入风险、无预编译、仅适配动态表名字段等SQL结构场景,生产遵循**值用#{}、结构用{}、${}必做校验**的核心规范。

极简速背版(面试口述)

#{}基于PreparedStatement预编译,隔离SQL与参数,防SQL注入、性能好、自动转义,用于普通参数值查询;${}是字符串直接拼接,无预编译、有注入风险,仅用于动态表名、排序字段等SQL结构场景,禁止接收用户可控参数。

简洁版

  1. #{}:预编译、防注入、自动转义、性能高,适用于所有参数值传递场景;

  2. ${}:字符串拼接、有注入风险、无转义、性能差,仅用于动态表名、排序字段等结构替换场景;

  3. 编码规范:优先#{},迫不得已使用${}必须做参数白名单校验。

简洁:

  1. #{} :预编译占位符,底层PreparedStatement,自动转义参数,彻底防止SQL注入,常规参数优先使用;

  2. ${} :字符串直接拼接,无预编译、无转义,存在SQL注入风险

  3. 适用场景:动态表名、排序字段、分表字段只能使用${},其余场景一律用#{}。

18. 模糊查询like语句该怎么写?写出实现代码

标准答案(完整版·面试满分+可运行代码)

MyBatis模糊查询严禁使用${}拼接 ,会产生SQL注入风险。生产环境仅推荐两种安全、支持预编译、防注入的标准写法,适配MySQL、多数据库兼容场景,附带完整可运行代码、优劣对比与避坑要点。

核心前提 :两种写法均基于 #{} 预编译,底层使用PreparedStatement,彻底杜绝SQL注入,性能稳定。

一、写法一:SQL内直接拼接通配符(最常用、最简)

原理:在SQL语句中直接拼接 % 通配符,参数通过#{}预编译传入,简单高效,适配绝大多数MySQL场景。

完整XML代码示例

XML 复制代码
<!-- 根据用户名模糊查询用户 -->
<select id="getUserByLikeName" resultType="User">
    SELECT id, username, age, email
    FROM user
    WHERE username LIKE CONCAT('%', #{keyword}, '%')
</select>

Mapper接口代码

java 复制代码
List<User> getUserByLikeName(@Param("keyword") String keyword);

补充说明

  1. 使用 CONCAT() 函数拼接通配符与参数,是MySQL标准写法;

  2. 全程预编译,无SQL注入风险;

  3. 缺点:Oracle、SQL Server等数据库拼接函数语法不同,跨数据库兼容性差

二、写法二:bind标签绑定变量(企业高阶·跨库通用·推荐)

原理 :通过MyBatis自带的<bind>标签,在Java层面提前拼接好带通配符的模糊参数,绑定为新变量,SQL直接引用变量,完全屏蔽数据库语法差异,兼容所有数据库。

完整XML代码示例

XML 复制代码
<select id="getUserByLikeNameBind" resultType="User">
    <!-- 提前绑定模糊参数,全局通用 -->
    <bind name="likeKeyword" value="'%' + keyword + '%'"/>
    SELECT id, username, age, email
    FROM user
    WHERE username LIKE #{likeKeyword}
</select>

Mapper接口代码

java 复制代码
List<User> getUserByLikeNameBind(@Param("keyword") String keyword);

核心优势

  1. 跨数据库兼容:无需修改SQL,适配MySQL、Oracle、SQL Server;

  2. 复用性强:同一参数可在当前SQL多处复用;

  3. 预编译安全,杜绝注入,企业复杂项目首选。

三、绝对禁止写法(面试高频坑点)

错误写法(存在严重SQL注入)

LIKE '%${keyword}%'

风险说明 :${}为字符串直接拼接,无预编译,用户传入恶意参数(如 ' or 1=1 --)可篡改SQL逻辑,造成数据泄露、全表查询、数据删除等安全问题。

四、两种规范写法优劣对比

  1. CONCAT拼接写法:简单便捷、适合单一MySQL项目,缺点跨库兼容性差;

  2. bind标签写法:兼容性强、通用性高、适合分布式、多数据库项目,是企业高阶规范。

五、面试高分总结金句

MyBatis模糊查询禁止使用${}拼接,优先采用两种#{}预编译写法:单一MySQL项目用CONCAT函数拼接通配符,多数据库项目用bind标签封装模糊参数,既保证SQL安全、杜绝注入,又兼顾业务兼容性。

极简速背版(面试口述)

MyBatis模糊查询有两种安全写法,一是通过CONCAT函数拼接#{}参数实现模糊查询,适合MySQL;二是通过bind标签提前绑定带通配符的参数,跨数据库通用,两种方式均基于预编译防SQL注入,禁止使用${}字符串拼接。

简洁版

  1. MySQL专用:LIKE CONCAT('%', #{keyword}, '%'),简单安全;

  2. 跨库通用:通过<bind>标签拼接通配符,适配所有数据库;

  3. 编码红线:严禁使用${}拼接模糊查询,存在SQL注入风险。

19. 在mapper中如何传递多个参数?(四种方式+完整代码+优劣对比)

标准答案(完整版·面试满分+可运行代码)

MyBatis Mapper接口传递多个参数,主流分为四种规范写法 ,各有适配场景、可读性、维护性差异。企业开发优先使用 @Param注解POJO实体传参,禁止使用原生下标取值,下面逐一讲解原理、完整代码、优缺点与适用场景。

一、方式一:@Param 注解显式绑定参数(企业首选·最推荐)

核心原理 :通过@Param("参数名")注解,手动给每个入参定义唯一参数名,XML中直接通过自定义参数名取值,参数顺序不受限制、可读性极强、无版本兼容问题,是多参数传参最优方案。

1. Mapper接口代码

java 复制代码
/**
 * 根据用户名和年龄分页查询用户(多参数传参)
 * @param username 用户名
 * @param age 年龄
 * @return 用户列表
 */
List<User> getUserByUsernameAndAge(@Param("username") String username,
                                   @Param("age") Integer age);

2. Mapper XML代码

XML 复制代码
<select id="getUserByUsernameAndAge" resultType="User">
    SELECT id,username,age,email
    FROM user
    WHERE username = #{username} AND age = #{age}
</select>

优缺点

✅ 优点:参数名硬绑定、可读性高、参数顺序不影响、支持任意参数数量、适配零散多参数场景;

❌ 缺点:参数过多时注解冗余,适合**参数数量较少(5个以内)**的场景。

二、方式二:POJO实体类传参(参数多·业务参数固定首选)

核心原理 :将多个零散查询参数,封装为自定义实体类/Query参数类,XML中直接通过属性名.字段名取值,参数高度聚合,适配复杂多条件查询。

1. 自定义参数POJO

java 复制代码
@Data
public class UserQuery {
    // 用户名
    private String username;
    // 年龄
    private Integer age;
    // 邮箱
    private String email;
}

2. Mapper接口代码

java 复制代码
List<User> getUserByQuery(UserQuery userQuery);

3. Mapper XML代码

XML 复制代码
<select id="getUserByQuery" resultType="User">
    SELECT id,username,age,email
    FROM user
    WHERE username = #{username} AND age = #{age} AND email = #{email}
</select>

优缺点

✅ 优点:参数聚合整洁、接口简洁、扩展性强,新增参数无需修改接口,只需修改实体字段;

❌ 缺点:需要额外创建参数实体类,适合参数多、查询条件固定、复杂条件查询场景。

三、方式三:Map集合传参(临时动态参数·快速开发)

核心原理 :将多个参数以Key-Value键值对形式存入Map,XML中通过map的key名称获取参数值,无需定义实体类,适合临时多变参数场景。

1. Mapper接口代码

java 复制代码
List<User> getUserByMapParam(Map<String,Object> paramMap);

2. 业务层调用代码

java 复制代码
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("username","张三");
paramMap.put("age",18);
List<User> userList = userMapper.getUserByMapParam(paramMap);

3. Mapper XML代码

XML 复制代码
<select id="getUserByMapParam" resultType="User">
    SELECT id,username,age,email
    FROM user
    WHERE username = #{username} AND age = #{age}
</select>

优缺点

✅ 优点:无需定义实体、灵活轻便、适配临时动态参数;

❌ 缺点:无代码提示、参数无约束、可读性差、维护成本高,不推荐正式项目主力使用,仅适合快速开发、临时查询。

四、方式四:原生顺序下标传参(不推荐·面试了解即可)

核心原理:MyBatis默认未加注解时,多参数会按顺序封装为param1、param2...,通过下标顺序取值,不绑定参数名。

1. Mapper接口代码

java 复制代码
List<User> getUserByParamIndex(String username, Integer age);

2. Mapper XML代码

XML 复制代码
<select id="getUserByParamIndex" resultType="User">
    SELECT id,username,age,email
    FROM user
    WHERE username = #{param1} AND age = #{param2}
</select>

致命缺点(企业禁用)

  1. 完全依赖参数顺序,接口参数顺序改动,XML必须同步改,极易出错;

  2. 无可读性、无代码提示、维护极差;

  3. 高版本MyBatis默认不支持,兼容性差,生产绝对禁止使用

五、四种方式核心选型规范(面试高频)

  1. 参数≤5个、零散参数 :优先 @Param注解,简洁清晰、安全稳定;

  2. 参数≥5个、复杂多条件查询 :优先 POJO实体传参,高扩展、易维护;

  3. 临时动态参数、快速调试 :临时使用 Map传参

  4. 原生下标取值:仅作了解,生产全程禁用。

六、面试高分总结金句

Mapper多参数传参共有四种方式,企业规范优先使用@Param注解和POJO实体传参,前者适配少量零散参数,后者适配大量复杂参数;Map仅用于临时动态场景,原生下标取值可读性差、极易出错,生产禁止使用。

极简速背版(面试口述)

MyBatis Mapper多参数传递有四种方式:一是@Param注解绑定参数名,企业最常用;二是封装POJO实体传参,适合多参数复杂查询;三是Map集合传参,适配临时动态参数;四是原生param下标取值,依赖参数顺序,可读性差,生产禁用。

简洁版

  1. @Param注解:最优方案,参数绑定明确、顺序无关、可读性强;

  2. POJO实体传参:多参数首选,参数聚合、扩展性高;

  3. Map传参:灵活无实体,适合临时查询,维护性差;

  4. 下标传参:依赖顺序、极易出错,生产禁止。

简洁:

  1. 使用**@Param**注解绑定参数名(企业推荐);

  2. 封装POJO实体类传参;

  3. 封装Map集合传参;

  4. 原生param1/param2顺序取值(不推荐、可读性差)。

20. Mybatis如何执行批量操作,能返回数据库主键列表吗?

标准答案(完整版·面试满分)

MyBatis 支持多种批量数据库操作方案,同时完全支持批量插入返回数据库自增主键列表 ,适配不同批量业务场景,下面从批量操作实现方式、批量主键返回方案、核心原理、生产坑点全方位详解,均为企业生产标准方案。

一、MyBatis三种批量操作实现方式(优先级从高到低)

1. BatchExecutor 批量执行器(原生最优·推荐)

这是MyBatis原生最高效的批量操作方案,核心原理是延迟SQL执行、复用Statement、攒批统一提交,大幅减少数据库IO交互次数,性能远优于循环单条插入。

核心执行机制

  1. 手动开启 ExecutorType.BATCH 类型的SqlSession;

  2. 循环执行insert/update/delete方法,MyBatis仅缓存SQL语句,不立即提交数据库

  3. 所有数据循环完毕后,统一执行commit提交,一次性批量执行所有SQL;

  4. 底层复用同一个PreparedStatement,减少语句创建销毁开销,批量性能极致优化。

代码示例

java 复制代码
// 开启批量执行器SqlSession,手动事务提交
SqlSession batchSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
UserMapper mapper = batchSession.getMapper(UserMapper.class);
// 循环批量插入
for (User user : userList) {
    mapper.insert(user);
}
// 统一批量提交事务,触发SQL执行
batchSession.commit();
batchSession.close();

优缺点:性能最高、无SQL冗余、支持大批量数据;缺点是默认无法实时获取单条主键,需配合主键配置实现批量返回主键。

2. foreach标签拼接批量SQL(简单轻量)

通过Mapper XML中foreach标签遍历集合,拼接多条数据为单条批量INSERT/UPDATE语句,一次性执行,无需手动开启批量会话,开发简单。

XML代码示例

XML 复制代码
<insert id="batchInsertUser" parameterType="java.util.List">
    INSERT INTO user(username, age, email)
    VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.username}, #{item.age}, #{item.email})
    </foreach>
</insert>

优缺点 :使用简单、无需编码配置;缺点是受数据库SQL长度限制,大批量数据会触发SQL超长报错,仅适合小批量数据操作。

3. MyBatis-Plus内置批量方法(业务快速开发)

整合MyBatis-Plus后,可直接调用 saveBatch() 内置批量方法,框架底层自动封装BatchExecutor逻辑,无需手动编写批量代码,适配快速迭代业务。

二、批量操作返回数据库主键列表(核心面试考点)

结论 :MyBatis 完全支持批量插入返回所有自增主键,仅支持MySQL等自增主键数据库,Oracle需通过selectKey实现。

1. 核心配置(必加)

在insert标签中开启批量主键返回,三个核心属性缺一不可:

useGeneratedKeys="true":开启获取数据库自增主键能力;

keyProperty="id":指定实体类主键字段;

keyColumn="id":指定数据库自增主键字段。

完整可运行XML配置

XML 复制代码
<insert id="batchInsertUser" parameterType="java.util.List" 
        useGeneratedKeys="true" 
        keyProperty="id" 
        keyColumn="id">
    INSERT INTO user(username, age, email)
    VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.username}, #{item.age}, #{item.email})
    </foreach>
</insert>

2. 主键返回原理

批量插入执行完成后,MyBatis会自动将数据库生成的每一条自增主键,逐一回写到传入的实体List集合中,遍历集合即可获取所有数据的主键ID。

3. 调用示例

java 复制代码
// 传入实体集合
List<User> userList = new ArrayList<>();
userList.add(new User("张三", 18));
userList.add(new User("李四", 20));
// 批量插入,自动回写主键
userMapper.batchInsertUser(userList);
// 遍历获取所有主键ID
for (User user : userList) {
    System.out.println(user.getId());
}

三、BatchExecutor批量返回主键特殊说明

  1. 低版本MyBatis中,BatchExecutor攒批提交模式默认无法实时返回主键

  2. 高版本MyBatis(3.3.1+)支持批量执行器返回主键,需同时开启useGeneratedKeys配置;

  3. 生产大批量插入且需要主键时,优先使用foreach批量SQL+主键配置,稳定性更强。

四、生产核心避坑要点

  1. foreach批量操作需限制单次批量数量(建议500-1000条),避免SQL语句过长导致数据库报错;

  2. 批量操作必须加事务,防止部分插入成功、部分失败的数据不一致问题;

  3. 批量更新/删除无主键返回场景,仅批量插入支持主键回写;

  4. Oracle无自增主键,需通过<selectKey>序列查询实现批量主键获取。

五、面试高分总结金句

MyBatis批量操作主要有三种实现:

原生BatchExecutor执行器性能最优,适合大批量数据;

foreach标签拼接SQL简单便捷,适配小批量场景;

MyBatis-Plus方法适合快速开发。

同时MyBatis支持批量插入返回主键列表,通过开启useGeneratedKeys配置即可自动回写主键至实体集合,是生产批量插入的标准实现方案。

极简速背版(面试口述)

MyBatis有三种批量操作方式:

一是开启BatchExecutor批量执行器,攒批统一提交、性能最优;

二是通过foreach标签拼接批量SQL,简单易用;

三是MyBatis-Plus内置批量方法快速开发。

批量插入可以返回主键列表,通过在insert标签开启useGeneratedKeys,指定主键字段,执行后主键会自动回写到实体集合中。

简洁版

  1. 批量操作方式:① BatchExecutor批量执行器(高性能大批量);② foreach标签拼接批量SQL(小批量常用);③ MyBatis-Plus saveBatch快速批量操作;

  2. 主键返回:支持批量返回自增主键,通过useGeneratedKeys=true + keyProperty配置,批量插入后自动回写所有主键ID;

  3. 限制:批量更新/删除无主键返回,大批量优先使用BatchExecutor,小批量用foreach。

21. 如何获取数据库生成的主键?(MySQL+Oracle+批量场景·满分完整版)

标准答案(完整版·面试满分)

MyBatis 支持获取数据库自动生成的主键,主流分为MySQL自增主键Oracle序列主键批量插入主键获取三种场景,核心是通过配置开启主键回填,将数据库生成的主键自动回写到入参实体对象中,无需二次查库,各场景完整实现、原理及坑点如下:

一、MySQL 自增主键获取(最常用)

核心原理:MySQL主键自增,插入数据后数据库自动生成主键,MyBatis通过开启主键回填配置,捕获自增ID并自动赋值到实体主键字段。

核心配置属性(insert标签内配置)

  • useGeneratedKeys="true":开启MyBatis主键自动获取功能,默认关闭;

  • keyProperty="id" :指定回写到实体类的主键字段名;

  • keyColumn="id":指定数据库表的主键字段名,MySQL可省略,高版本推荐显式配置。

完整XML示例

XML 复制代码
<insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
    INSERT INTO user (username, age, email)
    VALUES (#{username}, #{age}, #{email})
</insert>

调用方式:插入完成后,直接获取实体类id属性即可,无需返回值接收

java 复制代码
userMapper.insertUser(user);
// 主键已自动回填,直接获取
Long userId = user.getId();

二、Oracle 序列主键获取(非自增主键通用方案)

Oracle无自增主键,依靠序列(Sequence) 生成主键,无法使用useGeneratedKeys,需通过 <selectKey> 标签手动查询序列获取主键。

核心属性说明

  • keyProperty:绑定实体主键字段;

  • order="BEFORE":插入数据前先查询序列获取主键,再执行插入;

  • resultType:指定主键返回类型。

完整XML示例

XML 复制代码
<insert id="insertUser" parameterType="User">
    <selectKey keyProperty="id" order="BEFORE" resultType="Long">
        SELECT USER_SEQ.NEXTVAL FROM DUAL
    </selectKey>
    INSERT INTO user (id, username, age, email)
    VALUES (#{id}, #{username}, #{age}, #{email})
</insert>

三、批量插入获取所有主键(高频面试坑点)

MyBatis 支持批量插入回填全部自增主键,适配MySQL批量场景,配置与单条插入一致,作用于List集合参数。

完整批量XML配置

XML 复制代码
<insert id="batchInsert" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO user (username, age)
    VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.username}, #{item.age})
    </foreach>
</insert>

获取方式 :批量插入后,遍历原List集合,每个实体的id均已自动回填。

四、核心面试考点与生产避坑(必背)

1. 主键回填核心特点

  • 主键是回填到入参实体中,并非通过方法返回值返回;

  • 插入操作支持主键获取,更新、查询无主键回填。

2. 失效场景(高频坑点)

  • 未开启useGeneratedKeys,主键无法回填;

  • keyProperty配置错误,与实体字段名不匹配;

  • BatchExecutor批量执行器(攒批提交)低版本不支持主键回填,批量需主键优先用foreach批量SQL;

  • 手动指定主键值时,数据库不会生成主键,回填失效。

3. 不同数据库适配总结

  • MySQL、PostgreSQL自增主键:useGeneratedKeys 方案;

  • Oracle、DB2序列主键:selectKey 方案。

五、面试高分总结金句

MyBatis获取主键分两种核心方案:MySQL自增主键通过开启useGeneratedKeys,绑定实体主键字段实现自动回填;Oracle序列主键通过selectKey标签前置查询序列获取主键;批量插入沿用MySQL主键配置,遍历集合即可获取全部主键,生产中需规避批量执行器主键失效、字段配置错误等问题。

极简速背版(面试口述)

MySQL自增主键在insert标签配置useGeneratedKeys为true、指定keyProperty绑定实体主键字段,插入后自动回填主键;Oracle用selectKey标签查询序列生成主键;批量插入同样支持主键回填,遍历原实体集合即可获取所有主键,无需二次查库。

简洁版

  1. MySQL自增主键:insert标签配置 useGeneratedKeys=true + keyProperty,自动回填主键到实体;

  2. Oracle序列主键:通过 <selectKey> 标签查询序列,前置生成主键并回填;

  3. 批量插入:复用MySQL主键配置,支持批量主键回填,遍历List获取所有ID;

  4. 核心逻辑:主键回填至入参实体,非方法返回值。

22. 当实体类中的属性名和表中的字段名不一样 ,怎么办

标准答案(完整版·面试满分)

在MyBatis开发中,当Java实体类属性名与数据库表字段名不一致时,会出现无法自动映射、查询结果字段值为null的问题,MyBatis提供三种成熟的解决方案,适配不同业务场景,从全局通用、精准自定义、简易临时三个维度解决映射不匹配问题,具体详解、配置示例、优劣对比如下:

一、方案一:全局开启驼峰命名自动映射(最常用、全局生效)

核心原理 :适配行业通用命名规范,自动完成 数据库下划线字段 → Java驼峰属性 的双向映射。例如数据库字段 user_name 自动映射实体属性 userNameuser_age 映射 userAge,无需逐个配置。

全局配置(mybatis-config.xml)

XML 复制代码
<configuration>
    <settings>
        <!-- 开启驼峰命名自动映射,默认关闭 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>

SpringBootyml配置方式

XML 复制代码
mybatis:
  configuration:
    map-underscore-to-camel-case: true

优缺点与适用场景

✅ 优点:一次配置、全局生效,零侵入、无需修改Mapper XML,适配90%常规项目;

❌ 缺点:仅支持下划线转驼峰的规范命名场景,无法适配自定义不规则字段映射;

适用场景:项目统一遵循「数据库下划线、实体驼峰」命名规范的场景。

二、方案二:SQL查询字段起别名(临时简易、单SQL生效)

核心原理 :在SELECT查询语句中,给数据库原生字段设置别名,让别名与实体类属性名完全一致,MyBatis根据别名自动完成映射,无需全局配置、无需自定义映射规则。

完整XML示例

XML 复制代码
<select id="getUserById" resultType="User">
    <!-- 数据库字段user_name、user_age,实体属性userName、userAge -->
    SELECT user_name AS userName, user_age AS userAge, id
    FROM user
    WHERE id = #{id}
</select>

优缺点与适用场景

✅ 优点:简单灵活、无需全局配置、按需生效,仅针对单条SQL生效;

❌ 缺点:每个查询SQL都需手动写别名,重复冗余、维护性差;

适用场景:少量不规则字段映射、临时查询、个别特殊字段不规范的场景。

三、方案三:自定义ResultMap映射(精准万能、复杂场景首选)

核心原理 :通过<resultMap> 标签手动绑定 数据库字段名实体属性名,完全自定义映射关系,不受命名规范限制,是最灵活、功能最强的映射方案。

核心标签说明

  1. id:resultMap唯一标识,供当前XML所有SQL引用;

  2. column:数据库表的字段名;

  3. property:Java实体类的属性名。

完整可运行XML示例

XML 复制代码
<!-- 自定义字段映射关系 -->
<resultMap id="UserResultMap" type="com.example.entity.User">
    <!-- 主键字段映射 -->
    <id column="id" property="id"/>
    <!-- 普通字段不规则映射 -->
    <id column="user_name" property="userName"/>
    <id column="user_age" property="userAge"/>
    <id column="db_email" property="email"/>
</resultMap>

<!-- 引用自定义映射规则 -->
<select id="getUserById" resultMap="UserResultMap">
    SELECT id,user_name,user_age,db_email FROM user WHERE id = #{id}
</select>

优缺点与适用场景

✅ 优点:万能适配,支持不规则字段映射、字段类型转换、一对一/一对多关联映射、嵌套对象映射,精准可控;

❌ 缺点:需要手动配置,少量场景略显繁琐;

适用场景:字段命名不规范、全局映射不生效、存在关联查询、嵌套实体映射的复杂场景。

四、企业选型规范(面试高频)

  1. 项目命名规范统一:优先全局驼峰映射,高效简洁;

  2. 少量特殊字段、临时查询:使用SQL字段别名快速适配;

  3. 字段混乱、复杂关联查询、嵌套映射:必须使用ResultMap自定义映射

五、面试高分总结金句

实体属性与数据库字段不一致有三种解决方式,全局驼峰映射适配规范命名场景、简洁高效;SQL别名适配临时特殊场景;ResultMap自定义映射万能适配所有复杂场景,生产中优先全局配置,复杂业务使用ResultMap精准映射,彻底解决字段映射失效、值为null的问题。

极简速背版(面试口述)

第一种是开启全局驼峰命名自动映射,适配下划线转驼峰的规范场景,全局生效;第二种是在SQL中给字段起别名,让别名匹配实体属性名,适合临时场景;第三种是自定义ResultMap,手动绑定数据库字段和实体属性,万能适配所有复杂映射场景,是企业复杂业务的首选方案。

简洁版

  1. 全局驼峰映射:配置mapUnderscoreToCamelCase=true,自动适配下划线字段与驼峰属性,全局生效;

  2. SQL字段别名:查询语句中AS起别名,匹配实体属性名,简单临时适配;

  3. ResultMap自定义映射:手动绑定column与property,适配不规则字段、关联查询等复杂场景。

23. Mapper 编写有哪几种方式?

标准答案(完整版·面试满分)

MyBatis Mapper编写主要分为XML映射方式注解映射方式两大类,是企业开发仅有的两种标准实现方式,两种方式各有优劣、适配不同业务场景,下面从原理、优缺点、适用场景、核心限制全方位解析,附带面试高频对比考点。

一、XML映射方式(企业主流首选)

核心原理 :定义纯Java Mapper接口(无实现类),通过XML映射文件的namespace绑定Mapper接口全限定类名,XML中编写对应的增删改查SQL、动态SQL、结果映射规则,MyBatis底层通过动态代理匹配接口方法与对应SQL执行。

核心优点

  1. SQL与代码完全解耦,SQL独立维护,修改SQL无需改动Java代码,方便DBA单独优化、调优、排查慢SQL;

  2. 支持全部MyBatis特性,完美适配复杂动态SQL、多表联查、嵌套关联查询、ResultMap自定义映射、批量操作等复杂场景;

  3. SQL语句清晰直观,无代码嵌套混乱问题,可读性、可维护性极强,适合大型复杂项目。

核心缺点

  1. 需要额外维护XML文件,简单CRUD操作略显繁琐;

  2. 接口方法与XML语句需要双向匹配,新手易出现绑定错误。

适用场景:复杂业务查询、多条件动态SQL、联表查询、批量操作、生产级复杂项目,互联网企业主流开发方式。

二、注解映射方式(轻量简单场景)

核心原理 :无需编写XML映射文件,直接在Mapper接口方法上通过MyBatis内置注解绑定SQL语句,常用核心注解:@Select@Insert@Update@Delete,启动时直接扫描注解生成MappedStatement。

完整代码示例

java 复制代码
@Mapper
public interface UserMapper {
    // 注解直接绑定查询SQL
    @Select("select id,username,age from user where id = #{id}")
    User getUserById(@Param("id") Long id);
}

核心优点

  1. 无需创建XML文件,开发简洁高效,代码高度聚合;

  2. 简单CRUD开发速度快,减少文件冗余,轻量化无多余配置。

核心缺点

  1. 不支持复杂动态SQL,原生注解无法直接使用if、where、foreach等动态标签,复杂条件拼接极其繁琐;

  2. SQL硬编码在Java代码中,与业务代码耦合,SQL优化、修改需要改动Java代码、重新编译;

  3. 不支持复杂ResultMap嵌套映射、多表复杂关联查询,功能局限性大。

适用场景:简单单表CRUD、无动态条件、业务简单的后台管理项目、快速迭代小型项目。

三、两种方式核心对比(面试高频考点)

  1. 解耦性:XML方式SQL与代码解耦;注解方式SQL与代码耦合;

  2. 动态SQL支持:XML完美支持所有动态标签;注解原生不支持复杂动态SQL;

  3. 维护性:XML适合长期维护、复杂业务;注解适合简单临时业务;

  4. 企业选型:复杂生产项目统一用XML,简单CRUD可酌情用注解。

四、重要开发规范(避坑要点)

  1. 同一个Mapper接口不建议混合使用XML和注解,容易出现SQL绑定冲突、覆盖问题;

  2. 互联网高并发、复杂业务项目,优先强制使用XML方式

  3. 注解方式仅用于极简单表操作,禁止用于复杂联表、动态查询、批量操作场景。

五、面试高分总结金句

MyBatis Mapper有XML映射和注解编写两种方式,XML方式解耦性强、支持所有复杂SQL与动态场景、可维护性高,是企业复杂项目首选;注解方式简洁高效、适合简单CRUD,但不支持复杂动态SQL、代码耦合度高,生产中需根据业务复杂度合理选型。

极简速背版(面试口述)

Mapper编写分为XML和注解两种方式。XML方式将SQL剥离到XML文件,解耦性好、支持动态SQL和复杂联表查询,适配所有生产复杂业务;注解方式通过@Select等注解直接写SQL,开发简单,但不支持复杂动态SQL,仅适合简单单表CRUD场景。

简洁版

  1. XML映射方式:SQL独立配置、解耦性强、支持动态SQL与复杂关联查询,企业复杂项目主流选择;

  2. 注解方式:基于@Select/@Insert等注解编码,开发轻量化,仅适用于简单单表CRUD,不支持复杂动态SQL。

24. MyBatis接口绑定的几种方式(满分完整版)

标准答案(完整版·面试满分)

MyBatis Mapper接口绑定,核心目的是将Java Mapper接口与XML/注解SQL语句进行关联绑定 ,让框架可以通过动态代理调用接口方法执行对应SQL。企业开发共有四种标准绑定方式,适配原生MyBatis、Spring、SpringBoot不同环境,底层原理、配置方式、适用场景各不相同,完整详解如下:

一、XML命名空间绑定(原生核心方式·底层基础)

核心原理 :MyBatis最基础的绑定方式,通过Mapper XML文件的 namespace属性 绑定Mapper接口的全限定类名,同时XML内标签id与接口方法名一一对应,实现接口方法与SQL语句的精准绑定。

核心配置规范

  1. XML文件头部namespace值必须等于Mapper接口完整包名+类名;

  2. select/insert/update/delete标签id必须与接口抽象方法名完全一致;

  3. 参数类型、返回值类型与接口方法参数、返回值匹配。

示例配置

XML 复制代码
<mapper namespace="com.example.mapper.UserMapper">
    <select id="getUserById" resultType="User">
        SELECT * FROM user WHERE id = #{id}
    </select>
</mapper>

适用场景:所有XML模式开发项目,是其余所有绑定方式的底层基础,必遵守的核心规范。

二、@Mapper注解绑定(SpringBoot单文件绑定)

核心原理 :在单个Mapper接口上添加 @Mapper 注解,项目启动时MyBatis自动扫描该接口,结合XML namespace或注解SQL,完成接口绑定与代理对象生成。

代码示例

java 复制代码
@Mapper
public interface UserMapper {
    User getUserById(@Param("id") Long id);
}

优缺点

✅ 优点:精准绑定单个Mapper,按需生效、配置简单、无包扫描冲突;

❌ 缺点:Mapper数量多时,每个接口都需手动加注解,冗余繁琐。

适用场景:项目Mapper数量少、需要单独指定绑定、分包不规则的场景。

三、@MapperScan批量扫描绑定(SpringBoot企业主流)

核心原理 :在启动类/配置类上添加 @MapperScan 注解,指定Mapper接口所在包路径,框架自动批量扫描该包下所有Mapper接口,统一完成接口绑定、自动生成代理对象、注入Spring容器,无需逐个加@Mapper注解。

核心配置示例

java 复制代码
@SpringBootApplication
@MapperScan("com.example.mapper") // 批量扫描Mapper包
public class MybatisApplication {
}

高级特性:支持多包扫描、指定注解扫描、指定父类扫描,适配多模块项目。

优缺点

✅ 优点:一次配置、全局生效,批量绑定所有Mapper,简洁高效,企业项目首选;

❌ 缺点:需保证Mapper包路径规整,路径配置错误会导致绑定失效。

适用场景:绝大多数SpringBoot互联网项目、Mapper分包规整的企业级项目。

四、MapperScannerConfigurer手动配置绑定(Spring原生传统方式)

核心原理 :Spring原生传统绑定方式,通过手动注册 MapperScannerConfigurer 配置Bean,指定Mapper扫描路径、SqlSessionFactory,手动触发Mapper接口扫描与绑定,无注解依赖。

核心配置示例(Spring配置类)

java 复制代码
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
    MapperScannerConfigurer scanner = new MapperScannerConfigurer();
    // 指定Mapper接口扫描包
    scanner.setBasePackage("com.example.mapper");
    // 绑定全局SqlSessionFactory
    scanner.setSqlSessionFactoryBeanName("sqlSessionFactory");
    return scanner;
}

适用场景:SSM传统整合项目、无SpringBoot注解驱动环境、需要手动精细化控制扫描逻辑的场景。

五、四种绑定方式核心总结与面试考点

  1. 底层基础 :所有绑定方式都依赖 namespace+方法名 的核心绑定规则,是MyBatis接口绑定的底层依据;

  2. 现代首选 :SpringBoot项目统一使用 @MapperScan批量扫描,高效规范;

  3. 小众补充:单个特殊Mapper用@Mapper注解,传统SSM项目用MapperScannerConfigurer;

  4. 核心禁忌:禁止混用多种绑定方式,避免扫描冲突、接口绑定异常。

面试高分金句

MyBatis接口绑定共有四种方式:XML namespace是底层核心绑定规则,@Mapper注解实现单个接口绑定,@MapperScan实现SpringBoot批量自动绑定(企业主流),MapperScannerConfigurer适配传统SSM手动配置绑定,所有方式最终都通过动态代理完成接口与SQL的关联调用。

极简速背版

MyBatis接口绑定四种方式:一是XML命名空间绑定,是底层核心规则;二是@Mapper注解单接口绑定;三是@MapperScan批量扫描绑定,SpringBoot项目首选;四是MapperScannerConfigurer手动Bean配置绑定,适配传统SSM项目。

简洁版

  1. XML namespace绑定:底层核心,通过命名空间绑定接口全类名,方法名匹配SQL标签id;

  2. @Mapper注解:单个接口精准绑定,适配少量特殊Mapper;

  3. @MapperScan批量扫描:SpringBoot主流,批量绑定所有Mapper接口;

  4. MapperScannerConfigurer:传统SSM手动配置,实现接口扫描绑定。

25. 使用MyBatis的mapper接口调用时有哪些要求?

标准答案(完整版·面试满分)

在MyBatis中通过Mapper接口代理方式调用数据库方法,是企业主流开发方式,想要正常完成接口绑定与SQL执行,必须满足四大核心硬性规范+多项细节要求,任意一点不满足都会导致接口绑定失败、运行报错,完整要求与底层原理、报错坑点如下:

一、核心硬性四大要求(必背核心)

1. 命名空间精准匹配

Mapper XML文件中的namespace属性 必须和对应Mapper接口的**全限定类名(包名+类名)**完全一致,这是接口与SQL绑定的核心依据,命名空间错误会直接导致找不到对应SQL语句,抛出绑定异常。

2. 方法标识完全统一

XML中select、insert、update、delete标签的 id属性 ,必须与Mapper接口中的抽象方法名完全一致,MyBatis通过「namespace+方法名」唯一锁定一条SQL语句,方法名不匹配会触发SQL找不到异常。

3. 参数与返回值严格匹配

Mapper接口方法的入参类型、返回值类型,必须和XML映射文件中配置的parameterType、resultType/resultMap适配:

  1. 入参不匹配会导致参数绑定失败、SQL执行参数异常;

  2. 返回值不匹配会出现结果集封装失败、类型转换异常、数据返回null等问题。

4. Mapper可被项目正常扫描注册

项目启动时必须能扫描到Mapper接口与对应的XML映射文件,保证动态代理对象正常生成并注入Spring容器:

  1. SpringBoot项目需配置@MapperScan包扫描 或 在Mapper接口上加@Mapper注解;

  2. XML文件需放置在可被加载的路径下,保证配置文件能解析到所有映射规则。

二、进阶补充约束要求(面试加分+生产避坑)

1. Mapper接口禁止方法重载

MyBatis绑定SQL的唯一标识是namespace+方法名,不区分方法参数列表,接口重载会导致同名方法冲突,无法精准匹配唯一SQL,运行直接报错,生产规范强制禁止重载。

2. 接口方法参数规范绑定

多参数场景下,必须通过@Param注解显式绑定参数名,否则MyBatis无法识别参数对应关系,导致参数赋值失败、SQL执行异常,禁止直接使用原生下标取值。

3. 动态SQL与映射规则合规

XML中动态SQL标签、resultMap映射、sql片段引用等配置需语法正确,无语法报错、标签嵌套错误,否则启动解析失败,导致Mapper初始化异常。

4. 避免XML与注解混用冲突

同一个Mapper接口,不建议同时使用XML映射和注解SQL(@Select/@Insert等),会出现SQL覆盖、绑定冲突,导致执行SQL错乱、业务异常。

三、常见报错场景汇总

  1. namespace配置错误、id与方法名不匹配:BindingException绑定异常,找不到MappedStatement;

  2. 未配置扫描注解:Mapper接口无法生成代理对象,注入Spring失败,报空指针异常;

  3. 参数/返回值不匹配:参数绑定失败、结果集封装异常、数据返回null;

  4. 接口方法重载:同名SQL冲突,运行时无法匹配执行语句。

四、面试高分总结金句

MyBatis Mapper接口调用核心遵循命名空间绑定、方法名匹配、参数返回值适配、文件可扫描四大核心规范,同时禁止方法重载、规范参数绑定、避免注解XML混用,保证接口动态代理正常生成、SQL精准匹配执行,是项目稳定运行的基础规范。

极简速背版(面试口述)

Mapper接口调用有四大核心要求:第一,XML命名空间必须匹配接口全限定类名;第二,XML标签id必须和接口方法名一致;第三,方法入参、返回值要和XML配置适配;第四,接口和XML文件必须能被项目扫描加载。同时接口禁止方法重载,多参数需用@Param注解绑定,避免绑定冲突和执行异常。

简洁版

  1. XML的namespace与Mapper接口全限定类名完全一致;

  2. XML增删改查标签id与接口方法名一一对应;

  3. 接口参数、返回值类型与XML映射配置匹配;

  4. 项目可正常扫描加载Mapper接口与XML文件;

  5. Mapper接口禁止方法重载,多参数需@Param注解绑定;

  6. 同一接口禁止XML与注解SQL混用。

26. 通常一个mapper.XML对应一个DAO接口,DAO是否可以重载?

标准答案(完整版·面试满分)

结论:Mapper接口完全不支持方法重载,生产环境严禁重载。

一、核心底层原理

MyBatis 在加载解析映射文件时,会以 namespace(Mapper接口全类名) + 方法名(XML标签id) 作为 MappedStatement 的唯一全局标识,用于绑定接口方法与SQL语句。

MyBatis 的绑定规则不区分方法参数列表、参数类型、返回值,仅依靠方法名做唯一匹配。如果接口存在重载方法,多个重载方法会对应同一个 namespace+方法名 标识,导致 MyBatis 无法精准匹配对应的 SQL 语句,启动或运行阶段直接报错。

二、重载报错现象

  1. 项目启动不报错(仅加载XML映射,不校验方法签名);

  2. 调用重载方法时触发 绑定异常、SQL匹配冲突、参数注入异常,大概率出现参数不匹配、执行SQL错乱、空指针等隐性BUG。

三、生产规范与解决方案

  1. 强制规范:所有 Mapper 接口方法名必须唯一,禁止方法重载;

  2. 替代方案:不同业务、不同参数的数据库操作,定义不同方法名,如 getUserById()、getUserByUsername()、listUserByCondition(),通过方法名区分业务逻辑;

  3. 多条件动态查询优先使用 动态SQL标签(if/where) 实现参数自适应,无需定义多个重载方法。

四、面试高分总结金句

Mapper接口不能重载,核心原因是MyBatis以namespace+方法名作为SQL唯一标识,不识别方法参数签名,重载会导致SQL绑定冲突、执行错乱;生产中统一通过唯一方法名+动态SQL实现多场景查询,杜绝方法重载。

极简速背版(面试口述)

Mapper接口不支持重载。因为MyBatis依靠namespace加方法名唯一绑定SQL,不区分参数列表,重载会造成方法与SQL匹配冲突、运行报错。开发中需保证Mapper方法名唯一,通过动态SQL适配多参数场景,不使用重载。

简洁版

  1. 结论:Mapper接口禁止、不支持方法重载

  2. 原因:SQL绑定唯一键为namespace+方法名,不区分参数,重载会导致绑定冲突;

  3. 规范:所有Mapper方法名唯一,通过动态SQL适配多条件查询,替代重载。

27. MyBatis不同映射文件中的id是否可以重复?(满分完整版)

标准答案(完整版·面试满分)

结论:不同Mapper映射XML文件中的SQL id完全可以重复,不会发生冲突报错,可正常使用。

一、核心底层原理(必背)

MyBatis 在内存中存储所有SQL语句的唯一标识并非单纯的标签id,而是 namespace + id 组合全局唯一键。

  1. 每个Mapper XML文件都有独立的namespace命名空间,对应唯一的Mapper接口全限定类名;

  2. 不同XML文件的namespace值天然不同,即便内部SQL标签id完全一致,拼接后的全局唯一键也不重复;

  3. MyBatis加载解析所有映射文件时,会根据完整唯一键缓存MappedStatement,精准区分不同文件的同名SQL,不会覆盖、不会冲突。

二、场景示例(直观理解)

  1. UserMapper.xml:namespace=com.example.mapper.UserMapper,包含id=listAll、getById的SQL语句;

  2. OrderMapper.xml:namespace=com.example.mapper.OrderMapper,同样可以定义id=listAll、getById的SQL语句;

  3. 两个文件id重复,但完整标识分别为: com.example.mapper.UserMapper.listAll com.example.mapper.OrderMapper.listAll 彼此独立、互不干扰,可正常调用执行。

三、核心禁忌(面试高频坑点)

  1. 同一个XML映射文件内,id必须唯一:同一namespace下,id重复会导致SQL覆盖、启动报错、绑定异常;

  2. 跨文件、跨namespace允许id重复,这是MyBatis命名空间隔离的核心设计目的。

四、延伸对比考点

  1. 同namespace:id不可重复,唯一键冲突,启动报错;

  2. 不同namespace:id可随意重复,命名空间隔离,无任何冲突;

  3. Mapper接口方法名同理:不同Mapper接口可存在同名方法,同一接口方法名必须唯一、禁止重载。

五、面试高分总结金句

MyBatis的SQL全局唯一标识是namespace加id的组合,依托命名空间隔离机制,不同映射文件的SQL id可以重复且互不冲突;但同一映射文件内id必须唯一,否则会出现SQL覆盖、绑定异常,这是MyBatis映射绑定的核心规则。

极简速背版(面试口述)

不同映射文件的id可以重复,因为MyBatis以namespace加id作为SQL唯一标识,不同XML的命名空间不同,天然隔离,不会冲突;但同一个XML文件内id必须唯一,否则会启动报错、SQL覆盖。

简洁版

  1. 跨文件:id可以重复,namespace命名空间隔离,全局唯一键不冲突;

  2. 同文件:id不可重复,会造成MappedStatement覆盖、绑定异常;

  3. 核心规则:MyBatis SQL唯一标识为 namespace + id 组合。

28. 简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系?

标准答案(完整版·面试满分)

MyBatis项目启动时,会通过核心解析器逐行解析所有Mapper Xml映射文件,将XML中所有配置、SQL语句、映射规则、动态节点、缓存配置等静态文本配置 ,统一解析转换为框架内存中的Java数据结构对象,常驻内存,供后续数据库请求直接复用,无需重复解析。所有XML标签均有对应的内部数据结构,完整映射关系与底层逻辑如下:

一、全局核心映射关系(顶层结构)

整个Mapper Xml文件的所有配置,最终会被封装为MappedStatement 集合,统一存入全局单例 Configuration 对象中常驻内存,是所有映射关系的容器载体。

二、XML标签与内部数据结构一一对应(核心考点)

  1. mapper 根标签(namespace)

XML中mapper的namespace属性,对应内部 MappedStatement 的命名空间标识,用于区分不同Mapper的SQL语句,是接口绑定的核心依据。

  1. select/insert/update/delete 语句标签

每一个增删改查标签,单独对应内存中一个独立的 MappedStatement 对象。该对象完整封装:SQL语句id、执行SQL文本、参数类型parameterType、返回值类型resultType/resultMap、超时时间、缓存策略、动态SQL节点、批量配置等所有执行元数据。

  1. resultMap 自定义映射标签

XML中每一个resultMap标签,对应内部 ResultMap 数据结构,内部封装主键映射(id标签)、普通字段映射(result标签)、一对一/一对多关联映射、嵌套对象映射规则,用于运行时自动封装结果集。

  1. sql / include 通用片段标签

XML中<sql>定义的通用SQL片段,会被解析为 SqlNode 节点对象,存入内存片段缓存;通过<include>引用时,框架直接复用内存中已解析的SqlNode,无需重复解析。

  1. 动态SQL标签(if/where/set/foreach/choose)

所有动态SQL标签,启动时会被解析为树形 SqlNode 数据结构(IfSqlNode、WhereSqlNode、ForEachSqlNode等),运行时通过OGNL表达式动态判断入参,自动拼接、裁剪生成完整可执行SQL。

  1. selectKey 主键标签

对应内部 SelectKey 数据结构,封装主键查询SQL、执行顺序(BEFORE/AFTER)、主键字段映射规则,用于实现数据库主键回填。

  1. parameter参数配置、jdbcType、typeHandler

对应内部 ParameterMapping 数据结构,封装参数名称、Java类型、数据库类型、自定义类型处理器,实现参数自动绑定与类型转换。

  1. 缓存相关标签(cache、cache-ref)

对应内部 Cache 缓存对象,记录当前Mapper命名空间的缓存开启状态、缓存规则、引用缓存配置,管控二级缓存的读写与失效逻辑。

三、底层加载执行流程

  1. 项目启动:SqlSessionFactoryBuilder解析所有Mapper Xml文件,将XML文本配置解析为各类内存数据结构;

  2. 全局缓存:所有MappedStatement、ResultMap、SqlNode等对象统一存入Configuration全局容器;

  3. 运行调用:业务请求时,直接从内存匹配对应的MappedStatement,复用预解析好的SQL与映射规则,高效执行数据库操作。

四、面试高分总结金句

MyBatis Xml映射文件与内部数据结构的核心关系是文本配置转内存对象:XML为静态配置载体,启动时被解析为MappedStatement、ResultMap、SqlNode等核心内存数据结构,统一托管在Configuration全局容器中,实现SQL预加载、映射规则复用,大幅提升运行效率。

极简速背版(面试口述)

MyBatis启动会解析XML映射文件,将所有配置转为内存数据结构:每条增删改查语句对应一个MappedStatement对象,resultMap映射为ResultMap结构,动态SQL标签解析为SqlNode树形节点,SQL片段缓存为独立SqlNode,所有结构统一存入Configuration全局容器,运行时直接复用,无需重复解析。

简洁版

  1. 核心载体:所有XML配置最终封装存入Configuration全局对象;

  2. 增删改查标签 → MappedStatement(存储SQL、参数、返回值、执行配置);

  3. resultMap标签 → ResultMap(存储字段与实体映射、关联映射规则);

  4. 动态SQL标签 → 各类SqlNode(树形结构,支持运行时动态拼接SQL);

  5. sql片段、主键、缓存等标签,均对应独立内存数据结构,全局复用。

29. Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?

标准答案(完整版·面试满分)

MyBatis通过结果集处理器ResultSetHandler + 反射机制 + 映射规则匹配,自动将数据库JDBC结果集(ResultSet)封装为Java目标对象,全程无需手动遍历、赋值,底层自动化完成数据映射与对象封装,具体执行流程、核心原理与两大映射形式详解如下:

一、结果封装完整底层流程

  1. 获取原始结果集:StatementHandler执行完SQL后,获取数据库返回的ResultSet原始结果集,包含所有查询字段与数据;

  2. 加载预定义映射规则:MyBatis根据SQL配置的resultType或resultMap,加载预解析好的字段与实体属性映射规则、类型转换规则;

  3. 遍历结果集、逐行封装:ResultSetHandler循环遍历ResultSet每一行数据,通过反射机制实例化目标Java对象;

  4. 字段匹配与赋值:根据映射规则,将数据库字段值与实体属性一一匹配,结合TypeHandler完成数据库类型与Java类型的自动转换,通过反射给对象属性赋值;

  5. 封装返回结果:单行数据封装为单个实体对象,多行数据自动封装为List集合、Map等目标结构,最终返回给业务层,同时自动关闭ResultSet资源。

二、两大核心映射形式(企业高频考点)

1. resultType 自动映射(简易自动映射)

核心原理 :基于默认命名匹配规则自动映射,无需手动配置字段关联。依托MyBatis驼峰命名自动映射配置,自动实现数据库下划线字段与Java驼峰属性的匹配赋值。

生效规则

① 开启mapUnderscoreToCamelCase=true时,数据库user_name自动映射实体userName;

② 数据库字段名与实体属性名完全一致时,无需配置可直接映射;

③ 底层自动匹配字段、完成类型转换、反射赋值。

优缺点与适用场景

✅ 优点:零配置、简洁高效、开发速度快;

❌ 缺点:仅支持规范命名字段映射,不支持不规则字段、嵌套关联、复杂类型转换;

适用场景:单表简单查询、字段命名规范、无嵌套关联的基础CRUD场景。

2. resultMap 自定义映射(万能精准映射)

核心原理 :通过手动定义resultMap标签,显式绑定数据库column字段Java实体property属性,自定义映射关系、类型转换、主键规则、嵌套关联,完全不受命名规范限制。

核心能力

① 适配数据库字段与实体属性命名不一致的不规则场景;

② 支持主键单独标识、自定义TypeHandler类型转换;

③ 支持一对一、一对多嵌套关联查询、嵌套对象映射;

④ 支持字段筛选、特殊字段脱敏、默认值填充等个性化配置。

优缺点与适用场景

✅ 优点:功能全面、映射精准、适配所有复杂业务场景,无映射死角;

❌ 缺点:需要手动配置,简单场景略显繁琐;

适用场景:字段命名不规范、多表联查、嵌套关联映射、自定义类型转换、复杂业务查询场景。

三、两种映射形式核心区别

  1. 配置方式:resultType零配置自动映射,resultMap手动自定义映射;

  2. 适配能力:resultType仅适配规范单表字段,resultMap支持所有复杂映射场景;

  3. 扩展性:resultType无扩展能力,resultMap支持关联查询、类型转换、高级映射;

  4. 企业选型:简单CRUD用resultType,复杂业务、关联查询统一用resultMap。

四、面试高分总结金句

MyBatis通过ResultSetHandler遍历结果集,结合反射与预定义映射规则自动完成字段匹配、类型转换、对象赋值,实现结果集封装;主要分为resultType自动映射和resultMap自定义映射两种形式,前者简洁高效适配规范简单场景,后者功能强大适配复杂关联、不规则字段映射场景,是项目开发的核心映射方案。

极简速背版(面试口述)

MyBatis依靠ResultSetHandler处理器,遍历数据库ResultSet结果集,通过反射机制结合映射规则自动给实体属性赋值,完成结果封装。映射形式分为两种,一是resultType自动映射,适配字段命名规范的简单单表查询;二是resultMap自定义映射,可手动绑定字段与属性,支持不规则字段、多表关联、嵌套映射等复杂场景。

简洁版

  1. 封装原理:通过ResultSetHandler处理结果集,结合反射机制、类型转换器、映射规则,自动遍历字段、赋值封装为Java对象;

  2. resultType自动映射:零配置,依托驼峰命名规则自动映射,适配简单规范单表查询;

  3. resultMap自定义映射:手动绑定字段与实体属性,支持不规则字段、一对一/一对多关联、嵌套映射、自定义类型转换,适配复杂业务场景。

30. Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?

标准答案(完整版·面试满分)

MyBatis XML映射文件中,除核心的增删改查(select/insert/update/delete)标签外,还包含SQL复用标签、动态SQL标签、结果映射标签、功能增强标签、缓存标签、主键生成标签六大类拓展标签,覆盖SQL复用、动态拼接、字段映射、主键回填、缓存管控等全场景能力,所有拓展标签完整解析及作用如下:

一、SQL复用标签(提升代码复用、减少冗余)

  1. <sql>:定义通用SQL代码片段,可封装通用查询字段、查询条件、联表语句等,实现SQL复用;

  2. <include>:引用<sql>定义的通用片段,直接嵌入当前SQL语句,大幅减少重复SQL编写,统一维护通用逻辑。

二、动态SQL核心标签(适配多条件动态查询/更新,面试高频)

  1. <if>:条件判断标签,根据入参是否为空、是否满足条件,动态拼接SQL片段,适配非必传参数查询;

  2. <where> :动态生成WHERE关键字,自动剔除多余的and/or前缀,规避SQL语法错误;

  3. <set>:动态生成SET关键字,用于动态更新语句,自动剔除末尾多余逗号,适配选择性字段更新;

  4. <foreach>:循环遍历标签,支持集合、数组参数遍历,适配批量新增、批量删除、in多值查询场景;

  5. <choose>、<when>、<otherwise>:多路条件判断,类似Java的switch-case逻辑,多条件互斥场景优先使用;

  6. <trim>:自定义字符串截取、拼接规则,可灵活替换where/set标签功能,自定义剔除前后缀多余字符,适配特殊动态SQL场景。

三、结果映射标签(复杂字段、关联查询核心)

  1. <resultMap>:自定义字段与实体映射规则,解决数据库字段与Java属性命名不一致、类型不匹配问题,同时支持一对一、一对多嵌套关联映射,是复杂查询必备标签;

  2. <id>:resultMap内部子标签,标识数据库主键字段,优化MyBatis缓存、关联查询匹配逻辑;

  3. <result>:resultMap内部子标签,映射普通数据库字段与实体属性;

  4. <association>:一对一关联映射标签,适配单对象嵌套关联查询;

  5. <collection>:一对多关联映射标签,适配集合类型嵌套关联查询。

四、主键生成标签

<selectKey>:主键回填标签,适配数据库自增、序列生成主键场景,可在插入数据前后执行主键查询,实现新增后自动获取主键值,兼容MySQL、Oracle等多数据库主键逻辑。

五、功能增强标签

<bind>:参数绑定标签,可自定义变量、拼接参数,常用于模糊查询,解决不同数据库模糊查询语法差异,提升SQL兼容性。

六、缓存配置标签

  1. <cache>:开启当前Mapper命名空间的二级缓存,配置缓存过期、回收策略;

  2. <cache-ref>:引用其他Mapper的二级缓存空间,实现多Mapper共享同一缓存,统一缓存管控。

面试高分总结金句

MyBatis XML拓展标签主要分为六大类:SQL复用标签实现代码精简,动态SQL标签适配复杂多变的业务条件,resultMap系列标签解决复杂字段与关联映射,selectKey实现主键回填,bind适配参数兼容,cache系列管控二级缓存,全方位弥补基础增删改查标签的能力短板,适配各类复杂生产场景。

极简速背版(面试口述)

除基础增删改查标签外,MyBatis XML还有六大类拓展标签:一是sql、include复用标签精简SQL;二是if、where、set、foreach等动态SQL标签实现条件拼接;三是resultMap及关联标签处理复杂字段和嵌套查询;四是selectKey实现主键回填;五是bind标签适配参数兼容;六是cache缓存标签管控二级缓存。

简洁版

  1. SQL复用:<sql>、<include>;

  2. 动态SQL:<if>、<where>、<set>、<foreach>、<choose>、<when>、<trim>;

  3. 结果关联映射:<resultMap>、<id>、<result>、<association>、<collection>;

  4. 主键处理:<selectKey>;

  5. 参数增强:<bind>;

  6. 缓存配置:<cache>、<cache-ref>。

31. MyBatis映射文件中A标签引用B标签,如果B标签在A的后面定义,可以吗?

标准答案(完整版·面试满分)

结论:完全可以,不会报错,可正常生效。

一、核心底层加载原理(面试必背)

MyBatis解析Mapper XML映射文件采用先全量加载缓存、后解析引用的机制,并非从上到下逐行解析、逐行生效,具体流程如下:

  1. 项目启动阶段,MyBatis的XMLLoader会一次性加载当前XML文件的所有节点内容,全局扫描并解析所有<sql>通用片段标签,无论标签书写的前后顺序;

  2. 将所有解析完成的SQL片段,以id为key、SqlNode节点为value,统一缓存到内存的全局片段容器中;

  3. 全部节点加载完毕后,再统一处理所有<include>引用逻辑,根据id从内存缓存中匹配对应的SQL片段完成拼接。

因此,即便被引用的B标签(sql片段)写在引用方A标签的后方,只要在同一个XML文件内,启动时会被统一加载缓存,引用完全生效,不存在顺序限制。

二、场景示例(直观验证)

下述写法完全合法、正常运行,后置定义的sql片段可被前方正常引用:

XML 复制代码
<!-- A标签:前方引用后置定义的B片段 -->
<select id="getUserList" resultType="User">
    SELECT <include refid="user_common_field"/> FROM user
</select>

<!-- B标签:后置定义的通用SQL片段 -->
<sql id="user_common_field">
    id,username,age,create_time,update_time
</sql>

三、核心限制与注意事项(面试坑点)

  1. 仅限同文件无顺序限制 :该规则仅适用于同一个Mapper XML文件内的片段引用;

  2. 跨文件引用无顺序概念:通过cache-ref或跨文件include引用时,全局所有XML片段统一加载缓存,同样无顺序限制;

  3. id必须全局唯一:同一XML内sql片段id不可重复,否则会出现覆盖报错,与定义顺序无关。

四、面试高分总结金句

MyBatis解析XML文件遵循先全量加载缓存、后统一解析引用的机制,同文件中SQL片段的定义位置无前后顺序要求,前方标签可正常引用后方定义的SQL片段,无需遵循"先定义、后引用"的编码顺序。

极简速背版(面试口述)

可以。因为MyBatis启动会一次性加载当前XML所有SQL片段并缓存到内存,全部加载完成后才会处理include引用逻辑,不受代码前后顺序限制,后置定义的标签可以被前方标签正常引用。

简洁版

  1. 结论:同XML文件中,前方标签可引用后置定义的SQL片段,完全合法;

  2. 原理:MyBatis先全量加载所有sql片段并缓存,再统一解析引用,无书写顺序限制;

  3. 规范:只需保证片段id唯一,无需遵循先定义后引用的顺序。

标准答案:可以。

MyBatis启动时会一次性加载当前XML所有节点,全局缓存所有sql片段,解析不分前后顺序,引用不受定义位置影响。

32. MyBatis 能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别

标准答案(完整版·面试满分)

MyBatis 完全支持一对一、一对多、多对一、多对多 的数据库关联查询,核心依托 resultMap 嵌套映射 实现,主要分为 嵌套结果查询(联表查询)嵌套查询(子查询) 两种实现方式,二者底层原理、性能特性、适用场景差异极大,是面试高频考点,完整解析如下:

一、方式一:嵌套结果查询(联表查询·企业推荐)

核心原理 :通过单条SQL联表查询(JOIN),一次性查询主表+关联表的所有数据,数据库返回整合后的结果集,MyBatis 依托 resultMap 的 association(一对一)、collection(一对多)标签,自动解析、去重、封装嵌套关联数据,全程仅执行一次SQL。

核心实现

  1. SQL层面:使用 left join / right join / inner join 关联多张表,一次性查询所有关联字段;

  2. 映射层面:通过 resultMap 自定义关联规则,association 映射一对一对象,collection 映射一对多集合;

  3. 底层自动处理数据去重、嵌套封装,无需手动处理数据整合。

核心优点

  1. 仅执行一次SQL,无N+1查询性能问题,数据库IO开销极低;

  2. 数据一致性高,单次事务查询,无数据延迟、数据不一致问题;

  3. 适配大数据量、高并发关联查询场景,生产环境首选。

核心缺点

  1. 联表查询字段多、数据量大时,结果集传输体积更大;

  2. 复杂多表联表SQL可读性、维护性略低;

  3. 无法实现延迟加载,无论是否需要关联数据,都会一次性查询封装所有数据。

适用场景:绝大多数企业级关联查询、大数据量查询、高并发场景、需要保证数据一致性的业务。

二、方式二:嵌套查询(子查询·分步查询)

核心原理 :分为两步独立SQL查询,先查询主表数据,再根据主表结果批量/循环查询关联表数据,通过 resultMap 绑定子查询方法,完成嵌套数据封装。

核心实现

  1. 第一步:执行主表查询SQL,查询出所有主数据列表;

  2. 第二步:通过子查询SQL,根据主表主键批量查询关联数据;

  3. MyBatis 自动将关联数据封装到主表实体的嵌套对象/集合中。

核心优点

  1. SQL拆分清晰、简单易懂,单条SQL逻辑单一,维护性极高;

  2. 支持延迟加载(懒加载):开启懒加载后,仅当代码调用关联属性时,才会执行子查询,无需关联数据时可节省查询开销;

  3. 可复用独立的子查询方法,代码复用性强。

核心缺点

  1. 极易触发N+1查询性能问题:查询N条主数据,会额外执行N次子查询,总执行SQL数为 1+N,数据库IO暴涨;

  2. 多次数据库交互,高并发、大数据量场景性能极差;

  3. 多次查询属于不同数据库交互,存在短暂数据不一致风险。

适用场景:数据量小、低并发场景、需要开启延迟加载、复用子查询、拆分复杂SQL的业务场景。

三、两种方式核心区别(面试必背对比)

  1. SQL执行次数不同:嵌套结果一次联表查询,嵌套查询分步多次查询;

  2. 性能差异:嵌套结果无N+1问题、性能优异;嵌套查询易产生N+1性能隐患;

  3. 延迟加载支持:嵌套结果不支持懒加载;嵌套查询原生支持延迟加载;

  4. SQL复杂度:嵌套结果单SQL复杂、联表逻辑多;嵌套查询SQL简洁、拆分清晰;

  5. 数据一致性:嵌套结果单次查询,数据一致性高;嵌套查询多次查询,存在数据延迟偏差;

  6. 生产选型:优先嵌套结果联表查询,仅需懒加载、SQL拆分场景使用嵌套查询。

四、面试高分总结金句

MyBatis关联查询分为嵌套结果联表查询和嵌套子查询两种方式,嵌套结果一次JOIN查询、无N+1问题、性能更强,是互联网项目主流选型;嵌套子查询拆分SQL、支持懒加载,但存在N+1性能隐患,仅适用于小数据量、需要延迟加载的场景,开发中需根据业务数据量、并发量合理选型。

极简速背版(面试口述)

MyBatis支持一对一、一对多关联查询,有两种实现方式。第一种嵌套结果联表查询,通过JOIN单SQL一次性查完所有数据,无N+1问题、性能好,不支持懒加载,生产首选;第二种嵌套子查询,先查主表再查关联表,SQL简洁、支持延迟加载,但存在N+1性能问题,仅适合小数据量场景。

简洁版

  1. 嵌套结果(联表查询):单条JOIN SQL一次性查询全部关联数据,无N+1问题、性能高、数据一致,不支持懒加载,企业主流推荐;

  2. 嵌套查询(子查询):分步查询主表与关联表,SQL简洁、支持延迟加载,存在N+1性能隐患,仅适配低并发、小数据量场景。

33. Mybatis是否可以映射Enum枚举类?有哪些映射方式?底层原理与生产坑点?

标准答案(完整版·面试满分)

结论:MyBatis 完全支持Java Enum枚举类与数据库字段的双向映射,内置默认枚举处理器,同时支持自定义处理器适配复杂业务场景,无需手动转换枚举与数据库值,自动完成双向映射赋值。

一、MyBatis内置两种默认枚举映射方式(原生无需自定义)

1. EnumTypeHandler(默认处理器:映射枚举名称)

底层规则 :默认全局生效,将枚举的**名称(name())**与数据库字符串字段双向映射。

映射逻辑

存入数据库:枚举对象 → 枚举name字符串(如 StatusEnum.SUCCESS → "SUCCESS");

查询回显:数据库字符串 → 匹配对应名称的枚举对象。

优缺点:使用简单、零配置;但数据库存储字符串,占用空间大、查询效率低,无法适配数字枚举场景。

2. EnumOrdinalTypeHandler(映射枚举下标)

底层规则 :将枚举的**ordinal序号(枚举定义下标,从0开始)**与数据库数字字段双向映射。

映射逻辑

存入数据库:枚举对象 → 枚举ordinal数字(如第一个枚举 → 0,第二个 → 1);

查询回显:数据库数字 → 匹配对应下标的枚举对象。

优缺点 :存储数字、性能高;致命坑点:新增/删除枚举会改变原有枚举下标,导致历史数据映射错乱,生产严禁使用。

二、生产通用方案:自定义TypeHandler映射枚举自定义字段(企业首选)

业务中枚举通常有自定义code(数字编码)、desc(描述),而非使用name和ordinal,需自定义TypeHandler实现枚举code与数据库字段映射,是生产唯一规范用法。

核心原理

  1. 定义通用枚举接口,规范获取编码的方法(getCode());

  2. 自定义泛型TypeHandler,实现数据库code值与枚举对象的双向自动转换;

  3. 全局注册TypeHandler,项目自动适配所有实现该接口的枚举,无需逐个配置。

优势:存储数字编码、性能高、稳定性强,新增枚举不影响历史数据,适配所有业务枚举场景。

三、三种映射方式核心对比与选型

  1. EnumTypeHandler:存枚举名称,零配置,仅适用于固定不变的字符串枚举;

  2. EnumOrdinalTypeHandler:存枚举下标,生产禁用,数据极易错乱;

  3. 自定义TypeHandler:存业务编码,稳定安全、适配所有业务,企业项目标准方案。

四、常见生产坑点(面试加分项)

  1. 禁止使用ordinal映射:枚举顺序变更会导致全量数据映射错误;

  2. 默认name映射存储冗余,大数据量场景性能差;

  3. 自定义枚举处理器需全局注册,否则无法自动生效,需手动指定handler;

  4. 枚举字段查询时,入参无需手动转换编码,MyBatis自动适配映射。

五、面试高分总结金句

MyBatis支持枚举映射,内置名称、下标两种原生映射方式,但下标映射存在数据错乱风险、名称映射性能一般;生产环境统一采用自定义TypeHandler实现枚举业务编码映射,兼顾存储性能与数据稳定性,是企业标准化实现方案。

极简速背版(面试口述)

MyBatis可以映射枚举,有三种方式。一是默认EnumTypeHandler映射枚举名称,简单但性能一般;二是EnumOrdinalTypeHandler映射枚举下标,存在数据错乱风险,生产禁用;三是自定义TypeHandler映射枚举业务编码,稳定安全,是企业主流方案。核心原理是通过类型处理器,实现数据库字段与枚举对象的双向自动转换。

简洁版

  1. 结论:MyBatis支持Enum枚举双向映射,依托TypeHandler类型处理器实现;

  2. 默认两种方式:EnumTypeHandler映射枚举name字符串,EnumOrdinalTypeHandler映射枚举下标(生产禁用);

  3. 生产方案:自定义TypeHandler,映射枚举自定义业务code,安全稳定、无数据错乱风险;

  4. 核心原理:通过类型处理器完成数据库字段与Java枚举的自动类型转换与赋值。

34. Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?

标准答案(完整版·面试满分)

MyBatis动态SQL是MyBatis核心核心特性,用于根据业务入参动态拼接、裁剪、优化SQL语句,解决传统硬编码SQL冗余、多条件查询代码繁琐、适配场景单一的问题,可灵活适配多条件模糊查询、选择性更新、批量操作等复杂多变的业务场景,大幅减少重复SQL编写,提升代码通用性与可维护性。

一、MyBatis全部核心动态SQL标签(面试全覆盖)

1. <if> 条件判断标签

最常用的动态标签,用于非必传参数的条件拼接,判断入参是否非空、满足指定条件,动态拼接对应SQL片段。多用于多条件检索,仅参数存在时拼接查询条件,无参数时不拼接。

2. <where> 动态条件前缀标签

自动生成WHERE关键字,同时智能剔除条件开头多余的 and/or,规避手动拼接SQL导致的语法报错,完美适配多if条件组合场景,无需手动处理前缀字符问题。

3. <set> 动态更新标签

专门用于update更新语句,自动生成SET关键字,动态拼接更新字段,同时自动剔除末尾多余的逗号,支持选择性字段更新,仅更新入参不为空的字段,避免全覆盖更新。

4. <foreach> 循环遍历标签

用于遍历数组、集合类型参数,核心适配IN多值查询、批量新增、批量删除、批量更新场景,可自定义遍历分隔符、前后缀,实现批量SQL动态拼接。

5. <choose>、<when>、<otherwise> 多路条件标签

类似Java的switch-case互斥逻辑,多条件优先级判断,只会命中第一个满足条件的分支,所有条件互斥,不会多条件叠加生效,适用于优先级筛选、互斥条件查询场景。

6. <trim> 自定义截取标签

万能自定义标签,可自定义前缀、后缀、需剔除的前后缀字符,可替代where、set标签,灵活处理特殊SQL拼接场景,适配非常规动态SQL裁剪需求。

7. <bind> 参数绑定标签

自定义参数变量、拼接参数内容,常用于跨数据库模糊查询,解决MySQL、Oracle模糊查询语法差异,提升SQL兼容性。

二、动态SQL底层执行原理(面试核心必背)

MyBatis动态SQL的核心机制是启动预解析树形SqlNode + 运行时OGNL表达式动态渲染,全程无字符串硬拼接,安全且高效,完整流程如下:

  1. 项目启动预解析阶段

MyBatis加载Mapper XML映射文件时,不会直接生成最终SQL,而是将所有动态SQL标签(if/where/foreach等),逐一解析为树形SqlNode节点对象(IfSqlNode、WhereSqlNode、ForEachSqlNode等),所有节点形成完整树形结构,统一缓存到MappedStatement中常驻内存,实现预加载。

  1. 运行时动态渲染阶段

业务请求执行SQL时,MyBatis获取当前接口入参,通过OGNL表达式逐一判断每个SqlNode的条件是否成立,动态筛选、拼接合法的SQL片段。

3.SQL修剪与优化

针对拼接后的SQL,自动执行语法修剪,剔除多余的and/or、逗号、空格等无效字符,生成语法完全合法、适配当前入参的最终可执行SQL。

  1. 预编译执行

最终生成的动态SQL会通过PreparedStatement预编译执行,杜绝SQL注入,保障执行安全。

三、面试高分总结金句

MyBatis动态SQL核心是树形节点预解析+OGNL动态判断,通过各类动态标签适配复杂多变的业务条件,规避硬编码SQL冗余问题,在保证SQL安全、语法合法的前提下,极大提升SQL的灵活性、通用性和可维护性,是MyBatis适配复杂业务的核心能力。

极简速背版(面试口述)

动态SQL用于根据入参动态拼接裁剪SQL,适配多变业务场景。核心标签包含if、where、set、foreach、choose、trim、bind。底层原理是启动时将动态标签解析为树形SqlNode节点缓存,运行时通过OGNL表达式判断入参条件,动态拼接、修剪生成合法SQL,最终预编译执行。

简洁版

  1. 作用:根据入参动态拼接、裁剪SQL,适配多条件查询、选择性更新、批量操作,减少SQL冗余;

  2. 核心标签:if、where、set、foreach、choose/when/otherwise、trim、bind;

  3. 原理:启动解析为SqlNode树形结构,运行时通过OGNL表达式动态判断条件、拼接修剪SQL,预编译执行。

35. Mybatis是如何进行分页的?分页插件的原理是什么?

标准答案(完整版·面试满分)

MyBatis自身仅提供简单逻辑分页 能力,无原生物理分页实现,企业开发中主流采用自定义SQL物理分页 + 第三方分页插件物理分页,共三种分页方式,适配不同业务场景,其中分页插件是生产最优解,底层基于MyBatis插件拦截机制实现无侵入分页增强,完整详解如下:

一、MyBatis三种分页方式详解(优劣+场景对比)

1. RowBounds 逻辑分页(原生自带、生产废弃)

实现原理 :基于内存分页,执行完整SQL查询出全量数据,加载到JVM内存后,通过RowBounds的offset、limit参数在内存中截取对应页码数据。

核心缺点

① 先查全表数据再内存截取,数据量大时会加载海量无效数据,极易导致OOM内存溢出

② 数据库IO开销极大,全表扫描严重拖慢接口性能;

③ 不支持总条数、总页码统计,无法适配前端分页组件。

适用场景 :仅适用于极小数据量、临时简单查询,生产环境完全废弃

2. 自定义SQL物理分页(原生手动实现)

实现原理:在Mapper XML中手动编写分页SQL,基于数据库原生分页语法实现物理分页,MySQL用LIMIT、Oracle用ROWNUM、SQL Server用TOP。

实现方式

① 手动编写业务分页查询SQL,拼接limit #{offset},#{pageSize};

② 单独编写count统计SQL,查询数据总条数;

③ 业务层封装分页结果(当前页、每页条数、总页数、总数据量、数据列表)。

优缺点

✅ 优点:精准可控、无性能隐患、适配所有复杂SQL、无插件依赖;

❌ 缺点:重复代码多、每个分页接口需手写两条SQL、维护成本高、开发效率低。

适用场景:复杂联表分页、特殊业务分页、插件无法适配的定制化分页场景。

3. 分页插件物理分页(PageHelper,企业主流首选)

基于MyBatis插件机制实现的无侵入自动物理分页,是互联网项目标准分页方案,无需手写分页SQL,一行代码即可实现分页。

使用方式 :业务层调用 PageHelper.startPage(pageNum, pageSize),紧邻的第一条查询SQL会自动触发分页。

核心优势:零SQL侵入、开发高效、自动分页、自动统计总条数、适配多数据库。

二、PageHelper分页插件底层核心原理(面试高频必背)

分页插件核心依托 MyBatis插件拦截机制 + 动态SQL改写 实现,基于责任链模式拦截核心执行对象,无侵入增强分页能力,完整执行流程如下:

1. 核心拦截点位

插件自定义拦截器,精准拦截 MyBatisStatementHandler 的 prepare 方法,该方法在SQL预编译、执行前触发,是改写SQL的最佳时机。

2. 分页参数缓存

业务代码调用PageHelper.startPage() 时,插件通过 ThreadLocal 将页码、页大小、排序等分页参数缓存到当前线程中,线程隔离、互不干扰。

3. 动态改写SQL

拦截到SQL执行请求后,检测当前线程存在分页参数:

① 自动拦截原始查询SQL,根据数据库类型(MySQL/Oracle)动态拼接分页语句(MySQL拼接LIMIT);

② 自动生成独立的 count统计SQL,查询数据总条数、计算总页码;

③ 完成SQL预编译与执行,无需开发者手动改造SQL。

4. 结果封装与参数清空

SQL执行完成后,插件自动将分页数据、总条数、分页信息封装为PageInfo分页对象;同时清空ThreadLocal中的分页参数,避免参数残留导致后续SQL误分页。

三、分页插件核心特性与生产规范

  1. 就近生效原则 :startPage仅对紧邻的第一条查询SQL生效,后续SQL不受影响;

  2. 多数据库适配:自动识别数据库方言,适配MySQL、Oracle、SQL Server等主流数据库分页语法;

  3. 支持排序:可携带排序参数,自动拼接ORDER BY排序语句;

  4. 线程安全:基于ThreadLocal存储参数,多线程环境无参数串扰问题。

四、生产高频坑点(面试加分项)

  1. 禁止startPage后空行、空逻辑,必须紧邻查询语句,否则会拦截错误SQL导致分页异常;

  2. 复杂多表联查、嵌套查询可能出现count统计不准问题,需手动自定义count SQL;

  3. 批量查询、返回非List结果的SQL,需规避分页拦截,避免报错;

  4. 高并发场景无需担心线程安全,ThreadLocal天然隔离分页参数。

五、面试高分总结金句

MyBatis分页分为三种方式,RowBounds内存分页性能极差已废弃,自定义SQL分页精准可控但繁琐,生产主流使用PageHelper插件分页;插件核心原理是通过拦截StatementHandler前置方法,结合ThreadLocal缓存分页参数,动态改写SQL拼接分页语句、自动统计总数,实现无侵入物理分页,兼顾开发效率与查询性能。

极简速背版(面试口述)

MyBatis有三种分页方式,RowBounds是内存逻辑分页,大数据量易OOM已废弃;手动写limit语句是物理分页,精准但繁琐;生产常用PageHelper插件分页。插件原理是基于MyBatis插件机制拦截StatementHandler,通过ThreadLocal缓存分页参数,执行前动态改写SQL拼接分页条件、自动统计总条数,实现无侵入物理分页。

简洁版

  1. RowBounds逻辑分页:查询全量数据后内存截取,性能差、易OOM,生产废弃;

  2. 自定义物理分页:手动编写limit分页SQL+count统计,精准稳定、开发繁琐;

  3. PageHelper插件分页原理:拦截StatementHandler、ThreadLocal缓存分页参数、动态改写SQL实现自动物理分页,企业主流方案。

36. 简述Mybatis的插件运行原理,以及如何编写一个插件。

标准答案(完整版·面试满分)

MyBatis插件是基于动态代理 + 责任链模式实现的无侵入式扩展机制,核心用于拦截框架核心执行流程,实现功能增强,无需修改MyBatis源码,可灵活拓展分页、数据权限、字段脱敏、慢SQL监控、日志打印等通用能力,是MyBatis高扩展性的核心支撑,具体运行原理、完整插件编写流程、生产规范详解如下:

一、MyBatis插件核心运行原理

1. 四大可拦截核心对象(唯一拦截点位·面试必背)

MyBatis仅允许拦截框架四大核心对象的指定方法,不支持任意节点拦截,精准管控扩展边界,四大对象职责如下:

Executor(执行器):拦截SQL执行、事务提交/回滚、缓存操作,适合做数据权限、SQL拦截、事务监控;

StatementHandler(语句处理器):拦截SQL预处理、执行过程,适合做分页改写、慢SQL统计、SQL日志打印;

ParameterHandler(参数处理器):拦截参数绑定、赋值过程,适合做参数脱敏、参数校验、类型转换增强;

ResultSetHandler(结果集处理器):拦截结果集封装过程,适合做查询结果脱敏、数据格式化、结果二次处理。

2. 底层核心机制

项目启动时,MyBatis加载所有自定义插件,通过JDK动态代理对四大核心目标对象进行代理包装,生成代理对象;

程序运行执行数据库操作时,会优先进入插件拦截方法,多个插件遵循责任链模式逐层执行(先包装后执行、后包装先执行),执行完自定义增强逻辑后,再调用原生方法完成原有业务流程;

整个过程无源码侵入、无业务耦合,实现框架功能的动态增强。

3. 插件执行顺序规则

多个插件同时生效时,拦截前置逻辑:后配置先执行;拦截后置逻辑:先配置先执行,遵循责任链嵌套执行规则。

二、完整插件编写步骤(企业实战流程·可直接落地)

步骤1:自定义拦截器类,实现Interceptor核心接口

实现MyBatis核心拦截器接口org.apache.ibatis.plugin.Interceptor,重写三大核心方法,定义拦截规则与增强逻辑。

步骤2:通过注解指定拦截对象与拦截方法

使用@Intercepts + @Signature注解,精准指定需要拦截的类、方法、参数,锁定拦截点位,避免全局无效拦截。

步骤3:实现拦截增强逻辑(intercept方法)

intercept是核心执行方法,通过Invocation.proceed()调用原生方法,可在原生方法执行前、后自定义增强逻辑(如改写SQL、统计耗时、脱敏数据等)。

步骤4:实现插件包装方法(plugin方法)

通过Plugin.wrap(target, this)对目标对象生成代理对象,完成插件绑定,固定模板写法。

步骤5:实现配置解析方法(setProperties方法)

读取全局配置文件中插件的自定义配置参数,实现插件可配置化,提升通用性。

步骤6:全局配置文件注册插件

在mybatis-config.xml中通过<plugin>标签注册自定义拦截器,配置自定义参数,项目启动后自动生效。

三、极简代码示例(通用模板·面试可默写)

java 复制代码
// 1. 自定义插件拦截StatementHandler,实现慢SQL监控
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class SlowSqlInterceptor implements Interceptor {
    // 拦截核心逻辑
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 方法执行前:记录开始时间
        long start = System.currentTimeMillis();
        // 执行原生SQL预处理逻辑
        Object result = invocation.proceed();
        // 方法执行后:统计耗时、监控慢SQL
        long cost = System.currentTimeMillis() - start;
        if (cost > 1000) { // 超过1秒判定为慢SQL
            System.out.println("检测到慢SQL,耗时:" + cost + "ms");
        }
        return result;
    }
    // 绑定代理对象
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    // 读取配置参数
    @Override
    public void setProperties(Properties properties) {}
}

四、SpringBoot整合简化配置

无需编写mybatis-config.xml,直接将自定义拦截器注入Spring容器,或通过@Configuration配置类注册插件,自动生效。

五、生产核心注意事项(面试加分项)

  1. 仅拦截必要对象与方法,避免全局拦截导致性能损耗;

  2. 多个插件共存时,注意执行顺序,避免功能冲突、覆盖;

  3. 拦截逻辑尽量轻量化,禁止在拦截器中执行耗时、阻塞操作;

  4. 插件属于全局增强,需做好异常捕获,避免单个插件异常导致全局SQL失效。

六、面试高分总结金句

MyBatis插件基于动态代理与责任链模式,拦截Executor、StatementHandler、ParameterHandler、ResultSetHandler四大核心对象,实现无侵入功能增强;开发插件核心是实现Interceptor接口、注解锁定拦截点位、自定义增强逻辑、注册生效,可快速实现分页、监控、脱敏等通用功能,是框架扩展性的核心体现。

极简速背版(面试口述)

MyBatis插件原理是通过动态代理包装四大核心执行对象,基于责任链模式拦截SQL执行流程,实现无侵入功能增强。编写插件分五步:实现Interceptor拦截器接口、注解指定拦截方法、重写intercept实现增强逻辑、plugin方法绑定代理、注册插件生效,可用于分页、慢SQL监控、数据脱敏等场景。

简洁版

1. 插件运行原理 :依托动态代理+责任链模式,拦截Executor、StatementHandler、ParameterHandler、ResultSetHandler四大核心对象,在SQL执行前后做无侵入增强,不修改源码、全局生效。

2. 插件编写步骤

① 实现Interceptor拦截器接口;

② @Intercepts+@Signature注解定义拦截点位;

③ intercept方法编写自定义增强逻辑;

④ plugin方法完成代理绑定;

⑤ 注册插件至全局配置,完成生效。

37. Mybatis缓存机制

标准答案(完整版·面试满分)

MyBatis内置两级缓存架构 ,分别是默认开启的一级缓存(SqlSession会话级缓存) 和手动开启的二级缓存(Mapper命名空间级缓存),核心作用是减少重复SQL查询、降低数据库IO压力、提升接口查询性能,两级缓存各司其职、层层兜底,是MyBatis核心性能优化机制,完整原理、特性、失效场景、优先级与生产坑点详解如下:

一、一级缓存(本地缓存·默认开启)

1. 核心定位与生效范围

一级缓存是SqlSession会话级别 的本地缓存,默认全程自动开启、无需手动配置,缓存数据仅在同一个数据库会话内共享,不同SqlSession会话完全隔离、无法互通。

2. 底层原理

每个SqlSession内部维护一个独立的本地缓存容器(PerpetualCache),查询数据时,会以「SQL语句 + 参数 + 分页条件 + 映射规则」为唯一key缓存查询结果;同一会话内重复执行完全一致的查询,直接读取内存缓存数据,无需访问数据库。

3. 核心失效场景(面试高频必背)

  1. SqlSession会话关闭、销毁,一级缓存自动清空;

  2. 同一会话内执行任意增删改操作(insert/update/delete),自动清空当前会话全部一级缓存,避免脏数据;

  3. 手动调用sqlSession.clearCache()方法主动清空缓存;

  4. 两次查询的SQL语句、入参、分页、排序条件不一致,无法命中缓存。

4. 核心特点

无需手动开启、会话隔离、无脏数据风险、生命周期短、仅适用于单次会话重复查询场景。

二、二级缓存(全局缓存·手动开启)

1. 核心定位与生效范围

二级缓存是Mapper命名空间(namespace)级别的全局缓存,跨SqlSession、跨请求、跨会话共享,全局生效,默认关闭,需手动配置开启,是项目级别的查询缓存。

2. 开启必备条件

  1. 全局配置文件开启二级缓存总开关:<setting name="cacheEnabled" value="true"/>(默认true);

  2. 对应Mapper XML文件中添加<cache/>标签开启当前命名空间缓存;

  3. 查询返回的实体类必须实现Serializable序列化接口,支持缓存对象序列化持久化。

3. 底层原理

项目启动后,每个Mapper命名空间独立维护一个全局缓存容器,查询数据优先存入二级缓存;所有访问该Mapper的请求,均可共享缓存数据,大幅减少全局重复查库次数。

4. 核心失效场景

  1. 当前同一个namespace下执行增删改操作,自动清空该命名空间下所有二级缓存;

  2. 手动清空对应Mapper缓存、项目重启、缓存过期,缓存失效;

  3. 查询条件、SQL语句变更,无法命中缓存。

5. 核心特点与生产坑点(面试加分项)

✅ 优点:跨会话共享、全局复用、大幅降低高频查询数据库压力;

❌ 致命坑点:多Mapper操作同一张数据表时,会出现缓存脏数据 。比如A、B两个Mapper同时操作user表,A Mapper执行更新操作,只会清空A的二级缓存,B的缓存不会刷新,导致后续查询读取旧数据,因此实时性高、多模块操作同表的生产项目普遍禁用二级缓存

三、两级缓存查询优先级(执行顺序)

查询请求执行时,缓存命中优先级自上而下:二级缓存(全局) → 一级缓存(会话) → 数据库

  1. 优先查询二级缓存,命中直接返回数据;

  2. 二级缓存未命中,查询当前会话一级缓存,命中直接返回;

  3. 两级缓存均未命中,才执行SQL查询数据库,并将结果依次回填至一级、二级缓存。

四、一二级缓存核心区别(极简对比)

  1. 生效范围:一级缓存是会话级,二级缓存是命名空间全局级;

  2. 开启状态:一级默认开启,二级需手动配置开启;

  3. 共享性:一级缓存会话隔离、不可共享,二级缓存跨会话全局共享;

  4. 脏数据风险:一级无脏数据风险,二级多Mapper操作同表易产生脏数据;

  5. 生命周期:一级随会话销毁,二级随项目常驻、手动失效。

五、面试高分总结金句

MyBatis通过一级、二级两级缓存实现查询性能优化,一级缓存默认开启、会话隔离、安全无脏数据,保障单次会话查询效率;二级缓存全局共享、提升高频查询性能,但存在跨Mapper脏数据风险,生产中仅推荐在读多写少、单Mapper单表操作、实时性要求低的静态数据场景使用,核心业务、实时业务建议禁用二级缓存。

极简速背版(面试口述)

MyBatis拥有两级缓存,一级缓存是默认开启的SqlSession会话级缓存,同会话重复查询复用数据,增删改、关会话即清空,无脏数据;二级缓存是手动开启的Mapper命名空间全局缓存,跨会话共享,需要实体序列化,多Mapper操作同表易产生脏数据。查询优先级为先查二级缓存、再查一级缓存、最后查数据库。

简洁版

1. 一级缓存:默认开启,SqlSession会话级别,会话内共享,增删改、关闭会话自动清空,无脏数据风险;

2. 二级缓存:手动开启,Mapper命名空间全局级别,跨会话共享,需实体序列化,同namespace增删改清空缓存,多Mapper操作同表易脏数据;

3. 查询优先级:二级缓存 → 一级缓存 → 数据库;

4. 生产选型:一级缓存全程使用,实时业务禁用二级缓存,静态读多写少业务可开启二级缓存优化性能。

38. Dao 接口的工作原理是什么?Dao 接口里的方法,参数不同时,方法能重载吗?

标准答案(完整版·面试满分)

一、Dao(Mapper)接口的工作原理

MyBatis的Mapper/Dao接口无需手动编写实现类 ,核心依托JDK动态代理机制实现接口方法调用,全程由MyBatis底层自动生成代理对象并完成数据库操作,完整工作原理如下:

  1. 绑定映射关系:Mapper接口的全限定类名对应Mapper XML文件的namespace,接口中的方法名与XML中标签的id一一绑定,建立「接口方法-SQL语句」的唯一映射关系。

  2. 动态代理生成实现类 :项目启动时,MyBatis通过MapperScannerConfigurer扫描所有Mapper接口,基于JDK动态代理为每个接口自动生成代理实现类,无需开发者手写DAO实现。

  3. 拦截方法执行SQL:业务层调用Mapper接口方法时,会被动态代理拦截,代理对象根据「接口全类名(namespace)+ 方法名」精准匹配内存中预加载的MappedStatement,获取对应的SQL语句、参数映射、结果封装规则。

  4. 调度执行并返回结果:代理对象调度Executor执行器,完成参数绑定、SQL预编译、数据库执行、结果集自动封装,最终将Java实体/集合结果返回给业务层,完成完整数据库操作。

二、Dao接口方法能否重载(核心考点)

不支持重载结论:Mapper接口方法,参数不同也无法实现方法重载,生产环境严禁编写重载方法。

底层核心原因

  1. MyBatis匹配SQL的唯一依据是namespace + 方法名不识别方法参数类型、参数个数差异

  2. 若接口中存在同名、不同参数的重载方法,MyBatis无法区分绑定对应的SQL标签(id唯一对应方法名),会出现SQL绑定冲突、方法覆盖、调用异常等问题。

  3. Mapper XML文件中,同一个namespace下的标签id必须全局唯一,对应接口方法名必须唯一,天然不支持重载机制。

三、业务替代方案

如需实现不同参数的查询逻辑,禁止重载,需定义不同方法名,分别对应XML中不同id的SQL标签,保证映射关系唯一、调用稳定。

四、面试高分总结金句

Mapper接口核心依靠JDK动态代理实现无实现类调用,通过namespace+方法名唯一匹配SQL语句;由于MyBatis仅通过方法名绑定SQL,不识别参数差异,因此Mapper接口方法完全不支持重载,所有接口方法名必须保证唯一。

极简速背版(面试口述)

Dao接口基于JDK动态代理工作,MyBatis自动生成接口代理对象,通过namespace加方法名匹配并执行对应SQL,无需手动写实现类。Mapper接口不支持方法重载,因为MyBatis仅通过方法名绑定SQL,不识别参数差异,重载会导致SQL绑定冲突。

简洁版

  1. 工作原理:MyBatis通过JDK动态代理为Mapper接口生成代理对象,依托namespace+方法名匹配对应SQL,自动完成SQL执行与结果封装,无需手动实现接口。

  2. 重载问题:不支持重载,MyBatis仅以方法名作为SQL唯一标识,不区分参数差异,重载会引发SQL绑定冲突、调用异常。

39. MyBatis 的 xml 映射文件中,不同的 xml 映射文件,id 是否可以重复?

标准答案(完整版·面试满分)不同XML映射文件中,SQL的id完全可以重复,不会冲突;同一XML文件中id必须唯一。

一、核心底层原理

MyBatis 中 SQL 语句的唯一标识不是单纯的id,而是 namespace + id 组合

  1. 每个 Mapper XML 文件的根节点都配置独立的 namespace(对应Mapper接口全类名),作为独立命名空间隔离;

  2. MyBatis 内存中缓存的 MappedStatement 唯一 Key 为:namespace + sqlId

  3. 不同 XML 文件 namespace 不同,即便 id 完全一致,组合后的唯一标识也不重复,不会出现覆盖、绑定冲突问题。

二、严格约束规则

  1. 不同XML文件:id 允许重复,命名空间天然隔离,安全无冲突;

  2. 同一个XML文件:id 必须全局唯一,不允许重复,否则会覆盖原有SQL,导致接口报错、执行SQL异常;

  3. Mapper 接口方法与 XML id 一一对应,不同接口可以存在同名方法,完全符合规范。

三、生产开发规范

  1. 无需刻意全局唯一id,只需保证单文件内id唯一即可,大幅降低命名成本;

  2. 建议业务语义化命名(如 getUserById、listUserByCondition),即便跨文件重名也无任何影响;

  3. 禁止同一XML内定义重复id,避免SQL覆盖、线上诡异BUG。

四、面试高分总结金句

MyBatis依靠namespace命名空间隔离不同映射文件的SQL标识,唯一索引是namespace+id而非单纯id,因此跨XML文件id可重复,同XML文件id必须唯一,这是MyBatis映射隔离的核心机制。

极简速背版(面试口述)

不同MyBatis的XML映射文件id可以重复,因为MyBatis通过namespace加id作为SQL唯一标识,命名空间不同不会冲突;但同一个XML文件中id必须唯一,否则会出现SQL覆盖报错。

简洁版

  1. 结论:不同XML文件id可重复,同XML文件id不可重复;

  2. 原理:SQL唯一标识为 namespace + id,命名空间实现文件隔离;

  3. 规范:单文件id唯一,跨文件无唯一性要求。

40. MyBatis 映射文件中,如果 A 标签通过 include 引用了 B 标签的内容,请问,B 标签能否定义在 A 标签的后面,还是说必须定义在 A 标签的前面?

标准答案(完整版·面试满分)B 标签可以定义在 A 标签后方,无需遵循书写前后顺序,MyBatis 不存在「先定义后引用」的书写强制约束。

一、底层核心原理

MyBatis 解析 Mapper XML 文件是全局一次性加载、先全量解析、后关联引用的机制,并非逐行即时解析执行:

  1. 项目启动加载 Mapper XML 时,MyBatis 会先扫描当前文件中所有 <sql> 片段标签,统一缓存到全局片段容器中,以标签 id 作为唯一标识常驻内存;

  2. 完成全文件所有 SQL 片段、查询语句加载解析后,再统一处理所有 <include refid=""> 引用逻辑;

  3. 无论被引用的 B 标签书写在 A 标签前方还是后方,只要当前 XML 文件中存在对应 id 的 <sql> 片段,均可正常匹配引用,不会报错。

二、核心规则与实操说明

  1. 同文件引用:A 标签与 B 标签在同一个 Mapper XML 中,B 标签任意位置定义均可,无前后顺序限制;

  2. 跨文件引用 :可通过 namespace.id 引用其他 XML 文件的 SQL 片段,同样不受书写顺序影响;

  3. 唯一约束 :仅要求引用的 refid 必须全局唯一且存在,不存在书写顺序的语法限制。

三、面试高分总结金句

MyBatis 采用先全量加载缓存 SQL 片段、后统一解析引用的机制,打破了代码逐行执行的顺序约束,因此 include 引用的 SQL 片段无需提前定义,前后书写均可,只要片段 id 有效即可正常生效。

极简速背版(面试口述)

B标签可以写在A标签后面。因为MyBatis启动时会先全量加载缓存所有sql片段,再统一处理include引用,不遵循逐行解析顺序,无需提前定义,只要id匹配即可正常引用。

简洁版

  1. 结论:被引用的B标签可定义在A标签后方,无书写顺序限制;

  2. 原理:MyBatis先全量解析缓存所有SQL片段,再统一处理引用逻辑;

  3. 约束:仅需保证refid对应的SQL片段id存在且唯一。

41. MyBatis 完整执行流程、核心组件作用

标准答案(完整版·面试满分)

MyBatis完整执行流程分为项目启动初始化(一次性执行) 和**业务运行调用(每次SQL执行)**两个核心阶段,依托七大核心组件层层协作完成数据库全流程操作,流程清晰、职责解耦,是面试高频核心考点,完整细化流程与组件作用如下:

一、第一阶段:项目启动初始化阶段(全局仅执行一次)

  1. 加载解析配置文件:通过 SqlSessionFactoryBuilder 加载 mybatis-config.xml 全局配置文件、所有 Mapper 映射文件,解析全局参数、插件、环境配置、SQL映射规则、动态SQL节点等所有配置信息。

  2. 构建全局Configuration对象:将所有解析后的配置、SQL语句、映射关系、插件规则统一封装为全局单例 Configuration 对象,常驻内存;同时将每一条SQL语句封装为 MappedStatement 对象,以 namespace+方法名为唯一标识缓存,完成SQL预加载。

  3. 创建全局SqlSessionFactory:基于 Configuration 配置,构建全局唯一、单例常驻的 SqlSessionFactory 会话工厂,作为MyBatis全局核心入口,全程负责生产数据库会话。

二、第二阶段:业务运行执行阶段(每次数据库请求执行)

  1. 获取SqlSession会话:业务操作数据库时,通过 SqlSessionFactory 创建 SqlSession 数据库会话,默认手动事务提交,内置缓存、事务、执行调度能力。

  2. 获取Mapper动态代理对象:SqlSession 获取 Mapper 接口的 JDK 动态代理对象,无需手动编写DAO实现类,拦截接口方法调用。

  3. 匹配MappedStatement:代理对象通过「Mapper全类名(namespace) + 方法名」精准匹配内存中预加载的 MappedStatement,获取对应SQL、参数映射、结果集映射规则。

  4. Executor执行器调度:SqlSession 调用底层 Executor 执行器(默认SimpleExecutor),优先查询一、二级缓存,无缓存则进入SQL执行流程。

  5. 参数预处理(ParameterHandler):解析方法入参,完成参数类型转换、预编译赋值,通过 #{ } 占位符隔离参数,杜绝SQL注入。

  6. SQL语句处理(StatementHandler):创建JDBC PreparedStatement,解析拼接动态SQL、处理分页/排序,完成SQL预编译。

  7. 执行SQL语句:通过JDBC底层向数据库提交SQL,执行查询/增删改操作。

  8. 结果集封装(ResultSetHandler):接收数据库返回的 ResultSet,结合反射和 resultType/resultMap 映射规则,自动封装为Java实体、集合、Map等对象。

  9. 事务处理与缓存更新:增删改操作自动提交事务、清空对应缓存;查询操作回填数据至一、二级缓存。

  10. 资源释放:关闭SqlSession、释放数据库连接资源,避免连接泄露,返回最终业务数据。

三、七大核心组件核心作用(面试必背)

  1. SqlSessionFactoryBuilder:配置构建器,仅负责解析配置文件、构建Configuration、创建SqlSessionFactory,一次性临时对象,构建完成即销毁。

  2. SqlSessionFactory:全局会话工厂,单例常驻内存,核心作用是生产SqlSession数据库会话,统一管理全局配置与映射规则。

  3. SqlSession:数据库操作顶层入口,提供CRUD通用方法,管理会话级一级缓存、数据库事务,调度底层执行流程。

  4. Executor(执行器):SQL执行总指挥,负责调度三大处理器、管理一二级缓存、控制SQL执行方式、维护事务状态,包含简单、复用、批量、缓存四种执行模式。

  5. ParameterHandler(参数处理器):专门处理SQL入参,完成参数绑定、类型转换、特殊字符转义,基于预编译彻底杜绝SQL注入。

  6. StatementHandler(语句处理器):对接JDBC底层,负责创建、预处理、执行SQL语句,处理动态SQL、分页排序、超时控制,是插件核心拦截点位。

  7. ResultSetHandler(结果集处理器):负责SQL执行后的结果集解析,自动将数据库ResultSet映射为Java实体对象,完成数据双向映射封装。

四、面试高分总结金句

MyBatis执行流程核心分为启动加载配置缓存SQL、运行动态代理调度执行两大阶段,依托七大核心组件分层协作,完成配置解析→会话创建→SQL匹配→参数处理→语句执行→结果封装→资源释放全流程,在封装JDBC冗余操作的同时,保留SQL可控性,兼顾性能与灵活性。

极简速背版(面试口述)

MyBatis启动时解析配置生成Configuration,创建SqlSessionFactory;运行时通过工厂获取SqlSession,借助动态代理调用Mapper方法,匹配预加载的SQL语句,由Executor调度ParameterHandler、StatementHandler、ResultSetHandler完成参数处理、SQL执行、结果封装,最终释放资源。七大组件各司其职,完整支撑数据库操作全流程。

简洁版

完整流程:加载配置文件 → 构建全局Configuration → 创建SqlSessionFactory → 开启SqlSession会话 → 获取Mapper代理对象 → 匹配MappedStatement → Executor调度执行 → 参数预处理 → SQL预编译执行 → 结果集自动封装 → 缓存更新、事务处理 → 释放资源返回数据。

核心组件作用

  1. SqlSessionFactoryBuilder:解析配置、构建会话工厂;

  2. SqlSessionFactory:全局生产数据库会话;

  3. SqlSession:数据库CRUD操作入口,管理缓存与事务;

  4. Executor:SQL执行调度、缓存管理核心;

  5. ParameterHandler:参数绑定、防SQL注入;

  6. StatementHandler:预处理、执行SQL语句;

  7. ResultSetHandler:自动封装数据库结果集为Java对象。

简洁:

完整流程:加载配置 → 构建Configuration → 创建SqlSessionFactory → 开启SqlSession → Executor调度 → StatementHandler处理SQL → ParameterHandler参数赋值 → 执行SQL → ResultSetHandler结果封装 → 返回结果、关闭资源。

核心组件作用

  1. Configuration:全局配置容器;

  2. SqlSessionFactory:生产会话;

  3. SqlSession:操作入口;

  4. Executor:执行调度与缓存管理;

  5. 三大Handler:分别处理语句、参数、结果集。

42. N+1 查询问题成因、危害、解决方案(含完整实战代码)

标准答案(完整版·面试满分+可落地代码)

N+1 查询是 MyBatis 关联查询中最经典的性能BUG ,指程序先查询出N条主表数据(1次SQL) ,再循环遍历每条主数据,单独查询关联从表数据(N次SQL ),最终总共执行 1+N 条SQL,数据量越大,性能损耗越严重。

一、完整成因深度解析

  1. 触发场景:MyBatis 关联嵌套子查询(association/collection 嵌套 select 查询)、开启延迟加载场景;

  2. 执行流程:先执行1条SQL查询所有主表数据,得到N条结果;遍历N条主数据,逐条触发关联子查询,额外执行N条SQL查询关联数据;

  3. 核心根源:关联查询使用子查询嵌套,而非联表查询,框架无法批量加载关联数据,只能循环单条查询。

二、N+1 问题核心危害

  1. 数据库IO暴增:单接口查询从1次SQL变为数十、上百次SQL,大幅增加数据库连接、IO、CPU开销;

  2. 接口响应变慢:循环查库产生大量网络往返耗时,大数据量下接口超时、卡顿严重;

  3. 高并发场景雪崩:大量N+1请求堆积,极易打垮数据库,引发服务雪崩。

三、N+1 问题实战复现(问题代码)

场景:用户表(主表)& 订单表(从表),一个用户对应多个订单,一对多关联

1. 实体类定义

java 复制代码
// 用户主实体
public class User {
    private Long id;
    private String username;
    // 一对多:一个用户对应多个订单
    private List<Order> orderList;
    // getter/setter 省略
}
// 订单从实体
public class Order {
    private Long id;
    private Long userId;
    private String orderNo;
    // getter/setter 省略
}

2. Mapper接口

java 复制代码
public interface UserMapper {
    // 查询所有用户
    List<User> selectAllUser();
}
public interface OrderMapper {
    // 根据用户ID查询订单
    List<Order> selectOrderByUserId(Long userId);
}

3. 触发N+1的Mapper XML(问题代码)

XML 复制代码
<!-- 用户查询映射,嵌套子查询触发N+1 -->
<resultMap id="UserResultMap" type="com.entity.User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <!-- 核心问题:嵌套子查询,每条用户都会单独执行一次订单查询 -->
<collection property="orderList" 
                select="com.mapper.OrderMapper.selectOrderByUserId"
 column="id"/>
</resultMap>
<select id="selectAllUser" resultMap="UserResultMap">
    SELECT id, username FROM user
</select>
<!-- 订单查询SQL -->
<select id="selectOrderByUserId" resultType="com.entity.Order">
    SELECT id,user_id,order_no FROM `order` WHERE user_id = #{userId}
</select>

4. 执行效果(N+1现象)

若数据库有100个用户:执行 1次查询所有用户SQL + 100次循环查询订单SQL,总共101条SQL,严重损耗性能。

四、三种生产级解决方案(完整可运行代码)

方案一:联表嵌套结果查询(最优方案·企业首选)

核心思路:放弃嵌套子查询,直接使用LEFT JOIN联表查询,一次性查询主表+从表所有数据,MyBatis通过resultMap自动封装一对多关系,全程仅执行1条SQL,彻底杜绝N+1。

优化后XML代码

XML 复制代码
<!-- 一对多联表查询结果映射 -->
<resultMap id="UserOrderResultMap" type="com.entity.User">
    <id property="id" column="u_id"/>
    <result property="username" column="username"/>
    <collection property="orderList" ofType="com.entity.Order">
        <id property="id" column="o_id"/>
        <result property="userId" column="user_id"/>
        <result property="orderNo" column="order_no"/>
    </collection>
</resultMap>
<!-- 联表一次性查询所有数据,仅执行1条SQL -->
<select id="selectAllUserWithOrder" resultMap="UserOrderResultMap">
    SELECT u.id u_id, u.username, o.id o_id, o.user_id, o.order_no
    FROM `user` u
    LEFT JOIN `order` o ON u.id = o.user_id
</select>

方案二:开启延迟加载(按需加载·折中方案)

核心思路:全局开启延迟加载,未使用关联属性时,不触发子查询;仅当业务代码调用关联属性时,才执行查询,减少无效SQL查询。

1. 全局配置开启延迟加载(mybatis-config.xml)

XML 复制代码
<settings>
    <!-- 开启延迟加载 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 关闭积极加载,仅按需加载 -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

2. 适用场景:无需每次都查询关联数据,仅部分业务需要关联数据,减少无效查库。

方案三:业务层批量查询(兜底方案)

核心思路:先查询所有主表数据,提取所有主表ID,批量IN查询关联从表数据,手动组装关联关系,仅执行2次SQL(1次主表+1次从表),彻底规避循环查库。

完整业务代码

java 复制代码
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private OrderMapper orderMapper;

    public List<User> getUserWithOrderBatch() {
        // 1. 一次性查询所有用户(1次SQL)
        List<User> userList = userMapper.selectAllUser();
        if (CollectionUtils.isEmpty(userList)) {
            return userList;
        }
        // 2. 提取所有用户ID
        List<Long> userIdList = userList.stream()
                .map(User::getId)
                .collect(Collectors.toList());
        // 3. 批量IN查询所有关联订单(仅1次SQL)
        List<Order> orderList = orderMapper.selectOrderByUserIdList(userIdList);
        // 4. 分组组装数据,手动绑定用户和订单关系
        Map<Long, List<Order>> orderMap = orderList.stream()
                .collect(Collectors.groupingBy(Order::getUserId));
        userList.forEach(user -> user.setOrderList(orderMap.get(user.getId())));
        return userList;
    }
}

对应Mapper批量查询SQL

XML 复制代码
<select id="selectOrderByUserIdList" resultType="com.entity.Order">
    SELECT id,user_id,order_no FROM `order` WHERE user_id IN
    <foreach collection="userIdList" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

五、面试高分总结金句

N+1查询核心成因是嵌套子查询导致循环查库 ,最优解决方案是优先使用联表嵌套结果查询,一次性加载所有数据;次要方案是业务层批量IN查询组装数据,折中方案是开启延迟加载减少无效查询,生产环境严禁使用嵌套子查询关联映射,彻底规避性能隐患。

极简速背版(面试口述)

N+1查询是嵌套子查询引发的性能问题,先查N条主数据(1次SQL),再循环查N次关联数据,导致SQL数量暴增。解决方案有三种:一是用LEFT JOIN联表查询一次性加载所有数据,最优;二是业务层批量IN查询,手动组装关联数据;三是开启延迟加载,按需查询减少无效SQL。

简洁版

  1. 成因:MyBatis collection/association 嵌套子查询,遍历主数据循环查询关联数据,产生1+N条SQL;

  2. 危害:数据库IO暴增、接口响应慢、高并发易引发性能瓶颈;

  3. 解决方案:优先联表嵌套结果查询、其次业务层批量IN查询、折中开启延迟加载。

简洁:

成因:关联嵌套查询,先查询N条主表数据(1次SQL),再循环每条数据查询关联从表(N次SQL),产生1+N条SQL,性能极差。

解决方案

  1. 使用联表嵌套结果查询,一次查询全部数据;

  2. 合理控制延迟加载时机;

  3. 业务层批量查询关联数据,避免循环查库。

43. SQL 注入原理与防范(MyBatis专属面试满分题)

标准答案(完整版·面试满分)

SQL注入是Web开发最经典的高危漏洞,在MyBatis场景下,注入漏洞的产生、规避方式具有极强的框架专属特性,核心区分 ${} 字符串拼接#{} 预编译占位符 的本质差异,具体原理、漏洞案例、完整防范方案、生产规范详解如下:

一、SQL注入核心原理

SQL注入的本质是:用户可控参数被当作SQL代码逻辑执行

当程序直接将用户输入的参数通过字符串拼接的方式拼入SQL语句时,用户可输入恶意SQL关键字、特殊语法(如 or 1=1、delete、union、--注释符),篡改原有SQL的执行逻辑,绕过权限校验、查询隐私数据、删除篡改数据库数据,造成数据泄露、数据丢失、权限越权等严重安全问题。

二、MyBatis场景下注入漏洞根源(核心考点)

  1. ${} 字符串直接拼接(高危·存在注入漏洞)

MyBatis使用 ${} 时,不会做任何预编译和字符转义,直接将用户输入的字符串拼接进SQL语句,参数和SQL逻辑混为一体,用户输入的恶意SQL会被数据库直接解析执行,是MyBatis SQL注入的唯一源头。

适用场景:仅用于固定不变、非用户可控的参数,如动态表名、排序字段、数据库关键字。

  1. #{} 预编译占位符(安全·彻底防注入)

MyBatis使用 #{} 时,底层基于JDBC PreparedStatement实现SQL模板与参数完全隔离 ,提前预编译SQL语句结构,用户传入的所有参数只会被数据库视为纯普通数据,不会被解析为SQL指令、关键字,自动转义特殊字符,从底层彻底杜绝SQL注入。

三、SQL注入实战漏洞案例

业务场景:根据用户名查询用户数据,使用${}接收用户参数(漏洞代码)

漏洞SQL

XML 复制代码
<select id="getUserByName" resultType="User">
select * from user where username = '${name}'
</select>

恶意注入参数 :用户输入 admin' or '1'='1

拼接后最终执行SQL

sql 复制代码
select * from user where username = 'admin' or '1'='1'

漏洞后果:条件恒成立,查询出数据库所有用户数据,造成全员数据泄露;若输入删除、修改恶意参数,可直接篡改、清空业务数据。

四、全方位防范方案(生产落地·面试必背)

  1. 优先使用 #{} 预编译占位符(核心方案)

所有接收用户输入、前端传参、接口可控参数的场景,统一使用 #{},依托预编译机制自动防注入,无需手动转义,安全且高效。

  1. 严格限制 ${} 使用场景

禁止用${}接收任何用户可控参数,仅在动态表名、动态排序、动态字段等无法使用预编译的场景少量使用。

  1. ${} 场景强制白名单校验

必须使用${}时,对传入的参数做严格白名单过滤,校验参数是否为预设合法值,拒绝所有非法、恶意参数,杜绝任意参数注入。

  1. 全局参数过滤与转义

通过全局拦截器、工具类,统一过滤请求参数中的SQL特殊字符(单引号、分号、注释符、or/and关键字等),双层防护。

  1. 最小权限数据库账号

项目数据库账号仅分配查询、增改业务所需最小权限,禁止授予drop、alter、delete高危权限,即便发生注入,也可降低损失。

  1. 开启SQL日志监控与慢SQL拦截

监控异常SQL、批量查询、高危删除语句,及时发现并拦截注入攻击行为。

五、面试高频易错点(加分项)

  1. MyBatis注入漏洞只由${}导致,#{} 绝对安全,不存在注入风险;

  2. 网上所说的"预编译绕过注入",在常规业务场景下不存在,#{} 是企业标准防注入方案;

  3. 动态SQL标签(if、where、foreach)本身不会产生注入,风险仅来自参数拼接方式;

  4. 批量操作、模糊查询场景,优先使用#{}, 禁止拼接字符串。

六、面试高分总结金句

MyBatis中SQL注入的核心成因是**{} 直接拼接用户可控参数,导致SQL逻辑被篡改** ;核心防范思路是**全员使用#{}预编译隔离参数、严控{}使用场景、白名单兜底校验**,结合数据库最小权限原则,全方位杜绝SQL注入漏洞,保障业务数据安全。

极简速背版(面试口述)

SQL注入原理是恶意用户利用{}字符串拼接漏洞,输入特殊SQL关键字篡改原有SQL逻辑,导致数据泄露或篡改。MyBatis防范核心是普通参数一律用#{}预编译防注入,严格限制{}使用,必须用时做参数白名单校验,同时配置数据库最小权限,杜绝注入风险。

简洁版

1. 注入原理 :使用 ${} 直接拼接用户可控参数,恶意参数被解析为SQL逻辑,拼接后改变原有SQL逻辑,造成数据泄露、删除、篡改。

2. 核心区别#{} 预编译安全防注入,${} 字符串拼接存在注入风险。

3. 防范方案

① 业务参数统一使用 #{} 预编译占位符;

② 严控${}使用,仅用于固定字段、表名,禁止接收用户参数;

③ ${} 场景强制白名单参数校验;

④ 数据库账号最小权限配置,降低漏洞损失。

44. 一级缓存、二级缓存区别、生效范围、失效场景

标准答案(完整版·面试满分)

MyBatis内置两级缓存机制,一级缓存为默认开启的会话级本地缓存,二级缓存为手动开启的命名空间级全局缓存,二者在生效范围、生命周期、共享性、失效规则、脏数据风险上差异极大,是面试高频对比考点,完整细节如下:

一、一级缓存(SqlSession 会话级缓存)

1. 核心生效范围

仅限同一个 SqlSession 数据库会话内生效,属于线程级本地缓存,不同会话完全隔离、数据互不共享,仅单次请求/单次会话可复用缓存数据。

2. 开启方式

默认全程自动开启,无需任何手动配置,框架原生支持。

3. 完整失效场景(必背)

  1. 当前 SqlSession 关闭、销毁,会话结束,一级缓存立即彻底清空;

  2. 同一会话内执行 任意 insert / update / delete 增删改操作,自动清空当前会话全部一级缓存,防止脏数据;

  3. 手动调用 sqlSession.clearCache() 主动清空缓存;

  4. 两次查询的 SQL 语句、查询参数、分页排序、映射规则不一致,无法命中缓存;

  5. 事务提交后,当前会话一级缓存自动刷新清空。

4. 核心特性

无脏数据风险、安全性高、生命周期短、仅复用同会话重复查询数据,不影响全局数据一致性,生产全程默认使用。

二、二级缓存(Mapper Namespace 命名空间级缓存)

1. 核心生效范围

全局跨会话、跨请求、跨线程生效,以Mapper 接口对应的 namespace 为隔离单位,所有访问该命名空间的 SqlSession 均可共享缓存数据,属于项目级全局缓存。

2. 开启必备条件

  1. 全局配置 cacheEnabled=true(默认开启);

  2. 对应 Mapper XML 文件添加 <cache/> 标签开启当前命名空间缓存;

  3. 查询返回的实体类 必须实现 Serializable 序列化接口

3. 完整失效场景

  1. 当前 namespace 下执行任意增删改操作,自动清空该命名空间下所有二级缓存数据;

  2. 项目重启、缓存过期、手动清空对应 Mapper 缓存;

  3. 查询 SQL、入参、分页条件变更,缓存 key 不匹配,缓存失效;

  4. 事务回滚后,对应缓存数据自动失效清空。

4. 核心特性与生产坑点

✅ 优势:全局共享、大幅减少高频重复查库、降低数据库压力;

❌ 致命缺陷:多 Mapper 操作同一张数据表时会产生脏数据。例如 UserMapper、UserOrderMapper 同时操作 user 表,UserMapper 更新数据只会清空自身 namespace 缓存,不会清空 UserOrderMapper 的缓存,导致后续查询读取旧数据,实时业务极易出现数据不一致问题。

三、一级缓存 & 二级缓存 核心完整区别(面试对比满分表)

  1. 生效范围:一级缓存为会话级,二级缓存为命名空间全局级;

  2. 开启状态:一级默认开启,二级需手动配置开启;

  3. 数据共享性:一级会话隔离、不可共享,二级跨会话、跨请求全局共享;

  4. 脏数据风险:一级无脏数据、安全可靠,二级多Mapper操作同表易产生脏数据;

  5. 生命周期:一级随会话销毁而清空,二级随项目常驻、按需失效;

  6. 依赖条件:一级无任何依赖,二级需要实体序列化、手动开启标签;

  7. 适用场景:一级全场景通用,二级仅适用于读多写少、单Mapper单表、低实时性静态数据。

四、缓存查询执行优先级

查询执行顺序固定:二级缓存(全局) → 一级缓存(会话) → 数据库

先匹配全局二级缓存,未命中再匹配当前会话一级缓存,两级均未命中才查询数据库,并回填缓存。

五、面试高分总结金句

一级缓存是MyBatis基础安全缓存,会话隔离、默认开启、无脏数据,保障单次会话查询性能;二级缓存是全局共享缓存,性能优化更强但存在命名空间隔离缺陷,极易引发脏数据。生产规范为一级缓存全程启用,实时读写业务禁用二级缓存,仅静态读多写少业务酌情开启

极简速背版(面试口述)

一级缓存默认开启、会话级别、会话隔离、增删改和关会话即清空,无脏数据;二级缓存手动开启、命名空间全局级别、跨会话共享,需要实体序列化,同命名空间增删改清空缓存,多Mapper操作同表易脏数据。查询优先走二级缓存,再走一级缓存,最后查库。

简洁版

一级缓存

范围:SqlSession会话级别,默认开启,会话隔离不共享;

失效:同会话增删改、会话关闭、手动清空、查询条件变更;

特点:安全无脏数据,全场景通用。

二级缓存

范围:Mapper命名空间全局级别,跨会话共享,需手动开启+实体序列化;

要求:实体实现序列化接口,手动开启;

失效:当前namespace命名空间增删改、项目重启、缓存过期、查询条件变更;

坑点:多Mapper操作同表易产生脏数据,实时业务禁用。

查询优先级:二级缓存 → 一级缓存 → 数据库

45. 多数据源、读写分离实现思路(完整落地版·面试满分)

标准答案(完整版·面试满分+生产落地)

在MyBatis+SpringBoot项目中,多数据源 用于适配多数据库异构场景,读写分离 用于解决单库高并发读压力、提升数据库吞吐能力,二者核心基于动态数据源路由实现,是分布式、微服务、高并发项目高频架构方案,下面分别拆解完整原理、实现方案、核心代码、生产坑点。

一、多数据源实现方案(适配多数据库场景)

1. 核心业务场景

业务需要操作多个不同数据库,比如:业务主库+日志库、用户库+订单库、MySQL+Oracle混合数据源、多租户独立数据源等场景。

2. 两大实现方案

方案一:静态多数据源(分包绑定·简单稳定·中小企业首选)

核心思路:不同业务模块、不同Mapper分包,绑定独立的数据源、SqlSessionFactory,数据源固定不动态切换。

实现步骤:

① 配置多组数据源参数:在yml中配置db1、db2等多组数据库连接信息;

② 手动创建多组DataSource、SqlSessionFactory、SqlSessionTemplate;

③ 通过**@MapperScan分包扫描**,不同包路径的Mapper绑定不同数据源;

④ 业务调用不同包下的Mapper,自动操作对应数据库,全程无动态切换、无路由异常,稳定性极高。

优缺点:实现简单、零坑稳定、无事务问题;缺点是无法动态切换数据源,仅适配模块隔离的多库场景。

方案二:动态多数据源(路由切换·灵活通用·大厂主流)

核心思路:基于Spring提供的AbstractRoutingDataSource动态数据源抽象类,自定义数据源路由规则,通过ThreadLocal存储当前线程数据源标识,实现接口、方法级别的动态数据源切换。

核心实现原理:

① 预先加载所有数据源存入Map集合,设置默认数据源;

② 重写determineCurrentLookupKey()方法,读取ThreadLocal中的数据源key;

③ 根据key动态匹配对应数据源,实现运行时动态切换;

④ 基于AOP+自定义注解,在方法执行前切换数据源,执行后清空线程标识,防止线程污染。

核心关键代码(可直接落地)

  1. 自定义动态数据源路由类
java 复制代码
public class DynamicDataSource extends AbstractRoutingDataSource {
    // 线程级数据源标识存储
    private static final ThreadLocal<String> DATA_SOURCE_KEY = new ThreadLocal<>();

    @Override
    protected Object determineCurrentLookupKey() {
        // 返回当前线程指定的数据源key
        return DATA_SOURCE_KEY.get();
    }

    // 设置数据源
    public static void setDataSource(String key) { DATA_SOURCE_KEY.set(key); }
    // 清空数据源
 public static void clearDataSource() { DATA_SOURCE_KEY.remove(); }
}
  1. 自定义切换注解+AOP切面,实现方法级数据源切换

通过注解标记方法,切面拦截后自动切换对应数据源,实现无侵入动态路由。

二、读写分离完整实现方案(高并发核心架构)

1. 核心架构原理

数据库主从架构:主库(Master)负责写操作(增删改)、从库(Slave)负责读操作(查询),主库数据同步至从库,通过读写拆分分摊数据库压力,解决单库读瓶颈,大幅提升接口QPS。

2. 主流落地实现方式

方式一:应用层代码实现(轻量无中间件·常用)

核心思路:基于动态数据源路由,区分主库、从库数据源,通过AOP切面根据方法名/注解自动路由主从库。

路由规则:

① 增删改方法(insert/update/delete)强制路由主库

② 查询方法默认路由从库

③ 核心实时查询、事务内查询强制走主库,规避主从延迟。

优点:无需部署中间件、轻量化、成本低、适配中小高并发项目;缺点:需自行处理主从延迟、路由规则。

方式二:中间件实现(大厂分布式首选)

基于Sharding-JDBC、MyCat等中间件,自动实现读写分离、主从切换、负载均衡,应用层零代码侵入,支持多从库负载均衡、故障自动转移,适配大型分布式集群项目。

三、读写分离核心痛点:主从延迟解决方案(面试高频)

主从同步存在毫秒/秒级延迟,写入主库后立即查从库,会查询不到最新数据,出现数据不一致问题,生产解决方案:

  1. 强制读主库:新增、修改、删除后的即时查询、核心实时数据查询,通过注解强制路由主库;

  2. 降低延迟:开启数据库半同步复制、并行复制,缩短主从同步时差;

  3. 业务兜底:核心接口、高一致性场景禁用从库,全部走主库;

  4. 缓存兜底:高频实时数据结合Redis缓存,绕过从库查询,规避延迟问题。

四、生产核心避坑点(面试加分项)

  1. 事务失效/数据源错乱 :Spring事务基于线程绑定,开启事务后数据源固定,事务内所有读写操作只会走第一次选中的数据源,无法动态切换,因此事务方法禁止动态切换主从库

  2. 线程污染问题:动态数据源切换后必须手动清空ThreadLocal标识,避免线程复用导致数据源错乱;

  3. 从库负载均衡:多从库场景需实现轮询、随机负载均衡,避免单从库压力过大;

  4. 故障熔断:新增数据源健康检测,从库宕机自动熔断,请求自动切换至主库,避免服务雪崩。

五、面试高分总结金句

多数据源核心基于AbstractRoutingDataSource+ThreadLocal+AOP实现动态路由,分包适配简单多库场景,动态路由适配灵活切换场景;读写分离核心是主写从读,通过AOP自动区分读写路由,核心难点是解决主从延迟和事务内数据源固定问题,生产中实时业务强制读主、静态查询走从库,兼顾性能与数据一致性。

极简速背版(面试口述)

多数据源分两种实现,简单场景用Mapper分包绑定独立数据源,灵活场景用AbstractRoutingDataSource结合AOP和ThreadLocal实现动态切换;读写分离采用主库写、从库读的架构,通过AOP切面自动路由读写请求,针对主从延迟,采用实时查询强制读主库、开启半同步复制、缓存兜底的方案解决,同时规避事务内数据源无法切换的问题。

简洁版

一、多数据源实现

  1. 静态分包:多组数据源配置,不同Mapper包绑定不同数据库,稳定无坑;

  2. 动态路由:继承AbstractRoutingDataSource,ThreadLocal存储数据源标识,AOP注解实现动态切换。

二、读写分离实现

  1. 架构规则:主库负责增删改,从库负责查询,分摊读压力;

  2. 代码实现:AOP切面根据操作类型自动路由主从库;

  3. 中间件实现:Sharding-JDBC/MyCat自动读写分离、负载均衡;

三、核心问题解决

  1. 主从延迟:实时查询强制走主库、优化同步机制、缓存兜底;

  2. 事务问题:事务内禁止切换数据源,保证数据一致性;

  3. 线程安全:操作完成清空ThreadLocal,防止线程污染。

六、全套可落地核心实现代码(动态多数据源 + 读写分离)

前置说明:基于 SpringBoot2.x + MyBatis 实现,无第三方中间件,纯代码实现动态数据源、主从读写分离,可直接用于生产项目。

1. 引入核心Maven依赖

xml

XML 复制代码
<!-- SpringBoot JDBC -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- MyBatis -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!-- 数据库驱动、连接池 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.16</version>
</dependency>
<!-- AOP 切面依赖(用于路由拦截) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2. 多数据源YAML配置(主库+从库)

yaml

XML 复制代码
spring:
  # 主从多数据源配置
  datasource:
    master: # 主库(写库)
      url: jdbc:mysql://127.0.0.1:3306/db_master?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
    slave: # 从库(读库)
 url: jdbc:mysql://127.0.0.1:3306/db_slave?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
# MyBatis配置
mybatis:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true

3. 数据源常量标识类

java 复制代码
/**
 * 数据源常量
 */
public class DataSourceConstant {
    // 主库-写数据源
    public static final String MASTER = "master";
    // 从库-读数据源
    public static final String SLAVE = "slave";
}

4. 自定义动态数据源路由核心类

java 复制代码
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 动态数据源路由:支持多数据源切换、从库轮询负载均衡
 */
@Component
public class DynamicDataSource extends AbstractRoutingDataSource {

    // 线程级存储当前数据源标识
    private static final ThreadLocal<String> DATA_SOURCE_KEY = new ThreadLocal<>();
    // 从库轮询计数器(负载均衡)
    private static final AtomicInteger SLAVE_INDEX = new AtomicInteger(0);

 /**
     * 核心方法:动态获取当前数据源key
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DATA_SOURCE_KEY.get();
    }

    // 设置指定数据源
    public static void setDataSource(String key) {
        DATA_SOURCE_KEY.set(key);
    }

    // 轮询获取从库数据源(多从库负载均衡扩展点)
    public static String getSlaveDataSource() {
        return DataSourceConstant.SLAVE;
    }

    // 清空数据源标识,防止线程污染
    public static void clearDataSource() {
        DATA_SOURCE_KEY.remove();
    }
}

5. 数据源配置类(初始化主从数据源、绑定动态路由)

java 复制代码
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 多数据源配置类
 */
@Configuration
public class DataSourceConfig {

    // 初始化主库数据源
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    // 初始化从库数据源
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 组装动态数据源,设置默认主库
     */
    @Primary // 优先使用动态数据源,覆盖默认数据源
    @Bean
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 设置默认数据源:主库
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
        // 组装多数据源Map
 Map<Object, Object> dataSourceMap = new HashMap<>(2);
        dataSourceMap.put(DataSourceConstant.MASTER, masterDataSource());
        dataSourceMap.put(DataSourceConstant.SLAVE, slaveDataSource());
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        return dynamicDataSource;
    }
}

6. 自定义数据源切换注解

java 复制代码
import java.lang.annotation.*;

/**
 * 数据源切换注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DS {
    // 默认主库
    String value() default DataSourceConstant.MASTER;
}

7. AOP切面实现读写自动路由(核心落地逻辑)

java 复制代码
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;

/**
 * 读写分离切面:自动匹配读写数据源
 * 规则:增删改走主库、查询默认走从库、注解优先
 */
@Aspect
@Component
public class DataSourceAspect {

    // 匹配mapper层所有方法
    @Before("execution(* com.mapper.*.*(..))")
    public void before(JoinPoint point) {
        // 1. 获取目标方法
        Method method = getMethod(point);
        if (method == null) {
            DynamicDataSource.setDataSource(DataSourceConstant.MASTER);
            return;
        }

        // 2. 优先读取注解,手动指定数据源优先级最高
        DS ds = method.getAnnotation(DS.class);
        if (ds != null) {
            DynamicDataSource.setDataSource(ds.value());
            return;
        }

        // 3. 无注解,根据方法名自动路由
        String methodName = method.getName().toLowerCase();
        // 增删改操作强制走主库
        if (methodName.startsWith("insert") || methodName.startsWith("add")
                || methodName.startsWith("update") || methodName.startsWith("edit")
                || methodName.startsWith("delete") || methodName.startsWith("remove")) {
            DynamicDataSource.setDataSource(DataSourceConstant.MASTER);
        } else {
            // 查询默认走从库
            DynamicDataSource.setDataSource(DataSourceConstant.SLAVE);
        }
    }

    // 执行完毕清空数据源,防止线程污染
    @After("execution(* com.mapper.*.*(..))")
    public void after() {
        DynamicDataSource.clearDataSource();
    }

    // 反射获取目标方法
    private Method getMethod(JoinPoint point) {
        try {
            String methodName = point.getSignature().getName();
            Class<?>[] parameterTypes = ((org.aspectj.lang.reflect.MethodSignature) point.getSignature()).getParameterTypes();
            return point.getTarget().getClass().getMethod(methodName, parameterTypes);
        } catch (Exception e) {
            return null;
        }
    }
}

8. Mapper使用示例(极简落地)

java 复制代码
import org.apache.ibatis.annotations.Param;
import java.util.List;

public interface UserMapper {
    // 新增:自动走主库
    int insertUser(@Param("user") User user);

 // 查询:自动走从库
    List<User> listUser();

    // 实时查询:手动注解强制走主库,解决主从延迟
 @DS(DataSourceConstant.MASTER)
    User getUserById(@Param("id") Long id);
}

七、生产核心适配扩展(面试加分)

  1. 多从库负载均衡:可扩展Slave数据源List集合,通过轮询、随机算法实现多从库分发,避免单从库压力过大;

  2. 数据源健康检测:新增定时任务检测从库连接状态,从库宕机自动熔断,强制路由主库;

  3. 事务兼容处理 :AOP切面增加事务判断,@Transactional事务内统一固定主库,禁止动态切换,规避事务失效;

  4. 主从延迟兜底:新增数据更新短时间缓存,写入后N秒内查询强制走主库,彻底解决延迟数据不一致。

八、代码执行流程总结

项目启动 → 初始化主从数据源 → 组装动态路由数据源 → Mapper方法调用 → AOP切面拦截 → 注解优先/方法名自动匹配读写数据源 → 切换线程数据源标识 → 执行SQL → 清空数据源、防止线程污染。

相关推荐
小江的记录本2 小时前
【JVM虚拟机】垃圾回收GC:垃圾回收算法:标记-清除、标记-复制、标记-整理、分代收集(附《思维导图》+《面试高频考点清单》)
java·jvm·后端·python·算法·安全·面试
小江的记录本3 小时前
【JVM虚拟机】垃圾回收GC:垃圾收集器:G1:Region分区、Mixed GC、回收流程、适用场景(高频)(附《思维导图》+《面试高频考点清单》)
java·jvm·后端·python·spring·spring cloud·面试
杨运交3 小时前
[022][数据模块]基于雪花算法的 MyBatis-Plus 主键生成器设计与实现
mybatis
摇滚侠3 小时前
Java 零基础全套教程,反射机制,笔记 187-188
java·开发语言·笔记
超梦dasgg4 小时前
Java 生产环境第三方对接安全保障方案
java·开发语言·安全
日月云棠4 小时前
9 Double 与 Float —— IEEE 754 浮点数在 Java 中的实现
java·后端
Refrain_zc4 小时前
Android 二维码登录轮询机制:从扫码到登录的完整客户端实现
java
z落落4 小时前
C#参数区别
java·算法·c#
日月云棠4 小时前
5 StringBuffer —— 线程安全的可变字符串
java·后端