一、基础概念与核心原理
1. 什么是 MyBatis?它与其他 ORM 框架(如 Hibernate)有什么区别?
MyBatis 是一款基于 Java 的半自动化 ORM(对象关系映射)框架,它通过 XML 或注解的方式将 SQL 语句与 Java 方法关联,实现对象与关系数据库的映射。与 Hibernate 等全自动 ORM 框架相比,主要区别在于:
- SQL 控制粒度:MyBatis 允许开发者直接编写 SQL,灵活控制查询逻辑;Hibernate 通过 HQL 或 Criteria API 生成 SQL,屏蔽了底层 SQL 细节。
- 学习成本:MyBatis 学习曲线平缓,适合 SQL 优化需求高的场景;Hibernate 初期学习成本高,但能快速开发简单 CRUD 功能。
- 性能优化:MyBatis 可手动优化 SQL,适合复杂查询场景;Hibernate 在复杂查询时性能优化较困难。
- 适用场景:MyBatis 适合需求多变、SQL 优化要求高的项目(如电商核心系统);Hibernate 适合快速开发、SQL 相对简单的项目。
面试点睛:重点强调 MyBatis 的 “半自动化” 特性 —— 既保留了 SQL 的灵活性,又提供了 ORM 的便利,这是它在企业级应用中广泛使用的核心原因。
2. MyBatis 的核心组件有哪些?各自的作用是什么?
MyBatis 的核心组件包括:
1、SqlSessionFactory:会话工厂,负责创建 SqlSession,生命周期为应用级别(单例)。通常由 SqlSessionFactoryBuilder 根据配置文件构建。
2、SqlSession:会话对象,代表与数据库的一次交互,包含了执行 SQL 的所有方法。生命周期为一次请求或事务,线程不安全,需及时关闭。
3、Executor:执行器,SqlSession 的底层实现,负责 SQL 的执行和缓存管理。有三种类型:
SimpleExecutor:默认执行器,每次执行 SQL 都会创建新的 Statement
ReuseExecutor:复用 Statement
BatchExecutor:批量执行 SQL
4、MappedStatement:映射语句,封装了 SQL 语句、参数类型、结果类型等信息,是 MyBatis 对 SQL 的抽象表示。
5、StatementHandler:处理 JDBC 的 Statement 操作,负责参数设置、SQL 执行和结果集处理。
6、ResultHandler:结果处理器,用于自定义结果集的处理逻辑。
7、TypeHandler:类型处理器,负责 Java 类型与 JDBC 类型之间的转换。
面试点睛:这些组件的协作流程是面试重点 ——SqlSessionFactory创建SqlSession,SqlSession通过Executor执行MappedStatement,最终由StatementHandler与数据库交互。
3. MyBatis 的工作原理是什么?请简述其执行流程。
MyBatis 的工作流程可分为初始化和执行两个阶段:
初始化阶段:
1、加载 MyBatis 全局配置文件(mybatis-config.xml)和 Mapper 映射文件。
2、通过 SqlSessionFactoryBuilder 解析配置文件,生成 Configuration 对象(包含所有配置信息)。
3、由 Configuration 创建 SqlSessionFactory 实例。
执行阶段:
1、调用 SqlSessionFactory 的 openSession () 方法创建 SqlSession。
2、SqlSession 通过 Mapper 接口的全限定名 + 方法名找到对应的 MappedStatement。
3、Executor 根据 MappedStatement 的配置,通过 StatementHandler 执行 SQL:
ParameterHandler 处理参数绑定
执行 SQL 语句
ResultSetHandler 处理结果集映射
4、事务提交或回滚。
5、关闭 SqlSession。
流程图解:
配置文件 → SqlSessionFactoryBuilder → SqlSessionFactory → SqlSession → Executor → MappedStatement → 数据库
面试点睛:重点说明 Configuration 的核心作用(存储所有配置)和 MappedStatement 的作用(封装 SQL 信息),体现对框架设计的理解。
二、配置与映射
4. MyBatis 的核心配置文件(mybatis-config.xml)包含哪些主要配置项?
核心配置文件的主要配置项按顺序如下:
properties:加载外部属性文件(如数据库连接信息)。
<properties resource="db.properties"/>
settings:全局配置参数,如缓存开关、延迟加载、日志实现等。
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
typeAliases:为 Java 类型设置别名,简化 Mapper 文件中的类型引用。
<typeHandlers>
<typeHandler handler="com.example.handler.MyTypeHandler"/>
</typeHandlers>
plugins:配置 MyBatis 插件(如分页插件、性能监控插件)。
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>
environments:配置数据库环境,支持多环境切换。
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<!-- 其他属性 -->
</dataSource>
</environment>
</environments>
mappers:注册 Mapper 映射文件或接口。
<mappers>
<mapper resource="com/example/mapper/UserMapper.xml"/>
<package name="com.example.mapper"/> <!-- 批量注册 -->
</mappers>
面试点睛:需说明配置项的顺序是固定的(MyBatis 解析时严格按顺序处理),且重点掌握 settings 和 mappers 的配置。
5. 如何理解 MyBatis 中的 Mapper 接口?它为什么不需要实现类?
MyBatis 的 Mapper 接口(也称映射器接口)是 SQL 操作的抽象定义,它与 Mapper.xml 文件或注解中的 SQL 语句绑定,无需手动实现。其底层原理是:
1、动态代理:MyBatis 在运行时通过 JDK 动态代理为 Mapper 接口生成代理对象(MapperProxy)。
2、方法映射:代理对象将接口方法调用转换为对SqlSession方法的调用,通过 “接口全限定名 + 方法名” 匹配对应的 MappedStatement。
3、参数传递:代理对象负责将接口方法的参数转换为 SQL 所需的参数类型。
关键源码片段:
// MapperProxy的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 转换为MapperMethod执行
MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
面试点睛:核心是动态代理机制,这也是 MyBatis"接口编程" 思想的体现,简化了数据访问层的代码。
6. resultMap 和 resultType 的区别是什么?何时使用 resultMap?
两者都是用于指定 SQL 查询结果的映射方式,主要区别如下:

