MyBatis简介
导学
通过本节学习,你将能够:
- 准确描述 MyBatis 在 Java 技术栈中的定位与职责
- 对比 JDBC、MyBatis、Hibernate 三种持久层方案的核心差异
- 理解 MyBatis "SQL 与代码解耦" 的设计哲学
- 判断实际项目中何时选择 MyBatis 而非其他 ORM 框架
定义
什么是 MyBatis
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码、参数设置和结果集获取工作,通过简单的 XML 或注解即可将原始类型、接口和 Java POJO 映射为数据库中的记录。
它解决了什么痛点
在 MyBatis 出现之前,Java 开发者直接使用 JDBC 访问数据库,面临以下典型痛苦:
| 痛点 | JDBC 原始写法 | 带来的问题 |
|---|---|---|
| SQL 散落在代码中 | 字符串拼接在 Java 代码里 | 修改 SQL 需重新编译部署 |
| 参数手动绑定 | ps.setInt(1, id)、ps.setString(2, name) | 参数多了极易错位,维护困难 |
| 结果集手动提取 | rs.getInt("id")、rs.getString("name") | 列名变更后到处改代码 |
| 无内置缓存 | 每次查询都走数据库 | 性能差,重复查询浪费资源 |
| 异常处理繁琐 | 每个操作都要 try-catch-finally | 代码臃肿,资源泄漏风险高 |
MyBatis 的核心价值:让开发者专注于 SQL 本身,而将参数绑定、结果映射、资源管理等重复劳动交给框架。
核心原理
MyBatis 核心组件关系
MyBatis 的运行时架构由一系列职责清晰的组件构成,它们协同完成从 Java 方法调用到 SQL 执行、结果返回的完整流程:
组件职责说明:
| 组件 | 职责 | 作用域 |
|---|---|---|
| SqlSessionFactory | 创建 SqlSession 的工厂,解析全局配置 | 应用级,全局唯一 |
| SqlSession | 与数据库交互的会话,执行 SQL | 请求级,线程不安全 |
| Executor | SQL 执行器,调度 StatementHandler | 会话级 |
| StatementHandler | 封装 JDBC Statement 操作 | 语句级 |
| ParameterHandler | 将 Java 参数转换为 JDBC 参数 | 语句级 |
| ResultSetHandler | 将 JDBC 结果集转换为 Java 对象 | 语句级 |
| 一级缓存 | SqlSession 级别的缓存,默认开启 | 会话级 |
| 二级缓存 | Mapper 级别的缓存,需手动配置 | 命名空间级 |
与 JDBC、Hibernate 的对比
| 维度 | JDBC | MyBatis | Hibernate |
|---|---|---|---|
| SQL 控制 | 完全手写,硬编码在 Java 中 | 手写 SQL,XML/注解管理,与代码解耦 | 自动生成 HQL/JPQL,或 Criteria API |
| 参数映射 | 手动 ps.setXXX() | #{} 自动类型映射 | 全自动字段映射 |
| 结果映射 | 手动 rs.getXXX() | resultMap/resultType 自动映射 | 全自动 ORM 映射 |
| 缓存机制 | 无内置缓存 | 一级缓存 + 二级缓存 | 多级缓存体系 |
| 学习曲线 | 低,但 API 繁琐 | 中等,需掌握 XML/注解配置 | 高,需理解对象状态、HQL |
| 灵活性 | 极高,但代价是重复代码 | 高,SQL 完全可控 | 中,复杂 SQL 需原生查询 |
| 适用场景 | 超轻量、极致性能调优 | 企业级应用,SQL 需优化和审计 | 快速开发,CRUD 为主 |
MyBatis 的核心理念可以概括为三句话:
- 不对应用程序或数据库强加影响 —— 不强制你改变数据库设计,也不强制你改变 Java 对象设计
- SQL 写在 XML 里与代码解耦 —— 运维人员可直接审阅和调优 SQL,无需重新编译 Java 代码
- 提供映射标签支持 ORM 字段关系映射 —— 复杂关联查询(一对一、一对多)可通过
resultMap优雅处理
完整示例
场景说明
乐途公司技术部要开发一个学生管理系统,需要查询学生信息。我们先看 JDBC 原始写法,再看 MyBatis 写法,直观感受差异。
操作前的数据库表结构及初始数据
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
age INT,
major VARCHAR(20),
score DECIMAL(5,2)
);
INSERT INTO student (name, age, major, score) VALUES
('大翔', 22, '计算机科学', 95.5),
('白歌', 21, '软件工程', 88.0),
('小崔', 20, '计算机科学', 92.0),
('黄俪', 21, '信息安全', 90.5),
('李眉', 22, '软件工程', 87.0);
当前数据状态:
| id | name | age | major | score |
|---|---|---|---|---|
| 1 | 大翔 | 22 | 计算机科学 | 95.50 |
| 2 | 白歌 | 21 | 软件工程 | 88.00 |
| 3 | 小崔 | 20 | 计算机科学 | 92.00 |
| 4 | 黄俪 | 21 | 信息安全 | 90.50 |
| 5 | 李眉 | 22 | 软件工程 | 87.00 |
JDBC 原始写法(痛点展示)
public Student findById_JDBC(int id) {
String sql = "SELECT id, name, age, major, score FROM student WHERE id = ?";
Student student = null;
// 1. 加载驱动、获取连接 —— 样板代码
try (Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/school", "root", "123456");
PreparedStatement ps = conn.prepareStatement(sql)) {
// 2. 手动设置参数 —— 参数多了极易错位
ps.setInt(1, id);
// 3. 执行查询
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
student = new Student();
// 4. 手动提取结果集 —— 列名变更后到处改
student.setId(rs.getInt("id"));
student.setName(rs.getString("name"));
student.setAge(rs.getInt("age"));
student.setMajor(rs.getString("major"));
student.setScore(rs.getBigDecimal("score"));
}
}
} catch (SQLException e) {
// 5. 异常处理 —— 每个方法都要写
e.printStackTrace();
}
// 6. 连接、语句、结果集都要手动关闭 —— 遗漏即泄漏
return student;
}
MyBatis 写法(框架托管)
StudentMapper.xml
<?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">
<mapper namespace="com.fly.mapper.StudentMapper">
<!-- SQL 与 Java 代码解耦,参数自动映射,结果自动封装 -->
<select id="findById" resultType="com.fly.entity.Student">
SELECT id, name, age, major, score
FROM student
WHERE id = #{id}
</select>
</mapper>
Java 调用代码
public Student findById_MyBatis(int id) {
try (SqlSession session = sqlSessionFactory.openSession()) {
// 一行代码完成:参数绑定、SQL 执行、结果映射、资源释放
StudentMapper mapper = session.getMapper(StudentMapper.class);
return mapper.findById(id);
}
}
实际执行结果及分析
执行 findById(1) 后控制台输出:
==> Preparing: SELECT id, name, age, major, score FROM student WHERE id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, age, major, score
<== Row: 1, 大翔, 22, 计算机科学, 95.50
<== Total: 1
返回的 Java 对象:
| 属性 | 值 |
|---|---|
| id | 1 |
| name | 大翔 |
| age | 22 |
| major | 计算机科学 |
| score | 95.50 |
对比分析:
| 维度 | JDBC 写法 | MyBatis 写法 |
|---|---|---|
| 代码行数 | 约 25 行 | 约 5 行 |
| SQL 位置 | 硬编码在 Java 中 | 独立在 XML 中 |
| 参数处理 | 手动 ps.setInt(1, id) | 自动 #{id} |
| 结果映射 | 手动 5 个 rs.getXXX() | 自动 resultType |
| 资源释放 | 手动 try-finally | 自动 try-with-resources |
| 可维护性 | 差 | 优 |
易错场景 / 常见误区
| 误区 | 错误认知 | 正解 |
|---|---|---|
| MyBatis 是全自动 ORM | 以为不需要写 SQL | MyBatis 是半自动 ORM,SQL 必须自己写,框架只负责映射和托管 |
| MyBatis 比 Hibernate 落后 | 认为自动生成 SQL 更高级 | 复杂业务、SQL 优化场景下,手写 SQL 是优势而非劣势 |
| 用 MyBatis 就不用懂 JDBC | 完全忽视底层原理 | 排查连接泄漏、死锁、SQL 注入等问题时,JDBC 知识必不可少 |
| 所有项目都用 MyBatis | 技术选型一刀切 | 纯 CRUD 后台、快速原型可考虑 Hibernate/JPA;遗留系统、复杂查询优选 MyBatis |
面试考点
Q1:MyBatis 与 Hibernate 的核心区别是什么?什么时候选 MyBatis?
A: MyBatis 是半自动 ORM,SQL 由开发者手写,通过 XML/注解配置参数映射和结果映射,灵活性高,适合 SQL 需要精细优化、审计或频繁变更的场景。Hibernate 是全自动 ORM,通过 HQL/JPQL 或 Criteria 自动生成 SQL,开发效率高,适合以 CRUD 为主、数据库结构稳定的业务系统。当项目需要 DBA 审阅 SQL、存在大量多表关联复杂查询、或数据库设计不完全遵循对象模型时,优先选择 MyBatis。
Q2:MyBatis 的 #{} 和 ${} 有什么区别?
A:
#{}是预编译处理,MyBatis 会将其替换为?占位符,通过PreparedStatement设置参数,能防止 SQL 注入,适用于传入普通参数值。${}是字符串直接替换,将参数原样拼接到 SQL 中,存在 SQL 注入风险,仅用于传入表名、列名等不能预编译的场景。
Q3:为什么说 MyBatis 不对应用程序或数据库强加影响?
A: MyBatis 不要求数据库表结构与 Java 对象严格一一对应(可通过
resultMap灵活映射),也不要求开发者必须使用某种特定的对象设计模式。它只负责将开发者手写的 SQL 与 Java 方法进行绑定,并自动完成参数和结果的转换,数据库设计和应用设计保持独立。
Q4:MyBatis 的一级缓存和二级缓存有什么区别?
A: 一级缓存是 SqlSession 级别的缓存,默认开启,生命周期与 SqlSession 相同,会话关闭即清空,主要用于避免同一会话内的重复查询。二级缓存是 Mapper 命名空间级别的缓存,需要手动在 XML 中配置
<cache/>,多个 SqlSession 可共享,适用于读多写少、数据变更不频繁的场景。
小结
本节从 JDBC 的繁琐痛点出发,介绍了 MyBatis 作为半自动 ORM 框架的定位:它让开发者保留对 SQL 的完全控制权,同时将参数绑定、结果映射、资源管理等重复工作交给框架。通过与 JDBC、Hibernate 的横向对比,我们明确了 MyBatis 的适用边界。核心组件关系图展示了 MyBatis 内部的协作架构,为后续深入各组件打下基础。
关键记忆点:
- MyBatis = 手写 SQL + 自动映射 + 资源托管
- 半自动 ORM 的核心优势:SQL 可控、与代码解耦、学习曲线适中
- 四大核心组件:SqlSessionFactory → SqlSession → Executor → StatementHandler
下一章引子
了解了 MyBatis "是什么" 和 "为什么用" 之后,下一步就是动手实践。下一节将带你完成 MyBatis 的环境搭建——从 Maven 依赖引入、mybatis-config.xml 核心配置,到标准项目目录结构的建立。准备好 IDE,我们即将写出第一个可运行的 MyBatis 程序。