🚀 MyBatis 从入门到精通:万字详解(小白必看)
摘要:本文将带你深入了解 Java 持久层框架 MyBatis。从环境搭建到核心配置,再到动态 SQL 和高级映射,结合实际项目代码(学生选课/图书管理系统),手把手教你掌握 MyBatis。文末附带高频面试题,助你从容应对面试!
📖 目录
- 什么是 MyBatis?
- 快速入门:环境搭建
- 核心配置详解 (SqlMapConfig.xml)
- MyBatis 核心组件与生命周期
- 实战演练:CRUD 操作
- 进阶必杀技:动态 SQL
- 高级映射与关联查询
- 🔥 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)
<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 version="1.0" encoding="UTF-8"?><!DOCTYPEconfigurationPUBLIC"-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><!-- 1. Settings: 全局设置 --><settings><!-- 开启驼峰命名自动映射:将数据库的 user_name 自动映射为 Java 的 userName --><settingname="mapUnderscoreToCamelCase"value="true"/><!-- 打印 SQL 日志到控制台 (开发环境推荐) --><settingname="logImpl"value="STDOUT_LOGGING"/></settings><!-- 2. Environments: 环境配置 (开发、测试、生产) --><environmentsdefault="development"><environmentid="development"><!-- 事务管理器: JDBC (使用 Connection 的 commit/rollback) --><transactionManagertype="JDBC"/><!-- 数据源: POOLED (使用连接池,复用连接,提高性能) --><dataSourcetype="POOLED"><propertyname="driver"value="com.mysql.cj.jdbc.Driver"/><propertyname="url"value="jdbc:mysql://localhost:3306/library_system?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8"/><propertyname="username"value="root"/><propertyname="password"value="your_password"/></dataSource></environment></environments><!-- 3. Mappers: 注册 Mapper XML 文件 --><mappers><!-- 使用 resource 属性指定类路径下的 XML 文件 --><mapperresource="mapper/BookMapper.xml"/><mapperresource="mapper/BorrowRecordMapper.xml"/></mappers></configuration>💡 知识点解析:
mapUnderscoreToCamelCase:非常重要!数据库字段通常是下划线命名(create_time),而 Java 属性是驼峰命名(createTime)。开启此配置后,MyBatis 会自动帮我们完成映射,不再需要手动写resultMap。POOLED:MyBatis 自带的连接池。生产环境通常会整合 Druid 或 HikariCP。
4. MyBatis 核心组件与生命周期
在代码中,我们通常这样使用 MyBatis:
// 1. 读取配置文件InputStreaminputStream=Resources.getResourceAsStream("SqlMapConfig.xml");// 2. 创建 SqlSessionFactory (工厂模式)SqlSessionFactorysqlSessionFactory=newSqlSessionFactoryBuilder().build(inputStream);// 3. 获取 SqlSession (会话)SqlSessionsqlSession=sqlSessionFactory.openSession(true);// true 表示自动提交事务// 4. 获取 Mapper 接口代理对象 (动态代理)BookMapperbookMapper=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)
@Data// Lombok 注解,自动生成 Getter/Setter/ToStringpublicclassBook{privateIntegerid;privateStringtitle;privateStringauthor;privateDoubleprice;privateIntegercategoryId;}5.2 Mapper 接口 (BookMapper.java)
publicinterfaceBookMapper{// 查询所有List<Book>findAll();// 根据 ID 查询BookfindById(Integerid);// 新增intinsertBook(Bookbook);// 更新intupdateBook(Bookbook);// 删除intdeleteBook(Integerid);}5.3 Mapper XML (BookMapper.xml)
<mappernamespace="com.qcby.dao.BookMapper"><!-- id: 对应接口中的方法名 resultType: 返回结果的全限定类名 (如果开启了驼峰映射,会自动匹配字段) --><selectid="findAll"resultType="com.qcby.entity.Book">SELECT * FROM book</select><!-- parameterType: 参数类型 (可选,MyBatis 能自动推断) --><selectid="findById"parameterType="int"resultType="com.qcby.entity.Book">SELECT * FROM book WHERE id = #{id}</select><!-- useGeneratedKeys="true": 使用数据库自增主键 keyProperty="id": 将生成的主键值回填到 book 对象的 id 属性中 --><insertid="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>:多条件查询
场景:用户可能只输入了书名,也可能同时输入了书名和作者,或者什么都没输。
<!-- 动态查询书籍信息 --><selectid="findBooksByCondition"resultType="com.qcby.entity.Book">SELECT * FROM book<where><!-- if test="条件表达式": 如果为 true,则拼接标签内的 SQL --><iftest="book.title != null and book.title !=''">AND title LIKE concat('%', #{book.title}, '%')</if><iftest="book.author != null and book.author !=''">AND author = #{book.author}</if><!-- 处理日期范围 --><iftest="startDate != null">AND publish_date >= #{startDate}</if></where></select>💡 知识点:
<where>标签:非常智能。如果标签内部有内容,它会自动插入WHERE关键字。如果内容以AND或OR开头,它会自动剔除掉第一个AND/OR,防止 SQL 语法错误。
6.2<set>:动态更新
场景:只更新用户修改了的字段,没修改的字段保持原样。
<updateid="updateBook">UPDATE book<set><iftest="title != null">title = #{title},</if><iftest="author != null">author = #{author},</if><iftest="price != null">price = #{price},</if></set>WHERE id = #{id}</update>💡 知识点:
<set>标签:会自动插入SET关键字,并且会自动剔除最后多余的逗号,。
6.3<foreach>:批量插入/查询
场景:一次性插入 100 本书,或者查询 ID 在 [1, 3, 5] 中的书。
<!-- 批量插入 --><insertid="batchInsertBooks">INSERT INTO book (title, author, category_id, publish_date) VALUES<!-- collection: 集合参数的名称 (list, array 或 @Param 指定的名) item: 当前遍历元素的别名 separator: 分隔符 --><foreachcollection="books"item="book"separator=",">(#{book.title}, #{book.author}, #{book.categoryId}, #{book.publishDate})</foreach></insert>7. 高级映射与关联查询
当数据库表结构比较复杂(如一对多、多对一)时,简单的resultType可能无法满足需求,这时需要使用resultMap。
7.1 多对一 / 一对一 (association)
场景:查询借阅记录 (BorrowRecord) 时,同时查出对应的用户 (User) 和书籍 (Book) 信息。
<!-- 定义 ResultMap --><resultMapid="BorrowRecordMap"type="com.qcby.entity.BorrowRecord"><idproperty="id"column="id"/><resultproperty="borrowDate"column="borrow_date"/><!-- 关联 User 对象 --><associationproperty="user"javaType="com.qcby.entity.User"><idproperty="id"column="user_id"/><resultproperty="name"column="user_name"/></association><!-- 关联 Book 对象 --><associationproperty="book"javaType="com.qcby.entity.Book"><idproperty="id"column="book_id"/><resultproperty="title"column="book_title"/></association></resultMap><selectid="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 知识体系!🚀