🚀 MyBatis 从入门到精通:万字详解(小白必看)
摘要:本文将带你深入了解 Java 持久层框架 MyBatis。从环境搭建到核心配置,再到动态 SQL 和高级映射,结合实际项目代码(学生选课/图书管理系统),手把手教你掌握 MyBatis。文末附带高频面试题,助你从容应对面试!
📖 目录
- [什么是 MyBatis?](#什么是 MyBatis?)
- 快速入门:环境搭建
- 核心配置详解 (SqlMapConfig.xml)
- [MyBatis 核心组件与生命周期](#MyBatis 核心组件与生命周期)
- [实战演练:CRUD 操作](#实战演练:CRUD 操作)
- [进阶必杀技:动态 SQL](#进阶必杀技:动态 SQL)
- 高级映射与关联查询
- [🔥 MyBatis 高频面试题](#🔥 MyBatis 高频面试题)
1. 什么是 MyBatis?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
- 免除 JDBC 代码:MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
- XML 或注解配置:可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects)为数据库中的记录。
- 半自动 ORM:与 Hibernate(全自动)不同,MyBatis 允许程序员直接编写 SQL,更加灵活,适合对 SQL 性能要求高的场景。
2. 快速入门:环境搭建
在 Maven 项目中,我们需要引入 MyBatis 和 MySQL 驱动的依赖。
2.1 引入依赖 (pom.xml)
xml
<dependencies>
<!-- MyBatis 核心包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<!-- MySQL 驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- Lombok (可选,用于简化实体类) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<!-- 日志框架 (推荐使用,方便查看 SQL) -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.36</version>
</dependency>
</dependencies>
3. 核心配置详解 (SqlMapConfig.xml)
这是 MyBatis 的全局配置文件,包含了数据库连接池、事务管理器、映射器文件路径等核心信息。
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 1. Settings: 全局设置 -->
<settings>
<!-- 开启驼峰命名自动映射:将数据库的 user_name 自动映射为 Java 的 userName -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 打印 SQL 日志到控制台 (开发环境推荐) -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!-- 2. Environments: 环境配置 (开发、测试、生产) -->
<environments default="development">
<environment id="development">
<!-- 事务管理器: JDBC (使用 Connection 的 commit/rollback) -->
<transactionManager type="JDBC"/>
<!-- 数据源: POOLED (使用连接池,复用连接,提高性能) -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/library_system?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="your_password"/>
</dataSource>
</environment>
</environments>
<!-- 3. Mappers: 注册 Mapper XML 文件 -->
<mappers>
<!-- 使用 resource 属性指定类路径下的 XML 文件 -->
<mapper resource="mapper/BookMapper.xml"/>
<mapper resource="mapper/BorrowRecordMapper.xml"/>
</mappers>
</configuration>
💡 知识点解析:
mapUnderscoreToCamelCase:非常重要!数据库字段通常是下划线命名(create_time),而 Java 属性是驼峰命名(createTime)。开启此配置后,MyBatis 会自动帮我们完成映射,不再需要手动写resultMap。POOLED:MyBatis 自带的连接池。生产环境通常会整合 Druid 或 HikariCP。
4. MyBatis 核心组件与生命周期
在代码中,我们通常这样使用 MyBatis:
java
// 1. 读取配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 创建 SqlSessionFactory (工厂模式)
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 获取 SqlSession (会话)
SqlSession sqlSession = sqlSessionFactory.openSession(true); // true 表示自动提交事务
// 4. 获取 Mapper 接口代理对象 (动态代理)
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
// 5. 执行方法
List<Book> books = bookMapper.findAll();
- SqlSessionFactoryBuilder:用完即丢,用来构建工厂。
- SqlSessionFactory :全局单例,整个应用运行期间存在,用来生产 SqlSession。
- SqlSession :线程不安全 ,用完必须关闭 (
close())。它相当于 JDBC 的Connection。 - Mapper 接口:MyBatis 通过 JDK 动态代理为接口生成实现类,我们只需要定义接口和 XML,不需要写实现类。
5. 实战演练:CRUD 操作
5.1 实体类 (Book.java)
java
@Data // Lombok 注解,自动生成 Getter/Setter/ToString
public class Book {
private Integer id;
private String title;
private String author;
private Double price;
private Integer categoryId;
}
5.2 Mapper 接口 (BookMapper.java)
java
public interface BookMapper {
// 查询所有
List<Book> findAll();
// 根据 ID 查询
Book findById(Integer id);
// 新增
int insertBook(Book book);
// 更新
int updateBook(Book book);
// 删除
int deleteBook(Integer id);
}
5.3 Mapper XML (BookMapper.xml)
xml
<mapper namespace="com.qcby.dao.BookMapper">
<!--
id: 对应接口中的方法名
resultType: 返回结果的全限定类名 (如果开启了驼峰映射,会自动匹配字段)
-->
<select id="findAll" resultType="com.qcby.entity.Book">
SELECT * FROM book
</select>
<!-- parameterType: 参数类型 (可选,MyBatis 能自动推断) -->
<select id="findById" parameterType="int" resultType="com.qcby.entity.Book">
SELECT * FROM book WHERE id = #{id}
</select>
<!--
useGeneratedKeys="true": 使用数据库自增主键
keyProperty="id": 将生成的主键值回填到 book 对象的 id 属性中
-->
<insert id="insertBook" useGeneratedKeys="true" keyProperty="id">
INSERT INTO book (title, author, price, category_id)
VALUES (#{title}, #{author}, #{price}, #{categoryId})
</insert>
</mapper>
6. 进阶必杀技:动态 SQL
MyBatis 最强大的功能之一就是动态 SQL。它摆脱了 JDBC 中拼接 SQL 字符串的痛苦。
6.1 <if> 和 <where>:多条件查询
场景:用户可能只输入了书名,也可能同时输入了书名和作者,或者什么都没输。
xml
<!-- 动态查询书籍信息 -->
<select id="findBooksByCondition" resultType="com.qcby.entity.Book">
SELECT * FROM book
<where>
<!-- if test="条件表达式": 如果为 true,则拼接标签内的 SQL -->
<if test="book.title != null and book.title != ''">
AND title LIKE concat('%', #{book.title}, '%')
</if>
<if test="book.author != null and book.author != ''">
AND author = #{book.author}
</if>
<!-- 处理日期范围 -->
<if test="startDate != null">
AND publish_date >= #{startDate}
</if>
</where>
</select>
💡 知识点:
<where>标签 :非常智能。如果标签内部有内容,它会自动插入WHERE关键字。如果内容以AND或OR开头,它会自动剔除掉第一个AND/OR,防止 SQL 语法错误。
6.2 <set>:动态更新
场景:只更新用户修改了的字段,没修改的字段保持原样。
xml
<update id="updateBook">
UPDATE book
<set>
<if test="title != null">title = #{title},</if>
<if test="author != null">author = #{author},</if>
<if test="price != null">price = #{price},</if>
</set>
WHERE id = #{id}
</update>
💡 知识点:
<set>标签 :会自动插入SET关键字,并且会自动剔除最后多余的逗号,。
6.3 <foreach>:批量插入/查询
场景:一次性插入 100 本书,或者查询 ID 在 [1, 3, 5] 中的书。
xml
<!-- 批量插入 -->
<insert id="batchInsertBooks">
INSERT INTO book (title, author, category_id, publish_date)
VALUES
<!--
collection: 集合参数的名称 (list, array 或 @Param 指定的名)
item: 当前遍历元素的别名
separator: 分隔符
-->
<foreach collection="books" item="book" separator=",">
(#{book.title}, #{book.author}, #{book.categoryId}, #{book.publishDate})
</foreach>
</insert>
7. 高级映射与关联查询
当数据库表结构比较复杂(如一对多、多对一)时,简单的 resultType 可能无法满足需求,这时需要使用 resultMap。
7.1 多对一 / 一对一 (association)
场景 :查询借阅记录 (BorrowRecord) 时,同时查出对应的用户 (User) 和书籍 (Book) 信息。
xml
<!-- 定义 ResultMap -->
<resultMap id="BorrowRecordMap" type="com.qcby.entity.BorrowRecord">
<id property="id" column="id"/>
<result property="borrowDate" column="borrow_date"/>
<!-- 关联 User 对象 -->
<association property="user" javaType="com.qcby.entity.User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
</association>
<!-- 关联 Book 对象 -->
<association property="book" javaType="com.qcby.entity.Book">
<id property="id" column="book_id"/>
<result property="title" column="book_title"/>
</association>
</resultMap>
<select id="findUserBorrowRecords" resultMap="BorrowRecordMap">
SELECT
br.*,
u.id as user_id, u.name as user_name,
b.id as book_id, b.title as book_title
FROM borrow_record br
LEFT JOIN user u ON br.user_id = u.id
LEFT JOIN book b ON br.book_id = b.id
</select>
8. 🔥 MyBatis 高频面试题
Q1: #{} 和 ${} 的区别是什么?(必问)
#{}:是预编译处理 (PreparedStatement)。MyBatis 会将其替换为?,然后调用 JDBC 的set方法赋值。- 优点 :防止 SQL 注入,安全性高。
- 适用:大部分参数传递。
${}:是字符串替换 。MyBatis 会直接将变量的值拼接到 SQL 语句中。- 缺点:存在 SQL 注入风险。
- 适用 :动态表名、列名、排序字段(如
ORDER BY ${columnName})。
Q2: MyBatis 的一级缓存和二级缓存?
- 一级缓存 (Local Cache) :
- 作用域 :SqlSession 级别。
- 默认开启。
- 同一个 SqlSession 中执行相同的 SQL,第一次查库,后续直接从缓存取。
commit、close或update/insert/delete操作会清空一级缓存。
- 二级缓存 (Global Cache) :
- 作用域 :Mapper (Namespace) 级别。
- 默认关闭 ,需要在 XML 中配置
<cache/>开启。 - 多个 SqlSession 共享。数据需要实现
Serializable接口。
Q3: Dao 接口的工作原理是什么?
- Dao 接口(Mapper 接口)没有实现类,MyBatis 使用 JDK 动态代理 为接口生成了一个代理对象。
- 当调用接口方法时,代理对象会拦截调用,根据方法名找到对应的 XML 标签(MappedStatement),然后执行 SQL 并处理结果。
Q4: 如何获取自动生成的主键?
- 在
<insert>标签中使用useGeneratedKeys="true"和keyProperty="id"。 - 执行插入后,MyBatis 会将数据库生成的主键值回填到传入的实体对象的
id属性中。
总结:MyBatis 是 Java 开发中必不可少的技能。掌握好动态 SQL 和 ResultMap,能让你在处理复杂业务时游刃有余。希望这篇教程能帮你建立起完整的 MyBatis 知识体系!🚀