春招准备之MyBatis框架篇
本系列内容直接以八股文,即问题的形式总结,面试所需内容
1、什么是MyBatis?
MyBatis 是一个半自动的持久层框架,它不屏蔽 SQL,而是让开发者100%掌控 SQL,同时自动化处理 "参数映射 + 结果集映射 + 资源管理" 等重复性工作
(1)半 ORM 框架(核心本质)
半ORM 的含义是:
不是完全自动:需要手写 SQL(优点:性能可控、SQL 调优灵活)
不是纯 JDBC:自动处理 Connection、PreparedStatement、ResultSet 的创建和关闭
"半"的优势 :结合了 JDBC 的灵活性 和 ORM 的便捷性,避免了 Hibernate 全自动化带来的性能黑箱和 SQL 不可控问题
(2)两种配置方式(XML vs 注解)
XML:<select id="findUser" resultType="User">SELECT * FROM user WHERE id = #{id}</select>
注解:@Select("SELECT * FROM user WHERE id = #{id}")
重要原则 : 复杂 SQL 一定要用 XML ,注解的可读性和维护性极差
(3)执行流程
// UserMapper.java
User user = userMapper.findUser(1);
// MyBatis 背后自动完成:
1. 加载 UserMapper.xml 中的 <select id="findUser">...
2. 创建 PreparedStatement,将 #{id} 替换为 ?,并设置参数 1
3. 执行 SQL,获取 ResultSet
4. 自动映射:rs.getInt("id") → user.setId(),rs.getString("name") → user.setName()
5. 关闭连接,返回 User 对象
关键自动点 : 参数映射 (#{id} → ?)和 结果映射 **(列名 → 属性名)是 MyBatis 的核心价值
(MyBatis = SQL 写手的最爱 + JDBC 的终结者 + 半自动 ORM 的标杆)
2、说说MyBatis的优点和缺点?
优点:
(1)基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重
(2)与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连 接
(3)很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)
(4)能够与Spring很好的集成
(5)提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象 关系组件维护
缺点:
(1)SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求
(2)SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库
3、#{}和${}的区别是什么?
#{} 是"占位符" :SQL 预编译,防注入
${} 是"字符串拼接" :直接替换,有注入风险
#{值} 防注入,99% 用它;${名} 只用于表名/列名/排序,绝不碰用户输入!
#{} 是预编译参数占位符,能自动防 SQL 注入,适用于所有参数值传递;
${} 是字符串直接替换,存在 SQL 注入风险,仅限用于动态表名、列名或排序字段等无法预编译的场景。 因此,实际开发中应 99% 使用 #{},仅在确保安全且必要时才使用 ${},绝不可将其用于用户输入的参数赋值
(#{值} 防注入,99% 用它;${名} 只用于表名/列名/排序,绝不碰用户输入!)
4、当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
第1种: 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致
第2种: 通过来映射字段名和实体类属性名的一一对应的关系
用id属性来映射主键字段
用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性
5、Mybatis是如何进行分页的?分页插件的原理是什么?
1、RowBounds分页:基于ResultSet的内存分页,非物理分页,数据量大时性能差
2、物理分页实现方式:
-
手动在SQL中拼接分页参数(如MySQL的LIMIT)
-
使用分页插件:通过实现MyBatis插件接口,在拦截方法中改写SQL,根据方言添加物理分页语句和参数
6、Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
MyBatis 通过反射将 SQL 结果封装为目标对象
映射形式有两种:
1) 使用 resultMap 标签,逐一定义列名与属性名的映射关系
2) 使用 SQL 别名使列名直接匹配属性名
建立映射后,MyBatis 通过反射创建对象并赋值,未映射的属性无法赋值
7、Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?
除基本 CRUD 标签外,MyBatis XML 映射文件还包括:
动态 SQL 标签(9个):if, choose, when, otherwise, trim, where, set, foreach, bind
辅助标签:
<sql>:定义可复用的 SQL 片段
<include>:引入 SQL 片段,可配合<property> 传递参数
<selectKey>:获取主键值(适用于不支持自增的数据库)
<resultMap>:自定义结果集映射 <cache> / <cache-ref>:二级缓存配置与引用
<parameterMap>:参数映射(已废弃)
9、MyBatis实现一对一有几种方式?具体怎么操作的?
联合查询:是几个表联合查询,通过 JOIN 只执行一次 SQL,在 resultMap 的 association 节点中直接配置一对一类的映射关系
嵌套查询:先查主表,再根据主表结果的外键 id,通过 association 的 select 属性指定从表查询语句,分两次查询
10、Mybatis是否支持延迟加载?它的实现原理是什么?
MyBatis 支持 association(一对一)和 collection(一对多)对象的延迟加载
配置 :在 MyBatis 配置文件中设置 lazyLoadingEnabled=true 开启。
原理 :使用 CGLIB 创建目标对象代理,当调用目标方法(如 a.getB().getName())时,拦截器 invoke() 发现 a.getB() 为 null,则单独执行预存的 SQL 查询关联对象 B,调用 a.setB(b) 赋值后再完成方法调用。该原理同样适用于 Hibernate 等框架
不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的
11、说说Mybatis的缓存机制?
整体架构:MyBatis 提供两级缓存,一级缓存基于 SqlSession,二级缓存基于 namespace 全局共享