resultMap 示例(解决字段名与属性名不一致问题):
<resultMap id="userMap" type="User">
<id column="user_id" property="id"/> <!-- 主键映射 -->
<result column="user_name" property="username"/> <!-- 普通字段映射 -->
<result column="create_time" property="createTime"/> <!-- 日期类型映射 -->
</resultMap>
<select id="selectUser" resultMap="userMap">
SELECT user_id, user_name, create_time FROM t_user WHERE id = #{id}
</select>
使用建议:
1、简单查询用 resultType,代码更简洁
2、复杂映射(如多表关联、字段名不一致、集合映射)必须用 resultMap
面试点睛:resultMap 是 MyBatis 映射能力的核心,能解决 resultType 无法处理的复杂场景,体现对 MyBatis 高级特性的掌握。
7. 动态 SQL 有哪些标签?请举例说明其用法。
MyBatis 的动态 SQL 用于根据条件动态生成 SQL 语句,核心标签包括:
if:条件判断,满足条件则包含标签内的 SQL。
<select id="selectUser" resultType="User">
SELECT * FROM user
WHERE 1=1
<if test="username != null">AND username LIKE CONCAT('%', #{username}, '%')</if>
<if test="status != null">AND status = #{status}</if>
</select>
where:替代 WHERE 关键字,自动处理 AND/OR 前缀。
<select id="selectUser" resultType="User">
SELECT * FROM user
<where>
<if test="username != null">AND username LIKE CONCAT('%', #{username}, '%')</if>
<if test="status != null">AND status = #{status}</if>
</where>
</select>
choose/when/otherwise:多条件分支判断(类似 switch-case)。
<select id="selectUser" resultType="User">
SELECT * FROM user
<where>
<choose>
<when test="id != null">AND id = #{id}</when>
<when test="username != null">AND username = #{username}</when>
<otherwise>AND status = 1</otherwise>
</choose>
</where>
</select>
foreach:遍历集合,常用于 IN 条件或批量操作。
<select id="selectByIds" resultType="User">
SELECT * FROM user WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
set:用于 UPDATE 语句,自动处理逗号。
<update id="updateUser">
UPDATE user
<set>
<if test="username != null">username = #{username},</if>
<if test="email != null">email = #{email},</if>
</set>
WHERE id = #{id}
</update>
trim:自定义字符串截取规则,可替代 where/set。
<trim prefix="WHERE" prefixOverrides="AND|OR">
<!-- 内容 -->
</trim>
面试点睛:动态 SQL 是 MyBatis 的强大特性,能避免手动拼接 SQL 的风险,需重点掌握 if、where、foreach 的用法及场景。
三、SQL 执行与事务
8. MyBatis 的 SqlSession 有哪些常用方法?它是线程安全的吗?
SqlSession 是 MyBatis 与数据库交互的核心接口,常用方法包括:
查询方法:
<T> T selectOne(String statement, Object parameter):查询单个结果
List<T> selectList(String statement, Object parameter):查询集合
Map<K, V> selectMap(String statement, Object parameter, String mapKey):查询结果映射为 Map
插入方法:
int insert(String statement, Object parameter):执行插入,返回影响行数
更新方法:
int update(String statement, Object parameter):执行更新,返回影响行数
删除方法:
int delete(String statement, Object parameter):执行删除,返回影响行数
事务方法:
void commit():提交事务
void rollback():回滚事务
获取 Mapper:
<T> T getMapper(Class<T> type):获取 Mapper 接口代理对象
线程安全性:
- SqlSession不是线程安全的,其设计为一次请求或一个事务的生命周期。原因是:
- SqlSession 内部持有 Connection 对象,而 Connection 是非线程安全的
- 多个线程共享 SqlSession 会导致事务管理混乱和数据不一致
最佳实践:
- 在 Spring 环境中,通过@Autowired注入的 Mapper 接口由 Spring 管理,无需手动处理 SqlSession
- 非 Spring 环境中,应在方法内部创建 SqlSession,使用后立即关闭(try-with-resources)
面试点睛:线程安全性是高频考点,需明确说明 SqlSession 不可共享,并解释原因。
9. MyBatis 的 Executor 有哪些类型?它们的区别是什么?
Executor 是 MyBatis 的核心执行器,负责 SQL 执行和缓存管理,主要有三种类型:
SimpleExecutor:
默认执行器,每次执行 SQL 都会创建新的 Statement(PreparedStatement/Statement)
执行后关闭 Statement,适用于大多数场景
优点:简单直观;缺点:频繁创建和关闭 Statement,性能略低
ReuseExecutor:
复用 Statement,根据 SQL 语句的 hash 值缓存 Statement
同一 SQL 语句重复执行时,直接复用已创建的 Statement
优点:减少 Statement 创建开销;缺点:缓存 Statement 会占用一定内存
BatchExecutor:
批量执行 SQL,将多个 INSERT/UPDATE/DELETE 操作缓存,统一提交
调用SqlSession.flushStatements()或commit()时执行批量操作
优点:大幅提升批量操作性能;缺点:只适用于批量写入场景
配置方式:
通过全局配置指定默认执行器:
<settings>
<setting name="defaultExecutorType" value="SIMPLE"/> <!-- 默认为SIMPLE -->
</settings>
或在创建 SqlSession 时指定:
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
适用场景:
- 普通查询:SimpleExecutor
- 重复执行相同 SQL:ReuseExecutor
- 批量插入 / 更新:BatchExecutor
面试点睛:BatchExecutor 的使用场景和原理是重点,体现对性能优化的理解。
10. MyBatis 如何处理事务?它与 Spring 事务如何整合?
MyBatis 自身的事务管理基于 JDBC 的事务机制,核心实现如下:
事务管理类型:
- JDBC:依赖数据库的事务支持,通过 Connection 的 commit/rollback 实现
- MANAGED:将事务管理交给容器(如 Spring),MyBatis 不参与事务控制
默认行为:
- SqlSession 默认开启事务(autoCommit=false)
- 需手动调用 commit () 提交事务,或 rollback () 回滚事务
- 关闭 SqlSession 时若未提交,会自动回滚
与 Spring 事务整合:
企业级应用中通常使用 Spring 的声明式事务管理,整合步骤:
配置数据源:使用 Spring 管理的数据源,确保 MyBatis 与 Spring 共享同一 Connection
@Bean
public DataSource dataSource() {
// 配置数据源(如HikariCP)
}
配置 SqlSessionFactory:使用 Spring 提供的SqlSessionFactoryBean,注入数据源
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
// 其他配置(如Mapper位置)
return factory.getObject();
}
配置事务管理器:使用DataSourceTransactionManager
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
启用声明式事务:通过@EnableTransactionManagement注解
@Configuration
@EnableTransactionManagement
public class AppConfig { ... }
使用@Transactional注解:在 Service 方法上声明事务属性
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
// 业务逻辑
}
整合原理:
- Spring 通过 AOP 为标注@Transactional的方法创建代理,在方法执行前开启事务,执行后根据是否异常决定提交或回滚,确保 MyBatis 的 SqlSession 与 Spring 事务使用同一 Connection。
面试点睛:重点说明整合的核心是共享数据源和 Connection,以及 Spring 事务管理器如何接管 MyBatis 的事务控制。
四、缓存机制
11. MyBatis 的一级缓存和二级缓存有什么区别?如何配置和使用?
MyBatis 提供两级缓存机制,用于减少数据库访问,提升性能:

