MyBatis面试题
多表查询的标签
association
collocation
# MyBatis执行流程?
MyBatis的执行流程如下:
- 读取MyBatis全局配置文件
mybatis-config.xml
和配置文件。 - 构造会话工厂
SqlSessionFactory
。 - 会话工厂创建
SqlSession
对象。 - 创建操作数据库的接口,
Executor
执行器。 - 获取Mapper代理对象
Executor
执行方法中的MappedStatement
类型的参数。- 输入参数映射,从Java类型->JDBC类型。
- 执行SQL
- 输出结果映射,从JDBC类型->Java类型。
# Mybatis是否支持延迟加载?
MyBatis支持延迟加载,即在需要用到数据时才加载。主要用于处理 关联数据,即那些不是主查询直接返回,而是通过额外 SQL 查询获取的数据。可以通过配置文件中的lazyLoadingEnabled
配置启用或禁用延迟加载。
# 延迟加载的底层原理知道吗?
延迟加载的底层原理主要使用CGLIB动态代理实现:
- 当你查询数据时,Mybatis会使用CGLIB创建目标对象的代理对象,这个代理对象的关联属性(需要关联其他表的字段)是空的。
- 调用目标方法时,代理对象会拦截这个方法,如果发现查询的目标属性是null值,则先执行SQL查询。
- 获取数据后,设置属性值再继续执行目标方法。
# Mybatis的一级、二级缓存用过吗?
一级缓存:基于PerpetualCache的HashMap本地缓存,其存储作用域为Session,当Session进行flush或close之后,该Session中的所有Cache就将清空,默认打开一级缓存 二级缓存需要单独开启,作用域为Namespace或mapper,不是依赖于SQL session,默认也是采用PerpetualCache的HashMap存储。通过在核心配置文件和mapper映射文件配置来开启
# Mybatis的缓存
MyBatis设计了二级缓存机制来提升数据检索效率,减少数据库查询压力。
一级缓存是SqlSession级别的本地缓存,用于避免同一会话中重复查询相同数据,MyBatis会将查询结果缓存在SqlSession中,后续相同查询可直接从缓存读取。 而二级缓存是跨SqlSession的全局缓存。当任意SqlSession查询数据后,结果会存入二级缓存,其他查询可直接从中获取数据。
实现原理:
一级缓存:每个SqlSession持有Executor,其中包含LocalCache对象。用户发起查询时先检查LocalCache,命中则直接返回,未命中才查库并写入缓存。所以一级缓存的生命周期随SqlSession,需注意:因为一级缓存,在分布式环境下可能引发脏读问题。
二级缓存:通过CachingExecutor装饰器实现,在原Executor外增加缓存层。通过配置文件开启后,查询流程变为:二级缓存 → 一级缓存 → 数据库。它采用装饰器模式,在原有执行逻辑上添加缓存功能。
- 实现SqlSession间数据共享
- 缓存粒度可控制到namespace级别
- 通过Cache接口支持多种缓存实现(如Ehcache、Redis等)
- 提供更高的灵活性和可控性

# Mybatis的二级缓存什么时候会清理缓存中的数据?
当作用域(一级缓存Session/二级缓存Namespaces)进行了新增、修改、删除操作后,默认该作用域下所有select中的缓存将被清空。
# MyBatis是如何进行分页的
分页是开发中的基础功能,MyBatis支持的分页方式可分为两大类:
- 逻辑分页(内存分页)先查询所有符合条件的数据到内存中,再通过代码筛选出分页数据。
- 使用MyBatis的
RowBounds
对象,一次性加载,所有符合查询条件的目标数据在内存里,通过设置offset
和limit
参数实现内存分页。 在数据量比较大的情况下,JDBC驱动会做一些优化,不会把所有的结果,一次性存储在result set里面,而是先只加载一部分数据,然后再根据需求去数据库里面滚动,去加载对应后续的数据,这种方式不适合数据量较大的一个场景,而且有可能会频繁的访问数据库,造成比较大的压力,
- 使用MyBatis的
- 物理分页(数据库分页),直接通过数据库的分页语法(如MySQL的
LIMIT
)实现。- 在Mapper XML中直接编写带分页参数的SQL(如
LIMIT #{offset}, #{pageSize}
),灵活性高但需重复编码。 - interceptor拦截器
拦截器是MyBatis里提供了一种针对于不同生命周期的拦截器,比如拦截执行器方法,拦截参数的处理,拦截结果集的处理,拦截SQL语法构建的处理,
我们可以通过拦截需要分页的select语句,然后在这个select语句里面去动态拼接分页的关键字,从而去实现分页查询,这种方式的好处,统一处理分页逻辑,避免重复代码。
PageHelper
、MyBatis-Plus
等均基于此实现。
- 在Mapper XML中直接编写带分页参数的SQL(如
总结:
- 直接在Select语句上增加数据库提供的分页关键字,让后在应用程序里面传递当前页以及每页展示的条数
- 使用MyBatis提供的RowBounds对象,实现内存级别的分页,可以执行查询后的结果集进行截取,但是这种方法不适合大数据集,因为它会加载所有符合条件的数据到内存中在进行截取,不过JDBC会进行一些优化,不会一次性加载所有数据,会加载一部分数据,再根据需要滚动加载其他数据
- 基于MyBatis里面的Interceptor拦截器,在Select语句只需之前动态的拼接分页关键字,一些第三方插件就是使用的这种方法,如PageHelper
- 可以优先使用数据库层面的分页,在一些复杂分页场景考虑使用PageHelper这样的插件
# MyBatis里#{}和${}的区别是什么
MyBatis提供了#{}
和${}
两种占位符来实现动态SQL,在执行操作之前,MyBatis会对这两个占位符进行动态的解析,
#{} 会被MyBatis替换为PreparedStatement预编译SQL语句中的问号占位符(?),然后将参数传递给PreparedStatement对象,而且会对特殊字符进行转义。这种方式可以有效防止SQL注入攻击。MyBatis还会根据传入参数的类型自动转换成相应的数据库类型
而${} 是直接将传入的参数值拼接到SQL语句中,MyBatis不会对它进行任何特殊处理。这意味着它不具备任何防护措施来阻止SQL注入攻击。通常用于无法使用预编译SQL的占位符,如动态表名、列名等非用户输入的场景。
所以#{}和${}最大的区别在于,前者是动态参数,后者是占位符,在实际应用里面呢,尽可能的应该去使用#{}占位符