一级缓存(LocalCache):
在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的 SQL,MyBatis 提供了一级缓存的方案优化这部分场景,如果是相同的 SQL 语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。每个 SqlSession 中持有了 Executor,每个 Executor 中有一个 LocalCache。当用户发起查询时, MyBatis 根据当前执行的语句生成MappedStatement,在 Local Cache 进行查询,如果缓存命中 的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入 Local Cache,最后返回结果给用户

生命周期:一级缓存的生命周期和 SqlSession 一致,会话结束即清空
实现机制:Executor 内置 HashMap 缓存,查询时优先从缓存获取,未命中则查询数据库并写入缓存
局限性:无容量限制,功能简单;多 SqlSession 或分布式环境下,写操作会导致脏数据,建议设置为 Statement 级别
二级缓存:
在一级缓存中,其最大的共享范围就是一个 SqlSession 内部,如果多个 SqlSession 之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用 CachingExecutor 装饰 Executor,进入一级缓存的查询流程前,先在 CachingExecutor 进行二级缓存的查询

实现机制:通过 CachingExecutor 装饰 Executor,在二级缓存未命中时才查询一级缓存和数据库
特点:同一 namespace 下所有 SqlSession 共享,粒度更细,支持 Cache 接口组合,可控性更强
缺陷:多表查询易产生脏数据,使用条件苛刻;分布式环境下默认本地实现会读取脏数据
当开启缓存后,数据的查询执行的流程为: 二级缓存 -> 一级缓存 -> 数据库
1、MyBatis 的二级缓存相对于一级缓存,实现了 SqlSession 之间缓存数据的共享,同时粒度 更加细,能够到 namespace 级别,通过 Cache 接口实现类不同的组合,对 Cache 的可控性也更强
2、MyBatis 在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件 比较苛刻
3、在分布式环境下,由于默认的 MyBatis Cache 实现都是基于本地的,分布式环境下必然会出现 读取到脏数据,需要使用集中式缓存将 MyBatis 的 Cache 接口实现,有一定的开发成本,直 接使用 Redis、Memcached 等分布式缓存可能成本更低,安全性也更高
12、JDBC 编程有哪些步骤?
1、装载相应的数据库的 JDBC 驱动并进行初始化:
Class.forName("com.mysql.jdbc.Driver");
2、建立 JDBC 和数据库之间的 Connection 连接:
3、创建 Statement 或者 PreparedStatement 接口,执行 SQL 语句
4、处理和显示结果
5、释放资源
13、MyBatis 中见过什么设计模式?
建造者模式:SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder
单例模式:ErrorContext、LogFactory
代理模式:MapperProxy、ConnectionLogger(JDK 动态代理),executor.loader 包(CGLIB/javassist 延迟加载)
组合模式:SqlNode 及其子类(如 ChooseSqlNode)
模板方法模式:BaseExecutor、SimpleExecutor、BaseTypeHandler
适配器模式:Log 接口及对接 JDBC 日志的适配
装饰者模式:cache.decorators 子包中各类缓存装饰器
迭代器模式:PropertyTokenizer
工厂模式:SqlSessionFactory、ObjectFactory、MapperProxyFactory
14、MyBatis 中比如 UserMapper.java 是接口,为什么没有实现类还能调用?
MyBatis 通过 JDK 动态代理 为 Mapper 接口生成代理对象,调用接口方法时实际执行 MapperProxy.invoke(),该方法解析方法签名,定位对应 SQL 并执行,最终返回结果
