MyBatis 从入门到精通(详解版)

🚀 MyBatis 从入门到精通:万字详解(小白必看)

摘要:本文将带你深入了解 Java 持久层框架 MyBatis。从环境搭建到核心配置,再到动态 SQL 和高级映射,结合实际项目代码(学生选课/图书管理系统),手把手教你掌握 MyBatis。文末附带高频面试题,助你从容应对面试!


📖 目录

  1. [什么是 MyBatis?](#什么是 MyBatis?)
  2. 快速入门:环境搭建
  3. 核心配置详解 (SqlMapConfig.xml)
  4. [MyBatis 核心组件与生命周期](#MyBatis 核心组件与生命周期)
  5. [实战演练:CRUD 操作](#实战演练:CRUD 操作)
  6. [进阶必杀技:动态 SQL](#进阶必杀技:动态 SQL)
  7. 高级映射与关联查询
  8. [🔥 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&amp;useUnicode=true&amp;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 关键字。如果内容以 ANDOR 开头,它会自动剔除掉第一个 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,第一次查库,后续直接从缓存取。
    • commitcloseupdate/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 知识体系!🚀

相关推荐
weixin_4250230013 小时前
Spring Boot + MyBatis Plus JOIN 分页多表查询项目文档
spring boot·后端·mybatis
undsky19 小时前
【RuoYi-Eggjs】:多数据库与 MyBatis 特性详解
node.js·mybatis·egg.js
柒.梧.20 小时前
MyBatis注解开发全解析:从基础CRUD到关联查询与延迟加载
mybatis
程序员侠客行21 小时前
Mybatis入门到精通 一
java·架构·mybatis
又是忙碌的一天1 天前
Myvatis 动态查询及关联查询
java·数据库·mybatis
柒.梧.1 天前
深度解析MyBatis缓存机制:从基础原理到实战配置
缓存·mybatis
一直都在5721 天前
MyBatis缓存
缓存·mybatis
7澄11 天前
MyBatis缓存详解:一级缓存、二级缓存与实战优化
缓存·mybatis·一级缓存
胡闹541 天前
MyBatis-Plus 更新字段为 null 为何失效?
java·数据库·mybatis