MyBatis学习笔记(含面试题)

一、核心知识点梳理

1. MyBatis简介

1.1 核心定位

MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集的过程,通过XML或注解的方式将Java对象与SQL语句动态映射,简化数据持久化操作。

核心思想:将SQL与Java代码分离,通过配置文件管理SQL,降低耦合度,提高代码可维护性。

1.2 与JDBC的对比优势

  • JDBC需要手动加载驱动、创建连接、编写SQL、设置参数、处理结果集、关闭资源,代码冗余且繁琐;MyBatis通过配置和映射自动完成这些操作,简化开发。

  • JDBC的SQL语句硬编码在Java代码中,修改SQL需重新编译代码;MyBatis的SQL写在XML或注解中,修改无需编译,灵活度高。

  • JDBC需要手动处理参数类型匹配和结果集与Java对象的映射;MyBatis支持自动参数映射和结果集映射,支持复杂对象(一对一、一对多)映射。

  • MyBatis提供缓存机制(一级缓存、二级缓存),提升查询效率;JDBC需手动实现缓存。

1.3 核心依赖(Maven)

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.15</version> <!-- 最新稳定版 -->
</dependency>
<!-- 数据库驱动(以MySQL为例) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.36</version>
</dependency>

2. MyBatis核心组件

MyBatis的核心组件围绕“SQL执行流程”展开,各组件分工明确,协同完成数据持久化操作:

2.1 SqlSessionFactoryBuilder(构建者)

作用:根据MyBatis核心配置文件(mybatis-config.xml)或配置类,构建SqlSessionFactory对象。

特点:

  • 属于临时对象,构建完SqlSessionFactory后即可销毁,无需长期持有。

  • 核心方法:build(InputStream inputStream),通过读取配置文件的输入流构建工厂。

2.2 SqlSessionFactory(会话工厂)

作用:创建SqlSession对象(数据库会话),是MyBatis的核心工厂类。

特点:

  • 属于单例对象,整个应用程序中只需创建一个,负责管理数据库连接池。

  • 核心方法:openSession(),创建SqlSession对象(默认手动提交事务);openSession(true),创建自动提交事务的SqlSession。

2.3 SqlSession(数据库会话)

作用:代表与数据库的一次会话,是MyBatis操作数据库的核心对象,可执行SQL语句、管理事务。

特点:

  • 属于线程不安全对象,每次数据库操作需创建新的SqlSession,使用完毕后必须关闭(避免资源泄漏)。

  • 核心方法:selectOne()(查询单个结果)、selectList()(查询集合结果)、insert()、update()、delete()(增删改操作)、commit()(提交事务)、rollback()(回滚事务)、getMapper()(获取Mapper接口代理对象)。

2.4 Mapper接口(映射器)

作用:定义数据操作的接口,MyBatis通过动态代理机制自动生成接口的实现类,实现SQL与Java方法的映射。

特点:

  • 接口无需手动实现,MyBatis通过XML或注解绑定SQL语句。

  • 方法名需与XML映射文件中的SQL语句ID一致,参数类型和返回值类型需与SQL的参数和结果集匹配。

2.5 核心配置文件(mybatis-config.xml)

作用:配置MyBatis的核心参数,如数据库连接信息、映射器路径、缓存策略、类型别名等。

核心配置节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<configuration>
<!-- 环境配置(可配置多环境,默认激活一个) -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/> <!-- 事务管理类型:JDBC(手动)/MANAGED(交给容器) -->
<dataSource type="POOLED"/> <!-- 数据源类型:POOLED(连接池)/UNPOOLED(非连接池)/JNDI(容器数据源) -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_db"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 映射器配置(指定XML映射文件或Mapper接口路径) -->
<mappers>
<mapper resource="mapper/UserMapper.xml"/> <!-- 基于XML文件 -->
<mapper class="com.example.mapper.UserMapper"/> <!-- 基于注解(接口全类名) -->
<package name="com.example.mapper"/> <!-- 扫描指定包下的所有Mapper接口 -->
</mappers>
<!-- 类型别名(简化全类名编写) -->
<typeAliases>
<typeAlias type="com.example.pojo.User" alias="User"/>
<package name="com.example.pojo"/> <!-- 扫描包,别名默认为类名首字母小写(User→user) -->
</typeAliases>
</configuration>

2.6 映射文件(XXXMapper.xml)

作用:存储SQL语句,定义Java对象与SQL参数、结果集的映射关系,是MyBatis中SQL与代码分离的核心载体。

