疯狂餐厅
86.88M · 2026-03-21
作为 Java 开发者,你一定被 JDBC 折磨过:写一堆重复的连接、关闭代码,SQL 硬编码在 Java 里,参数手动 set、结果手动映射,折腾半天,真正的业务逻辑没写几行。
直到 MyBatis 出现,彻底拯救了被 JDBC 折磨的程序员 —— 它不是替代 JDBC,而是站在 JDBC 的肩膀上,用 “SQL 与代码分离” 的思想,把繁琐的样板代码全部封装,让你只专注 SQL 和结果映射,开发效率直接翻倍。
这篇文章,不跳步、不玄学、不堆砌概念,用 “道法术器” 四层逻辑,从 “为什么要有 MyBatis” 到 “生态工具怎么用”,把 MyBatis 彻底讲透。全程专业准确,新手能快速上手,老手能查漏补缺,建议立刻收藏,面试、开发、复盘都能直接翻。
在 MyBatis 出现之前,用 JDBC 操作数据库,堪称 “体力活天花板”,全程踩坑不重样,我们一步步还原当时的绝望:
开发一个简单的 “根据 ID 查用户” 功能,JDBC 要写几十行代码,真正的业务逻辑(SQL 查询)只占 1 行,剩下全是重复的样板代码:
java
运行
// 传统JDBC的噩梦:重复代码占90%
public User findUserById(Long id) {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
User user = null;
try {
// 1. 加载驱动(重复)
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接(重复)
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password");
// 3. 创建语句(重复)
String sql = "SELECT * FROM user WHERE id = ?";
stmt = conn.prepareStatement(sql);
stmt.setLong(1, id);
// 4. 执行查询(核心逻辑,仅1行)
rs = stmt.executeQuery();
// 5. 处理结果集(重复+枯燥)
if (rs.next()) {
user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6. 关闭资源(重复+易错)
if (rs != null) try { rs.close(); } catch (Exception e) {}
if (stmt != null) try { stmt.close(); } catch (Exception e) {}
if (conn != null) try { conn.close(); } catch (Exception e) {}
}
return user;
}
每个 SQL 占位符都要手动setXXX,参数多了容易数错位置,类型不匹配直接抛异常,调试半天找不到问题。
数据库字段和 Java 对象属性要手动映射,表有 10 个字段就要写 10 行getXXX+setXXX,字段名不一致(如create_time vs createTime)还要额外处理,纯纯的体力活。
SQL 硬编码在 Java 字符串里,没有语法高亮、没有自动补全,改个字段都要重新编译、打包、部署,效率极低,DBA 想优化 SQL 都无从下手。
每次查询都要走数据库,哪怕是相同的查询条件,重复请求会给数据库带来不必要的压力,性能拉胯。
MyBatis 存在的唯一理由:解决 JDBC“太底层、太繁琐” 的痛点 —— 把连接管理、参数设置、结果映射、资源关闭等重复样板代码封装起来,让开发者只专注核心的 SQL 和业务逻辑,同时保留对 SQL 的完全控制权。
“道” 是 MyBatis 的灵魂,回答 “为什么这么设计”,搞懂这 3 个核心思想,就抓住了 MyBatis 的本质,面试被问也能对答如流。
这是 MyBatis 最核心的设计思想,也是它能解决 “SQL 与代码耦合” 的根本。
实战示例(一看就懂):
xml
<!-- XML中写SQL,脱离Java代码,支持语法高亮、自动补全 -->
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectUserById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
java
运行
// Java代码中只剩接口调用,简洁明了
User user = userMapper.selectUserById(1L);
核心价值:
MyBatis 的本质是轻量级 ORM(对象关系映射)框架,解决 “Java 对象和数据库记录的阻抗不匹配” 问题。
常见的 “阻抗不匹配” 场景:
create_time vs Java 属性createTime;datetime vs Java Date;MyBatis 的解决方案(结果映射):
xml
<!-- 自定义结果映射,解决字段与属性不匹配问题 -->
<resultMap id="userResultMap" type="User">
<id property="id" column="id"/> <!-- 主键映射 -->
<result property="name" column="name"/> <!-- 普通字段映射 -->
<result property="age" column="age"/>
<result property="createTime" column="create_time"/> <!-- 命名不一致映射 -->
</resultMap>
MyBatis 的核心理念是:把不变的、重复的 JDBC 样板代码封装,把变化的、业务相关的部分留给开发者定制—— 这也是它区别于 Hibernate(全自动化 ORM)的关键。
表格
| 不变的部分(框架封装) | 变化的部分(开发者定制) |
|---|---|
| 连接获取与关闭 | SQL 语句(核心业务逻辑) |
| 参数自动设置 | 参数类型 / 映射规则 |
| 结果集自动遍历 | 结果映射方式 |
| 异常统一处理 | 动态 SQL 条件 |
| 事务管理 | 返回值类型 |
核心价值:
“法” 是实现 “道” 的路径和方法论,回答 “怎么做”,这 4 个方法论,是 MyBatis 简化开发的核心,也是面试高频考点。
MyBatis 的整体架构分为三层,每一层有明确的职责,保证框架的清晰性和可维护性:
plaintext
┌─────────────────────────────────────┐
│ API接口层 │
│ SqlSession、Mapper接口代理(开发者直接用) │
├─────────────────────────────────────┤
│ 数据处理层 │
│ SQL解析、SQL执行、结果映射(核心逻辑) │
├─────────────────────────────────────┤
│ 基础支撑层 │
│ 连接管理、事务管理、缓存、配置加载(底层) │
└─────────────────────────────────────┘
MyBatis 的 SQL 执行流程,围绕四个核心组件展开,各司其职、高效协作:
表格
| 组件 | 核心职责 | 通俗类比 |
|---|---|---|
| Executor(执行器) | 调度执行流程,维护缓存,是核心调度者 | 项目经理 |
| StatementHandler | 封装 JDBC Statement,设置参数、执行 SQL | 施工队 |
| ParameterHandler | 处理参数映射,把 Java 参数转 JDBC 参数 | 材料员 |
| ResultSetHandler | 处理结果集映射,把 JDBC 结果转 Java 对象 | 质检员 |
MyBatis 最神奇的地方:只定义 Mapper 接口,不用写实现类,却能直接调用方法。
实战示例:
java
运行
// 只定义接口,无实现类
public interface UserMapper {
User selectUserById(Long id);
}
// 直接调用,框架自动生成实现
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectUserById(1L);
sqlSession.getMapper()时,MyBatis 通过MapperProxyFactory创建 Mapper 接口的动态代理对象;invoke方法拦截所有接口方法调用;MappedStatement(SQL 封装对象);MyBatis 将 XML / 注解中的每个 SQL 语句(<select>/<insert>等),封装成MappedStatement对象,存储在内存中,执行时直接取用。
java
运行
public class MappedStatement {
private String id; // SQL唯一标识(namespace+id)
private SqlSource sqlSource; // SQL源(包含原始SQL和参数信息)
private StatementType statementType; // 语句类型(STATEMENT/PREPARED/CALLABLE)
private ResultMap resultMap; // 结果集映射配置
private SqlCommandType sqlCommandType; // SQL类型(SELECT/UPDATE/INSERT/DELETE)
}
MappedStatement,存入Configuration(全局配置);statementId(如com.example.mapper.UserMapper.selectUserById)找到对应的MappedStatement,获取 SQL 和映射规则。“术” 是具体的技术技巧,回答 “用什么工具实现”,掌握这些,遇到 MyBatis 问题(动态 SQL 不生效、缓存异常等),能快速定位、解决。
MyBatis 的启动过程,本质是 “解析配置→构建全局上下文→初始化核心组件”,简化流程如下:
plaintext
1. 读取核心配置文件(mybatis-config.xml)
2. 创建XMLConfigBuilder,解析配置
├── 解析<environments>(数据源、事务管理器)
├── 解析<typeAliases>(类型别名,简化配置)
├── 解析<mappers>(映射文件路径)
└── 构建Configuration对象(全局上下文)
3. 解析Mapper.xml文件
├── 每个SQL标签解析为MappedStatement
├── 每个<resultMap>解析为ResultMap对象
└── 存入Configuration
4. 创建SqlSessionFactory(会话工厂)
5. 通过SqlSessionFactory获取SqlSession(操作入口)
关键点:Configuration是 MyBatis 的 “大脑”,所有配置、映射规则、SQL 封装体都存在这里。
动态 SQL(<if>/<where>/<foreach>)是 MyBatis 的核心亮点,能根据参数动态拼接 SQL,实现复杂查询。
实战示例:
xml
<select id="findUsers" resultType="User">
SELECT * FROM users
<where>
<if test="name != null">AND name = #{name}</if>
<if test="age != null">AND age = #{age}</if>
</where>
</select>
<if>/<where>)被解析为SqlNode对象树(IfSqlNode/WhereSqlNode等);SqlSource管理这些SqlNode,负责动态拼接;SqlSource根据传入参数,遍历SqlNode树,动态生成完整 SQL;BoundSql对象(包含完整 SQL + 参数映射),交给 StatementHandler 执行。Java 类型和 JDBC 类型的转换,由TypeHandler(类型处理器)负责,是 MyBatis 实现 ORM 映射的核心。
java
运行
public interface TypeHandler<T> {
// Java → JDBC(设置参数时)
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType);
// JDBC → Java(获取结果时)
T getResult(ResultSet rs, String columnName);
T getResult(ResultSet rs, int columnIndex);
}
StringTypeHandler/IntegerTypeHandler);TypeHandler接口并配置。MyBatis 内置两级缓存,减少数据库查询,提升性能:
表格
| 缓存级别 | 作用范围 | 默认状态 | 触发清空条件 |
|---|---|---|---|
| 一级缓存 | SqlSession 级别(会话内) | 开启(无需配置) | 执行 update/delete/insert、commit、close、clearCache |
| 二级缓存 | Mapper 级别(跨会话) | 关闭(需手动配置) | 执行增删改操作、缓存过期、手动清空 |
开启二级缓存(Mapper.xml 中配置):
xml
<!-- 开启二级缓存:LRU淘汰策略,60秒过期,最多存512条,只读 -->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
MyBatis 允许通过插件拦截四大核心对象的方法,插入自定义逻辑(如分页、日志、权限控制)。
Executor/StatementHandler/ParameterHandler/ResultSetHandler的方法;核心价值:无需修改 MyBatis 源码,即可扩展核心功能,灵活性拉满。
“器” 是 MyBatis 的具体工具和组件,回答 “有哪些东西可以用”,这些工具覆盖开发、测试、优化全流程,开箱即用,大幅提升效率。
表格
| 依赖 | 说明 |
|---|---|
| mybatis-x.x.x.jar | MyBatis 核心框架,包含所有核心类 |
| 数据库驱动 | MySQL/Oracle/PostgreSQL 等对应的 JDBC 驱动(如 mysql-connector-java) |
将 MyBatis 无缝集成到 Spring 中,支持 Spring 容器管理 Mapper、事务统一管理:
xml
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.0</version>
</dependency>
SqlSessionFactoryBean:Spring 中创建 SqlSessionFactory;MapperFactoryBean:将 Mapper 接口注入 Spring 容器;@Transactional注解无缝兼容。MyBatis-Plus是 MyBatis 的增强工具,“只做增强,不做改变”,大幅降低开发量:
BaseMapper,自动获得增删改查方法,无需写 SQL;实战示例(通用 CRUD):
java
运行
// 继承BaseMapper,自动获得CRUD方法
public interface UserMapper extends BaseMapper<User> {}
// 条件构造器查询(无需写XML)
List<User> users = userMapper.selectList(
new LambdaQueryWrapper<User>()
.eq(User::getAge, 18) // 年龄=18
.like(User::getName, "张") // 姓名含“张”
);
MyBatis 官方提供的代码生成器,根据数据库表自动生成:
核心价值:减少重复代码编写,提升开发效率,规范代码格式。
基于 MyBatis 插件机制实现的分页工具,一行代码实现分页:
java
运行
// 分页只需一行代码
PageHelper.startPage(1, 10); // 第1页,每页10条
List<User> users = userMapper.selectAll();
PageInfo<User> pageInfo = new PageInfo<>(users); // 分页结果
拦截Executor的query方法,自动在 SQL 后添加LIMIT子句,同时查询总记录数。
很多人觉得 MyBatis 复杂,其实用 “定制化餐厅” 的比喻,就能轻松理解道法术器四层逻辑:
结合以上分析,MyBatis 执行一条 SQL 的完整流程如下:
plaintext
1. 加载配置文件 → 构建Configuration全局上下文
2. 通过SqlSessionFactory创建SqlSession(操作入口)
3. 获取Mapper接口的动态代理对象
4. 调用Mapper方法 → 动态代理拦截调用
5. SqlSession根据statementId找到MappedStatement
6. Executor执行查询
├── 检查一级缓存 → 命中则返回
├── 未命中 → 通过StatementHandler创建Statement
├── ParameterHandler设置SQL参数
├── 执行SQL,得到ResultSet
├── ResultSetHandler映射为Java对象
└── 存入一级缓存
7. 返回Java对象
8. 关闭SqlSession(释放连接)
用第一性原理来看,MyBatis 的本质是:一个轻量级持久层框架,通过 SQL 与代码分离、封装 JDBC 样板代码、实现 ORM 映射,在保留开发者对 SQL 完全控制权的前提下,大幅简化数据库操作,提升开发效率。
它的核心价值,就是 “平衡”—— 平衡 “自动化” 和 “控制权”:既像 Hibernate 一样减少重复劳动,又不像 Hibernate 那样屏蔽 SQL,完美适配 Java 开发者的需求。
从道法术器四个层次,总结 MyBatis 的核心:
表格
| 层次 | 核心内容 | 一句话总结 |
|---|---|---|
| 道 | SQL 与代码分离、ORM 映射、封装不变变化定制 | 核心思想:保留 SQL 控制权,解放重复劳动 |
| 法 | 三层架构、四大组件协作、动态代理、MappedStatement | 实现路径:组件分工,高效协作 |
| 术 | 配置解析、动态 SQL、TypeHandler、缓存、插件 | 技术细节:具体实现技巧 |
| 器 | MyBatis-Plus、MyBatis-Spring、PageHelper | 工具支撑:提升开发效率 |
关键提醒:MyBatis 不是替代 JDBC,而是 “封装” JDBC—— 它底层依然使用 JDBC 操作数据库,但把繁琐的细节全部封装,让开发者只关注核心的 SQL 和业务逻辑。这也是它能成为 Java 持久层框架 “事实标准” 的根本原因。
表格
| 层次 | 核心内容 | 核心价值 |
|---|---|---|
| 道 | SQL 与代码分离、ORM 映射、封装不变变化定制 | 理解设计理念,知道 “为什么这么做” |
| 法 | 三层架构、四大组件、动态代理、MappedStatement | 掌握核心方法,知道 “怎么做” |
| 术 | 配置解析、动态 SQL、TypeHandler、缓存、插件 | 解决实际问题,掌握 “具体实现” |
| 器 | MyBatis-Plus、MyBatis-Spring、PageHelper | 用好生态工具,提升开发效率 |
学 MyBatis 时,你最头疼的问题是什么?
评论区留下你的痛点,我会挑高频问题,下期专门出《MyBatis 常见坑排查实战》,手把手教你解决,还会附赠 MyBatis 面试高频题合集,帮你轻松应对面试!