MyBatis 是一款 Java 持久层框架(ORM 框架的轻量级实现),核心目标是「简化数据库操作、实现 SQL 与 Java 代码分离」------ 它替代了 JDBC 繁琐的手动编码(加载驱动、创建连接、处理结果集等),同时保留了 SQL 的灵活性(无需像 Hibernate 那样完全屏蔽 SQL),是 Java 后端开发中最常用的持久层解决方案之一(尤其适合需要精细控制 SQL 的场景)。
下面从「核心定位、核心组件、工作原理、核心用法、高级特性、常见问题」六个维度,全面讲解 MyBatis,结合之前的搭建示例,帮你建立完整认知:
一、MyBatis 的核心定位:解决什么问题?
在 MyBatis 出现前,开发者使用 JDBC 操作数据库存在诸多痛点:
- 代码冗余:每次操作数据库都要重复写「加载驱动、创建 Connection、PreparedStatement、关闭资源」等代码;
- SQL 与代码耦合:SQL 语句硬编码在 Java 代码中,修改 SQL 需重新编译代码;
- 参数传递繁琐 :手动设置 SQL 参数(
ps.setInt(1, id)),参数顺序易出错; - 结果集映射复杂 :手动将 ResultSet 结果集封装为 Java 实体类(
user.setId(rs.getInt("id"))),字段多时代码量大; - 资源管理麻烦:需手动关闭 Connection、Statement 等资源,容易遗漏导致连接泄漏。
MyBatis 的核心解决方案:
- 用 XML / 注解 存储 SQL 语句,实现 SQL 与代码分离;
- 自动完成「参数注入」(将 Java 方法参数映射到 SQL 参数);
- 自动完成「结果集映射」(将数据库查询结果映射到 Java 实体类);
- 管理数据库连接(内置连接池),自动释放资源;
- 支持动态 SQL、缓存、分页等高级特性,满足复杂业务需求。
二、MyBatis 核心组件:各司其职的 "工具链"
MyBatis 的核心能力依赖以下组件,它们协同工作完成数据库操作,组件间的关系是理解 MyBatis 的关键:
| 组件 | 作用 |
|---|---|
mybatis-config.xml |
全局配置文件:存储数据库连接信息、缓存策略、Mapper 注册等全局配置 |
SqlSessionFactory |
会话工厂:重量级对象(全局仅需一个),负责创建 SqlSession,基于配置文件构建 |
SqlSession |
数据库会话:类似 JDBC 的 Connection,代表一次数据库连接,线程不安全,用完即关 |
Mapper 接口 |
DAO 层接口:定义数据库操作方法(如 selectById),无实现类(MyBatis 动态代理生成) |
Mapper 映射文件 |
与 Mapper 接口关联:存储 SQL 语句、参数映射、结果映射规则(如 UserMapper.xml) |
ResultMap |
结果映射规则:解决「数据库字段名与实体类属性名不一致」的问题 |
ParameterMap |
参数映射规则:定义方法参数与 SQL 参数的映射关系(较少直接使用,默认自动映射) |
核心组件的关系:
- 加载
mybatis-config.xml→ 构建SqlSessionFactory; SqlSessionFactory创建SqlSession(代表一次数据库会话);SqlSession通过「动态代理」生成Mapper 接口的实现类;- 调用 Mapper 接口方法 → MyBatis 解析对应的
Mapper 映射文件中的 SQL; - 执行 SQL 并自动完成「参数注入」和「结果集映射」→ 返回结果。
三、MyBatis 工作原理:一次 SQL 执行的完整流程
以「查询用户(selectById)」为例,拆解 MyBatis 的执行流程(结合之前的搭建示例):
java
运行
// 1. 加载全局配置文件,创建 SqlSessionFactory
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
// 2. 创建 SqlSession(手动提交事务)
SqlSession sqlSession = factory.openSession();
// 3. 获取 Mapper 接口实例(动态代理)
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 4. 调用接口方法,执行 SQL
User user = userMapper.selectById(1);
底层执行流程(隐藏的关键步骤):
-
配置加载阶段:
- MyBatis 解析
mybatis-config.xml,读取数据库连接信息(数据源)、Mapper 注册(<mappers>标签); - 解析
UserMapper.xml,将 SQL 语句、参数映射、结果映射规则封装为MappedStatement(存储在Configuration中,全局唯一)。
- MyBatis 解析
-
SqlSession 创建阶段:
SqlSessionFactory通过数据源创建SqlSession,同时关联Executor(执行器,MyBatis 核心执行组件)。
-
动态代理阶段:
sqlSession.getMapper(UserMapper.class)时,MyBatis 通过「JDK 动态代理」生成UserMapper的代理类;- 代理类的核心逻辑:将接口方法(如
selectById)关联到对应的MappedStatement(即UserMapper.xml中的<select id="selectById">)。
-
SQL 执行阶段:
- 调用
userMapper.selectById(1)→ 代理类触发Executor执行 SQL; Executor从Configuration中获取对应的MappedStatement,解析 SQL 语句;- 通过
ParameterHandler(参数处理器)将 Java 方法参数(1)注入 SQL 的#{id}占位符; - 执行 SQL(通过
StatementHandler操作数据库),获取 ResultSet 结果集; - 通过
ResultSetHandler(结果集处理器)将 ResultSet 自动映射为User实体类(基于resultType配置); - 返回
User对象给调用者。
- 调用
-
资源释放阶段:
- 调用
sqlSession.close()→ 关闭Connection,释放资源。
- 调用
四、MyBatis 核心用法:从基础到实战
1. SQL 映射的两种方式(核心)
MyBatis 支持「XML 映射」和「注解映射」两种方式,按需选择:
(1)XML 映射(推荐,适合复杂 SQL)
即之前搭建中用的方式:Mapper 接口 + XML 映射文件,示例:
java
运行
// Mapper 接口
public interface UserMapper {
User selectById(Integer id); // 方法名 = XML 中 <select> 的 id
}
// XML 映射文件(UserMapper.xml)
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectById" resultType="com.example.pojo.User">
SELECT id, username, password, age FROM user WHERE id = #{id}
</select>
</mapper>
(2)注解映射(适合简单 SQL)
无需 XML 文件,直接在 Mapper 接口方法上用注解写 SQL:
java
运行
public interface UserMapper {
// 注解映射:直接写 SQL,无需 XML
@Select("SELECT id, username, password, age FROM user WHERE id = #{id}")
User selectById(Integer id);
@Insert("INSERT INTO user (username, password, age) VALUES (#{username}, #{password}, #{age})")
@Options(useGeneratedKeys = true, keyProperty = "id") // 自增主键
int insert(User user);
}
👉 对比:
- XML 映射:适合复杂 SQL(如多表关联、动态 SQL),维护方便;
- 注解映射:适合简单 SQL(单表 CRUD),代码简洁,无需额外 XML 文件。
2. 参数传递:#{} vs ${}(关键区别)
MyBatis 支持两种参数占位符,核心区别是「是否防止 SQL 注入」:
| 占位符 | 原理 | 优势 | 适用场景 |
|---|---|---|---|
#{} |
预编译(PreparedStatement),参数替换为 ? |
自动防止 SQL 注入 | 绝大多数场景(参数查询、新增) |
${} |
字符串拼接(Statement),直接替换参数 | 支持动态表名、排序字段 | 动态表名(如 SELECT * FROM ${tableName}) |
⚠️ 注意:${} 存在 SQL 注入风险,如:
java
运行
// 危险:若 id 传入 "1 OR 1=1",会拼接为 SELECT * FROM user WHERE id = 1 OR 1=1(查询所有数据)
@Select("SELECT * FROM user WHERE id = ${id}")
User selectById(String id);
👉 优先使用 #{},仅在需要动态表名、动态排序字段时使用 ${},且需手动过滤参数。
3. 结果映射:resultType vs resultMap
当数据库字段名与实体类属性名一致 时,用 resultType 直接映射;若不一致 (如数据库字段 user_name,实体类属性 username),用 resultMap 手动配置映射关系:
示例:字段名与属性名不一致
sql
-- 数据库表字段:user_name(而非 username)
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_name` varchar(50) NOT NULL, -- 字段名:user_name
`password` varchar(50) NOT NULL,
`age` int(3) DEFAULT NULL,
PRIMARY KEY (`id`)
);
// 实体类属性:username(与字段名不一致)
@Data
public class User {
private Integer id;
private String username; // 属性名:username
private String password;
private Integer age;
}
用 resultMap 配置映射:
xml
<mapper namespace="com.example.mapper.UserMapper">
<!-- 定义 resultMap:映射规则 -->
<resultMap id="UserResultMap" type="com.example.pojo.User">
<id column="id" property="id"/> <!-- 主键映射 -->
<result column="user_name" property="username"/> <!-- 字段名 → 属性名 -->
<result column="password" property="password"/>
<result column="age" property="age"/>
</resultMap>
<!-- 查询时引用 resultMap,而非 resultType -->
<select id="selectById" resultMap="UserResultMap">
SELECT id, user_name, password, age FROM user WHERE id = #{id}
</select>
</mapper>
五、MyBatis 高级特性:提升开发效率
1. 动态 SQL(核心高级特性)
MyBatis 提供 <if> <where> <foreach> 等标签,支持根据参数动态拼接 SQL(无需手动拼接字符串,避免语法错误):
示例:多条件查询(动态拼接 WHERE 条件)
xml
<select id="selectByCondition" resultType="com.example.pojo.User">
SELECT id, username, password, age FROM user
<where> <!-- 自动省略多余的 AND/OR,避免 WHERE AND 语法错误 -->
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
示例:批量新增(foreach 遍历集合)
xml
<insert id="batchInsert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (username, password, age) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.username}, #{user.password}, #{user.age})
</foreach>
</insert>
// Mapper 接口方法
int batchInsert(List<User> userList); // collection="list" 对应方法参数的 List
2. 缓存机制(提升查询性能)
MyBatis 提供两级缓存,核心是「缓存查询结果,避免重复查询数据库」:
(1)一级缓存(本地缓存,默认开启)
- 作用域:
SqlSession级别(一次数据库会话); - 原理:同一
SqlSession中,多次执行相同的 SQL 查询(参数相同),第一次查询后缓存结果,后续直接从缓存获取; - 失效场景:执行
insert/update/delete操作、关闭SqlSession、手动调用sqlSession.clearCache()。
(2)二级缓存(全局缓存,需手动开启)
-
作用域:
SqlSessionFactory级别(全局共享); -
开启方式:
-
在
mybatis-config.xml中开启全局缓存(默认开启,可省略):xml
<settings> <setting name="cacheEnabled" value="true"/> </settings> -
在 Mapper 映射文件中添加
<cache>标签:xml
<mapper namespace="com.example.mapper.UserMapper"> <cache/> <!-- 开启该 Mapper 的二级缓存 --> <!-- ... SQL 语句 ... --> </mapper>
-
-
注意:二级缓存的实体类需实现
Serializable接口(缓存序列化存储)。
3. 分页插件(PageHelper)
MyBatis 本身不支持分页,需通过第三方插件实现(如 PageHelper),步骤:
-
引入依赖(pom.xml): xml
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.3.3</version> </dependency> -
在
mybatis-config.xml中配置插件:xml
<plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <!-- 配置数据库方言(如 MySQL) --> <property name="helperDialect" value="mysql"/> </plugin> </plugins> -
使用分页: java
运行
// 分页查询:第 1 页,每页 10 条数据 PageHelper.startPage(1, 10); List<User> userList = userMapper.selectAll(); // 执行查询,自动拼接 LIMIT 语句 PageInfo<User> pageInfo = new PageInfo<>(userList); // 分页结果封装(总条数、总页数等)
六、常见问题与注意事项
1. 事务提交问题(新增 / 修改 / 删除无效果)
MyBatis 默认 手动提交事务 (sqlSession = factory.openSession()),执行 insert/update/delete 后需手动调用 sqlSession.commit(),否则数据不会写入数据库;若需自动提交,可使用 openSession(true)。
2. Mapper 映射文件未注册(BindingException)
报错:Type interface com.example.mapper.UserMapper is not known to the MapperRegistry解决:在 mybatis-config.xml 的 <mappers> 标签中注册 Mapper(XML 或包扫描):
xml
<!-- 方式1:注册单个 XML 文件 -->
<mapper resource="mappers/UserMapper.xml"/>
<!-- 方式2:扫描包下所有 Mapper 接口(XML 需与接口同路径) -->
<package name="com.example.mapper"/>
3. 字段名与属性名不一致(查询结果为 null)
若未配置 resultMap,且数据库字段名与实体类属性名不一致(如 user_name vs username),查询结果会为 null;解决:要么用 resultMap 配置映射,要么在 SQL 中用别名(SELECT user_name AS username FROM user)。
4. SQL 注入风险(${} 滥用)
避免在查询条件中使用 ${},优先用 #{};若必须使用 ${}(如动态表名),需手动过滤参数(如限制表名只能是指定值)。
5. 整合 Spring 注意事项
实际开发中 MyBatis 常与 Spring 整合(Spring + MyBatis),核心是:
- 由 Spring 管理
SqlSessionFactory和DataSource; - 通过
@Mapper注解或包扫描自动注册 Mapper 接口; - 事务由 Spring 管理(
@Transactional注解),无需手动调用sqlSession.commit()。
总结
MyBatis 的核心价值是「灵活与简化的平衡」:
- 简化:自动处理参数注入、结果映射、资源管理,减少重复代码;
- 灵活:SQL 完全由开发者控制,支持复杂查询、动态 SQL,适配各类业务场景。