核心节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace:绑定对应的Mapper接口全类名 -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 结果集映射:解决Java属性名与数据库字段名不一致问题 -->
<resultMap id="UserMap" type="User">
<id column="user_id" property="userId"/> <!-- 主键映射 -->
<result column="user_name" property="userName"/> <!-- 普通字段映射 -->
<result column="age" property="age"/>
</resultMap>

<!-- 查询:id对应Mapper接口的方法名,resultMap指定映射规则 -->
<select id="getUserById" parameterType="int" resultMap="UserMap">
SELECT user_id, user_name, age FROM user WHERE user_id = #{id}
</select>

<!-- 新增:useGeneratedKeys获取自增主键,keyProperty绑定Java属性 -->
<insert id="addUser" parameterType="User" useGeneratedKeys="true" keyProperty="userId">
INSERT INTO user (user_name, age) VALUES (#{userName}, #{age})
</insert>

<!-- 修改 -->
<update id="updateUser" parameterType="User">
UPDATE user SET user_name = #{userName}, age = #{age} WHERE user_id = #{userId}
</update>

<!-- 删除 -->
<delete id="deleteUser" parameterType="int">
DELETE FROM user WHERE user_id = #{id}
</delete>
</mapper>

3. MyBatis核心工作原理

MyBatis的工作流程可分为“初始化阶段”和“SQL执行阶段”两大步骤,核心是“配置解析”和“动态代理”:

3.1 初始化阶段(启动时)

  1. 加载核心配置文件:SqlSessionFactoryBuilder读取mybatis-config.xml,解析配置信息(环境、数据源、映射器等)。

  2. 创建SqlSessionFactory:通过配置信息初始化数据源(连接池)和事务管理器,生成SqlSessionFactory单例对象。

  3. 解析映射文件:SqlSessionFactory加载XXXMapper.xml,解析SQL语句、参数映射、结果集映射,存储到Configuration对象中(MyBatis的核心配置容器)。

3.2 SQL执行阶段(运行时)

  1. 创建SqlSession:通过SqlSessionFactory.openSession()创建SqlSession,关联数据库连接,开启事务。

  2. 获取Mapper代理对象:SqlSession.getMapper(Mapper接口.class),MyBatis通过JDK动态代理生成Mapper接口的代理实现类(MapperProxy)。

  3. 执行SQL:调用Mapper接口的方法,代理对象拦截方法调用,从Configuration中获取对应的SQL语句和映射规则。

  4. 参数处理:将Java方法参数转换为SQL参数,通过参数处理器(ParameterHandler)设置到PreparedStatement中。

  5. 执行SQL并处理结果:通过执行器(Executor)执行SQL,获取结果集,通过结果集处理器(ResultSetHandler)将结果集映射为Java对象。

  6. 事务管理:执行完成后,通过SqlSession.commit()提交事务或rollback()回滚事务,关闭SqlSession释放资源。

4. MyBatis核心特性

4.1 动态SQL

MyBatis提供动态SQL标签,可根据参数条件动态拼接SQL语句,避免手动拼接SQL的繁琐和SQL注入风险。

常用动态SQL标签:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<select id="getUserList" parameterType="User" resultMap="UserMap">
SELECT user_id, user_name, age FROM user
<where> <!-- 自动拼接WHERE关键字,去除多余AND/OR -->
<if test="userName != null and userName != ''">
AND user_name LIKE CONCAT('%', #{userName}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
<choose> <!-- 类似switch-case,只执行一个when -->
<when test="sortBy == 'age'">ORDER BY age ASC</when>
<when test="sortBy == 'userName'">ORDER BY user_name ASC</when>
<otherwise>ORDER BY user_id ASC</otherwise>
</choose>
</select>

<!-- 批量删除:foreach遍历集合 -->
<delete id="deleteBatchUser" parameterType="java.util.List">
DELETE FROM user WHERE user_id IN
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>

4.2 结果集映射

解决Java对象属性名与数据库字段名不一致的问题,常用两种方式:

  1. 使用resultMap标签:在XML中定义字段名与属性名的映射关系(推荐,适合复杂对象)。

  2. 使用别名:在SQL语句中给字段起别名,别名与Java属性名一致(适合简单场景),如SELECT user_id AS userId, user_name AS userName FROM user。

复杂映射(关联查询):

  • 一对一(association):如用户与身份证信息,使用标签映射关联对象。

  • 一对多(collection):如用户与订单列表,使用标签映射关联集合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 一对多:用户与订单 -->
<resultMap id="UserOrderMap" type="User">
<id column="user_id" property="userId"/>
<result column="user_name" property="userName"/>
<!-- collection映射订单集合 -->
<collection property="orderList" ofType="Order">
<id column="order_id" property="orderId"/>
<result column="order_no" property="orderNo"/>
</collection>
</resultMap>

<select id="getUserWithOrders" parameterType="int" resultMap="UserOrderMap">
SELECT u.user_id, u.user_name, o.order_id, o.order_no
FROM user u LEFT JOIN order o ON u.user_id = o.user_id
WHERE u.user_id = #{id}
</select>

4.3 缓存机制

MyBatis提供两级缓存,用于减少数据库查询次数,提升性能:

4.3.1 一级缓存(本地缓存)

  • 作用域:SqlSession级别,默认开启,不可关闭。

  • 原理:同一个SqlSession中,执行相同的查询SQL(参数一致),第一次查询从数据库获取结果并缓存到SqlSession中,后续查询直接从缓存获取,无需访问数据库。

  • 失效场景:执行insert/update/delete操作(会清空一级缓存)、手动调用SqlSession.clearCache()、SqlSession关闭。

4.3.2 二级缓存(全局缓存)

  • 作用域:Mapper接口级别(同一个namespace),默认关闭,需手动开启。

  • 开启方式:① 核心配置文件中设置(默认true,可省略);② 映射文件中添加标签。

  • 原理:多个SqlSession共享同一个Mapper的缓存,查询结果先存入一级缓存,当SqlSession关闭时,一级缓存的内容会同步到二级缓存。

  • 注意事项:缓存的对象需实现Serializable接口(支持序列化,便于缓存存储);执行insert/update/delete操作会清空当前Mapper的二级缓存。

4.4 分页机制

MyBatis支持两种分页方式:

  1. RowBounds分页(内存分页):通过RowBounds对象设置起始行和查询条数,原理是查询全部结果后在内存中截取,效率低,适合小数据量场景。

  2. 插件分页(物理分页,推荐):使用第三方插件(如PageHelper),通过拦截SQL语句动态添加LIMIT(MySQL)、ROWNUM(Oracle)等分页语法,直接从数据库查询分页结果,效率高。

5. MyBatis注解开发

MyBatis支持通过注解直接在Mapper接口上编写SQL,无需XML映射文件,适合简单SQL场景,复杂SQL仍推荐XML方式。

常用注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public interface UserMapper {
// 查询(@Select)
@Select("SELECT user_id AS userId, user_name AS userName, age FROM user WHERE user_id = #{id}")
User getUserById(int id);

// 新增(@Insert)
@Insert("INSERT INTO user (user_name, age) VALUES (#{userName}, #{age})")
@Options(useGeneratedKeys = true, keyProperty = "userId") // 获取自增主键
int addUser(User user);

// 修改(@Update)
@Update("UPDATE user SET user_name = #{userName}, age = #{age} WHERE user_id = #{userId}")
int updateUser(User user);

// 删除(@Delete)
@Delete("DELETE FROM user WHERE user_id = #{id}")
int deleteUser(int id);

// 动态SQL(@SelectProvider,通过类的方法生成SQL)
@SelectProvider(type = UserSqlProvider.class, method = "getUserListSql")
List<User> getUserList(User user);

// 动态SQL提供者类
class UserSqlProvider {
public String getUserListSql(User user) {
return new SQL() {{
SELECT("user_id, user_name, age");
FROM("user");
if (user.getUserName() != null && !user.getUserName().isEmpty()) {
WHERE("user_name LIKE CONCAT('%', #{userName}, '%')");
}
if (user.getAge() != null) {
WHERE("age = #{age}");
}
}}.toString();
}
}
}

二、面试高频问题及解析

1. 基础概念类

Q1:MyBatis是什么?它的核心优势是什么?

解析:

MyBatis是一款持久层框架,支持定制化SQL、存储过程和高级映射,通过XML或注解将Java对象与SQL动态映射,简化JDBC开发。

核心优势:

  • SQL与Java代码分离,便于维护和修改,无需重新编译代码。

  • 自动完成参数映射和结果集映射,避免手动处理JDBC的参数和结果集,减少冗余代码。

  • 支持动态SQL,可根据参数条件动态拼接SQL,灵活应对复杂查询场景。

  • 提供缓存机制(一级、二级),提升查询效率。

  • 支持复杂映射(一对一、一对多),满足复杂业务场景需求。

  • 轻量级框架,配置简单,学习成本低,易于集成到Spring等主流框架。

Q2:MyBatis与Hibernate的区别是什么?适用场景分别是什么?

解析:

两者均为持久层框架,核心区别在于“SQL控制粒度”和“开发模式”:

维度 MyBatis Hibernate
SQL控制 手动编写SQL,灵活度高,可优化SQL性能 自动生成SQL,无需手动编写,灵活度低
开发模式 半ORM(对象关系映射),需要关注SQL和数据库细节 全ORM,完全面向对象开发,无需关注数据库细节
学习成本 低,重点掌握SQL和映射配置 高,需要掌握HQL、缓存、关联映射等复杂特性
性能优化 手动优化SQL,针对性强,适合复杂查询场景 自动生成的SQL可能低效,优化难度大,需通过复杂配置优化
适用场景 需求多变、SQL复杂的项目(如电商、金融);对性能要求高,需要手动优化SQL的场景 需求稳定、SQL简单的项目(如后台管理系统);快速开发原型的场景

2. 核心组件与工作原理类

Q1:MyBatis的核心组件有哪些?各自的作用是什么?

解析:

  1. SqlSessionFactoryBuilder:构建者,根据核心配置文件构建SqlSessionFactory,构建完成后可销毁。

  2. SqlSessionFactory:会话工厂,单例对象,管理数据库连接池,负责创建SqlSession。

  3. SqlSession:数据库会话,线程不安全,代表与数据库的一次交互,可执行SQL、管理事务。

  4. Mapper接口:映射器,定义数据操作接口,MyBatis通过动态代理生成实现类,绑定SQL语句。

  5. Configuration:核心配置容器,存储MyBatis的所有配置信息(环境、映射器、SQL语句等)。

  6. Executor:执行器,SqlSession的底层核心组件,负责执行SQL语句,管理一级缓存。

  7. ParameterHandler:参数处理器,将Java方法参数转换为SQL参数,设置到PreparedStatement中。

  8. ResultSetHandler:结果集处理器,将SQL查询结果集映射为Java对象。

Q2:MyBatis的工作原理是什么?请简述完整的执行流程。

解析:

MyBatis的工作流程分为“初始化阶段”和“SQL执行阶段”:

  1. 初始化阶段:
    加载配置:SqlSessionFactoryBuilder读取mybatis-config.xml和XXXMapper.xml,解析配置信息(数据源、事务、SQL语句、映射规则等)。

  2. 创建SqlSessionFactory:根据解析后的配置信息,初始化数据源和事务管理器,生成SqlSessionFactory单例对象。

  3. 存储配置:将解析后的SQL语句、映射规则等存入Configuration对象中,供运行时使用。

  4. SQL执行阶段:
    创建SqlSession:通过SqlSessionFactory.openSession()创建SqlSession,关联数据库连接,开启事务。

  5. 获取Mapper代理:调用SqlSession.getMapper(),MyBatis通过JDK动态代理生成Mapper接口的代理对象(MapperProxy)。

  6. 拦截方法调用:调用Mapper接口方法时,代理对象拦截调用,从Configuration中获取对应的SQL语句和映射规则。

  7. 参数处理:ParameterHandler将Java方法参数转换为SQL参数,设置到PreparedStatement。

  8. 执行SQL:Executor执行SQL语句,获取结果集。

  9. 结果映射:ResultSetHandler将结果集映射为Java对象。

  10. 事务管理:执行完成后,通过SqlSession.commit()/rollback()管理事务,关闭SqlSession释放资源。

Q3:MyBatis的Mapper接口为什么不需要实现类?

解析:

MyBatis通过JDK动态代理机制自动生成Mapper接口的实现类,无需手动编写:

  1. 当调用SqlSession.getMapper(Mapper接口.class)时,MyBatis会创建一个实现了该接口的代理对象(MapperProxy)。

  2. 代理对象会拦截接口中所有方法的调用,根据方法名从Configuration中找到对应的SQL语句和映射规则。

  3. 代理对象通过Executor、ParameterHandler、ResultSetHandler等组件完成SQL执行和结果映射,最终将结果返回给调用者。

核心优势:简化开发,避免编写大量重复的实现类代码,同时实现SQL与Java代码的解耦。

3. 映射与SQL相关类

Q1:MyBatis中如何解决Java属性名与数据库字段名不一致的问题?

解析:

常用三种解决方案,优先级:resultMap > SQL别名 > 开启驼峰命名自动映射:

  1. 使用resultMap标签(推荐):在XML映射文件中定义resultMap,明确指定数据库字段名(column)与Java属性名(property)的映射关系。
    `