一级缓存示例:
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
// 第一次查询:从数据库获取
User user1 = mapper.selectById(1);
// 第二次查询:命中一级缓存
User user2 = mapper.selectById(1);
System.out.println(user1 == user2); // true(同一对象)
}
二级缓存配置与使用:
- 全局开启二级缓存:
<settings>
<setting name="cacheEnabled" value="true"/> <!-- 默认true,可省略 -->
</settings>
- 在 Mapper.xml 中配置缓存:
<mapper namespace="com.example.mapper.UserMapper">
<!-- 开启二级缓存 -->
<cache
eviction="LRU" <!-- 淘汰策略:LRU(最近最少使用) -->
flushInterval="60000" <!-- 自动刷新时间(毫秒) -->
size="1024" <!-- 最大缓存对象数 -->
readOnly="false"/> <!-- 是否只读 -->
<!-- 配置SQL使用缓存 -->
<select id="selectById" resultType="User" useCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
<!-- 写操作刷新缓存 -->
<update id="updateById" flushCache="true">
UPDATE user SET username = #{username} WHERE id = #{id}
</update>
</mapper>
- 实体类实现序列化:
public class User implements Serializable { ... }
使用建议:
- 一级缓存无需手动配置,注意 SqlSession 的生命周期即可
- 二级缓存适用于查询频繁、更新较少的数据(如字典表)
- 关联查询多的表不建议使用二级缓存,可能导致数据不一致
面试点睛:需明确两级缓存的作用范围和失效机制,以及二级缓存的适用场景限制。
12. 如何整合 Redis 作为 MyBatis 的二级缓存?
MyBatis 默认的二级缓存基于内存,不适合分布式环境,整合 Redis 作为二级缓存的步骤如下:
引入依赖:
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0-beta2</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
配置 Redis 缓存:
在src/main/resources下创建redis.properties:
redis.host=localhost
redis.port=6379
redis.timeout=2000
redis.password=
redis.database=0
redis.keyPrefix=mybatis:cache:
redis.expire=3600 # 缓存过期时间(秒)
在 Mapper 中使用 Redis 缓存:
<mapper namespace="com.example.mapper.DictMapper">
<!-- 指定使用Redis缓存 -->
<cache type="org.mybatis.caches.redis.RedisCache"/>
<!-- 查询方法使用缓存 -->
<select id="selectByType" resultType="Dict" useCache="true">
SELECT * FROM dict WHERE type = #{type}
</select>
</mapper>
自定义 Redis 缓存(可选):
如需自定义序列化方式或缓存逻辑,可继承RedisCache:
public class CustomRedisCache extends RedisCache {
public CustomRedisCache(String id) {
super(id);
}
@Override
public void putObject(Object key, Object value) {
// 自定义存入逻辑(如使用JSON序列化)
super.putObject(key, value);
}
@Override
public Object getObject(Object key) {
// 自定义获取逻辑
return super.getObject(key);
}
}
并在 Mapper 中引用:
<cache type="com.example.cache.CustomRedisCache"/>
整合原理:
MyBatis 的缓存接口Cache是扩展点,RedisCache实现了该接口,将缓存数据存储到 Redis 而非内存,实现分布式环境下的缓存共享。
面试点睛:重点说明 MyBatis 缓存的扩展机制(通过实现 Cache 接口),以及分布式缓存的必要性。
五、高级特性与源码分析
13. MyBatis 的插件机制是什么?如何实现一个自定义插件(如分页插件)?
MyBatis 的插件机制基于拦截器模式,允许在 SQL 执行过程中插入自定义逻辑,可拦截的四大核心对象包括:
- Executor:执行器(update/query/flushStatements/commit/rollback 等方法)
- ParameterHandler:参数处理器(getParameterObject/setParameters 方法)
- ResultSetHandler:结果集处理器(handleResultSets/handleOutputParameters 方法)
- StatementHandler:语句处理器(prepare/parameterize/batch/update/query 方法)
实现自定义分页插件的步骤:
实现 Interceptor 接口:
@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 {
// 1. 获取StatementHandler
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
// 2. 获取当前SQL信息
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
String sql = (String) metaObject.getValue("delegate.boundSql.sql");
// 3. 判断是否需要分页(假设参数中包含Page对象)
Object parameter = statementHandler.getParameterHandler().getParameterObject();
if (parameter instanceof Page) {
Page<?> page = (Page<?>) parameter;
// 4. 重写SQL,添加分页条件(以MySQL为例)
String pageSql = sql + " LIMIT " + page.getOffset() + ", " + page.getPageSize();
metaObject.setValue("delegate.boundSql.sql", pageSql);
}
// 5. 执行原方法
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
// 生成代理对象
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 读取插件配置参数
}
}
配置插件:
<plugins>
<plugin interceptor="com.example.plugin.PageInterceptor">
<!-- 可选配置参数 -->
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
使用分页插件:
Page<User> page = new Page<>(1, 10); // 第1页,每页10条
List<User> users = userMapper.selectByPage(page);
long total = page.getTotal(); // 总条数(需额外查询count)
插件执行原理:
MyBatis 通过 JDK 动态代理为被拦截对象生成代理,当调用被拦截方法时,会先执行插件的intercept方法,再通过invocation.proceed()执行原方法。
面试点睛:需说明插件可拦截的对象和方法,以及拦截器的实现要点(@Intercepts 注解、Plugin.wrap 方法)。
14. MyBatis 的延迟加载(懒加载)原理是什么?如何配置?
延迟加载是 MyBatis 的关联查询优化机制,指在查询主对象时不立即加载关联对象,而是在真正使用关联对象时才执行查询,减少不必要的数据库访问。
实现原理:
MyBatis 通过 CGLIB 或 JDK 动态代理为关联对象创建代理对象,当调用关联对象的 getter 方法时,触发代理逻辑,执行关联查询 SQL。
配置方式:
全局开启懒加载:
<settings>
<setting name="lazyLoadingEnabled" value="true"/> <!-- 开启懒加载 -->
<setting name="aggressiveLazyLoading" value="false"/> <!-- 关闭积极加载(按需加载) -->
</settings>
在 resultMap 中配置关联查询:
<!-- 订单结果映射 -->
<resultMap id="orderMap" type="Order">
<id column="id" property="id"/>
<result column="order_no" property="orderNo"/>
<!-- 懒加载订单项(一对多) -->
<collection
property="items"
ofType="OrderItem"
select="com.example.mapper.OrderItemMapper.selectByOrderId"
column="id"
fetchType="lazy"/> <!-- 显式指定懒加载 -->
</resultMap>
<select id="selectOrder" resultMap="orderMap">
SELECT id, order_no FROM `order` WHERE id = #{id}
</select>
订单项查询:
<mapper namespace="com.example.mapper.OrderItemMapper">
<select id="selectByOrderId" resultType="OrderItem">
SELECT * FROM order_item WHERE order_id = #{orderId}
</select>
</mapper>
使用效果:
Order order = orderMapper.selectOrder(1L);
// 此时未查询订单项,只查询了订单主表
System.out.println(order.getOrderNo()); // 不触发关联查询
List<OrderItem> items = order.getItems();
// 调用getItems()时,触发懒加载,执行selectByOrderId查询
items.forEach(System.out::println);
注意事项:
- 懒加载仅适用于关联查询(association/collection)
- 必须在 SqlSession 生命周期内使用懒加载的关联对象,否则会抛出异常
- 可通过fetchType="eager"为特定关联配置立即加载
面试点睛:核心是动态代理机制,需说明懒加载的触发时机和配置要点,以及与 N+1 查询问题的关系。
15. MyBatis 的 Mapper 代理是如何实现的?请结合源码说明。
MyBatis 的 Mapper 代理通过 JDK 动态代理实现,核心是MapperProxy类,其工作流程如下:
获取 Mapper 接口代理对象:
当调用SqlSession.getMapper(Class<T> type)时,MyBatis 通过MapperProxyFactory创建代理对象。
// MapperRegistry类
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
return mapperProxyFactory.newInstance(sqlSession);
}
// MapperProxyFactory类
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
// 创建JDK动态代理
return (T) Proxy.newProxyInstance(
mapperInterface.getClassLoader(),
new Class[] { mapperInterface },
mapperProxy
);
}
代理对象执行方法:
当调用 Mapper 接口方法时,会触发MapperProxy的invoke方法。
// MapperProxy类
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 处理Object类的方法(如toString、hashCode)
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 处理接口默认方法
if (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
// 处理Mapper接口方法
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
方法执行映射:
MapperMethod将接口方法映射为SqlSession的对应操作。
// MapperMethod类
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
// 处理更新
}
case DELETE: {
// 处理删除
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
// 处理带ResultHandler的查询
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args); // 处理集合查询
} else if (method.returnsMap()) {
// 处理Map查询
} else {
// 处理单个对象查询
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
// 处理返回结果
return result;
}
核心原理:
通过 “接口全限定名 + 方法名” 与 Mapper.xml 中 SQL 的 id 进行匹配,将接口方法调用转换为对SqlSession相应方法的调用,实现无侵入式的 SQL 执行。
面试点睛:这是 MyBatis 的核心设计,需结合动态代理和方法映射的过程说明,体现对框架底层的理解。
六、实战问题与性能优化
16. 什么是 N+1 查询问题?如何解决?
N+1 查询问题是 MyBatis 关联查询中常见的性能陷阱,指查询 N 条主表数据后,每条主表数据又触发一次子表查询,导致总共 N+1 次数据库交互。
问题示例:
// 1. 查询所有订单(1次查询)
List<Order> orders = orderMapper.selectAll();
// 2. 遍历订单,查询每个订单的明细(N次查询)
for (Order order : orders) {
List<OrderItem> items = orderItemMapper.selectByOrderId(order.getId());
order.setItems(items);
}
// 总计:1 + N 次查询
解决方案:
关联查询一次性加载:
使用association或collection进行 JOIN 查询,一次性获取所有数据。
<resultMap id="orderWithItemsMap" type="Order">
<id column="id" property="id"/>
<result column="order_no" property="orderNo"/>
<collection property="items" ofType="OrderItem">
<id column="item_id" property="id"/>
<result column="product_id" property="productId"/>
</collection>
</resultMap>
<select id="selectOrdersWithItems" resultMap="orderWithItemsMap">
SELECT
o.id, o.order_no,
oi.id AS item_id, oi.product_id
FROM `order` o
LEFT JOIN order_item oi ON o.id = oi.order_id
</select>
延迟加载 + 批量查询:
结合懒加载和foreach实现 “1+1” 次查询。
<!-- 订单映射(懒加载) -->
<resultMap id="orderMap" type="Order">
<id column="id" property="id"/>
<collection
property="items"
select="com.example.mapper.OrderItemMapper.selectByOrderIds"
column="id"
fetchType="lazy"/>
</resultMap>
<!-- 订单项批量查询 -->
<select id="selectByOrderIds" resultType="OrderItem">
SELECT * FROM order_item WHERE order_id IN
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
使用 MyBatis-Plus 的关联查询:
利用 MyBatis-Plus 的@TableName和@TableField注解简化关联查询。
面试点睛:需说明 N+1 问题的成因和两种解决方案的适用场景(关联查询适合数据量小的场景,延迟加载 + 批量查询适合数据量大的场景)。
17. MyBatis 有哪些常见的性能优化手段?
MyBatis 的性能优化需从 SQL、缓存、连接池等多方面入手,常见手段包括:
SQL 优化:
- 避免使用SELECT *,只查询必要字段
- 优化 JOIN 操作,控制关联表数量(不超过 3 张)
- 使用索引优化查询条件,避免全表扫描
- 分页查询限制返回数据量,避免大数据集加载
缓存优化:
- 合理使用一级缓存(控制 SqlSession 生命周期)
- 对热点数据启用二级缓存或分布式缓存(如 Redis)
- 设置合理的缓存过期时间,避免缓存雪崩
连接池优化:
- 使用性能优异的连接池(如 HikariCP)
- 合理配置连接池参数(最大连接数、等待时间等)
<dataSource type="POOLED">
<property name="poolMaximumActiveConnections" value="20"/> <!-- 最大活跃连接 -->
<property name="poolMaximumIdleConnections" value="10"/> <!-- 最大空闲连接 -->
<property name="poolMaximumCheckoutTime" value="20000"/> <!-- 最大 checkout 时间 -->
</dataSource>
4. 批量操作优化:
使用 BatchExecutor 执行批量插入 / 更新
避免循环中执行单条 SQL,改用foreach批量操作
<insert id="batchInsert">
INSERT INTO user (username, email) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.username}, #{user.email})
</foreach>
</insert>
5. 其他优化:
- 启用延迟加载,减少不必要的关联查询
- 使用resultMap代替resultType,避免字段映射开销
- 合理使用fetchSize控制 JDBC 的每次抓取行数
- 监控慢查询,使用EXPLAIN分析并优化
面试点睛:性能优化是企业级应用的重点,需结合具体场景说明优化手段,体现实战经验。
18. MyBatis 如何处理存储过程?请举例说明。
MyBatis 支持调用数据库存储过程,通过statementType="CALLABLE"配置,并使用#{parameterMode=IN/OUT/INOUT}指定参数模式。
示例(MySQL 存储过程):
创建存储过程:
-- 根据用户ID查询用户信息,并返回总记录数
DELIMITER //
CREATE PROCEDURE select_user(
IN userId INT,
OUT total INT
)
BEGIN
SELECT COUNT(1) INTO total FROM user;
SELECT * FROM user WHERE id = userId;
END //
DELIMITER ;
Mapper.xml 配置:
<select id="callSelectUser" statementType="CALLABLE" resultType="User">
{
call select_user(
#{userId, mode=IN, jdbcType=INTEGER},
#{total, mode=OUT, jdbcType=INTEGER}
)
}
</select>
Mapper 接口:
public interface UserMapper {
User callSelectUser(Map<String, Object> params);
}
调用存储过程:
Map<String, Object> params = new HashMap<>();
params.put("userId", 1);
// 执行存储过程
User user = userMapper.callSelectUser(params);
// 获取OUT参数
Integer total = (Integer) params.get("total");
处理结果集的存储过程:
对于返回多个结果集的存储过程,需使用resultSets属性指定结果集映射:
<select id="callMultiResultProc" statementType="CALLABLE"
resultSets="users,orders"
resultMap="userMap,orderMap">
{call get_user_orders(#{userId, mode=IN})}
</select>
七、总结与面试建议
MyBatis 作为企业级应用的主流 ORM 框架,其面试考察点涵盖基础概念、核心原理、配置使用、性能优化和源码理解等多个层面。准备面试时,建议:
夯实基础:熟练掌握核心组件、配置文件、映射规则和动态 SQL。
理解原理:深入理解 Mapper 代理、SQL 执行流程、缓存机制等底层实现。
注重实战:掌握事务管理、批量操作、存储过程调用等实际应用场景。
关注性能:能分析和解决 N+1 查询、慢查询等性能问题。
阅读源码:重点理解 SqlSession、Executor、MapperProxy 等核心类的实现。
2万+

被折叠的 条评论
为什么被折叠?



