MyBatis 通过 XML 或注解配置 SQL,并自动处理参数映射和结果集映射,大大简化了数据库操作:
flowchart LR
A["☕️ Java 代码"] --> B["📋 MyBatis"]
B --> C["🗄️ 数据库"]
A -->|1. 调用 API| D["SqlSession"]
D -->|2. 执行 Mapper| E["SQL XML / 注解"]
E -->|3. 映射| F["输入参数\n自动映射"]
E -->|4. 映射| G["结果集\n自动映射"]
F --> D
G --> D
D -->|5. 返回结果| A
style A fill:#e3f2fd
style B fill:#c8e6c9
style C fill:#f8bbd0
1.3 MyBatis vs Hibernate 对比
flowchart LR
A["ORM 框架对比"] --> B["Hibernate\n全自动 ORM"]
A --> C["MyBatis\n半自动 ORM"]
B --> B1["✅ 全自动\n无需写 SQL"]
B --> B2["✅ 屏蔽数据库差异\n数据库无关"]
B --> B3["❌ SQL 不可控\n性能调优困难"]
B --> B4["❌ 学习成本高\n配置复杂"]
C --> C1["✅ SQL 完全可控\n性能优异"]
C --> C2["✅ 学习曲线平缓\n上手快"]
C --> C3["✅ 灵活定制 SQL\n复杂查询轻松"]
C --> C4["❌ 需要手写 SQL\n有一定工作量"]
style A fill:#fff3e0
style B fill:#ffcdd2
style C fill:#c8e6c9
flowchart TD
A["📁 mybatis-project"] --> B["📁 src/main/java"]
A --> C["📁 src/main/resources"]
B --> D["📁 com.example.mapper"]
B --> E["📁 com.example.entity"]
B --> F["📁 com.example.service"]
B --> G["📁 com.example.util"]
C --> H["📄 mybatis-config.xml"]
C --> I["📄 mapper/UserMapper.xml"]
C --> J["📄 jdbc.properties"]
D --> D1["UserMapper.java"]
D1 --> D2["UserMapper.xml"]
style A fill:#e3f2fd
flowchart TD
A["🔧 MyBatis 核心组件"] --> B["📋 SqlSessionFactoryBuilder"]
A --> C["🏭 SqlSessionFactory"]
A --> D["📦 SqlSession"]
A --> E["🗂️ Mapper 接口"]
A --> F["📝 Mapper XML"]
B --> B1["构建 SqlSessionFactory"]
B1 --> C
C -->|每次创建| D
D -->|调用| E
E -->|执行| F
style A fill:#fff3e0
style C fill:#c8e6c9
style D fill:#c8e6c9
<!-- namespace 必须与 Mapper 接口全限定名一致 --> <mappernamespace="com.example.mapper.UserMapper"> <!-- ========== 查询操作 ========== --> <!-- resultType 指定返回类型(单条记录的类型) --> <selectid="findById"resultType="User"> SELECT * FROM users WHERE id = #{id} </select> <!-- 查询所有用户 --> <selectid="findAll"resultType="User"> SELECT id, username, password, email, phone, status, created_at AS createdAt, updated_at AS updatedAt FROM users ORDER BY id DESC </select> <!-- 单参数查询:使用 #{参数名} 占位 --> <selectid="findByUsername"resultType="User"> SELECT * FROM users WHERE username = #{username} </select> <!-- 多参数查询:@Param 注解指定参数名 --> <selectid="findByConditions"resultType="User"> SELECT * FROM users WHERE 1=1 <iftest="username != null and username != ''"> AND username LIKE CONCAT('%', #{username}, '%') </if> <iftest="status != null"> AND status = #{status} </if> ORDER BY created_at DESC </select> <!-- 使用 Map 作为参数 --> <selectid="findByMap"resultType="User"> SELECT * FROM users WHERE 1=1 <iftest="username != null"> AND username LIKE CONCAT('%', #{username}, '%') </if> <iftest="status != null"> AND status = #{status} </if> ORDER BY id DESC </select> <!-- 统计查询:返回简单类型 --> <selectid="count"resultType="int"> SELECT COUNT(*) FROM users </select> <!-- ========== 插入操作 ========== --> <!-- 插入用户(自增主键会自动返回到对象中) --> <insertid="insert"parameterType="User"> INSERT INTO users (username, password, email, phone, status) VALUES (#{username}, #{password}, #{email}, #{phone}, #{status}) </insert> <!-- 插入并获取自增主键(方式一:useGeneratedKeys) --> <insertid="insertAndReturnId"parameterType="User" useGeneratedKeys="true"keyProperty="id"> INSERT INTO users (username, password, email, phone, status) VALUES (#{username}, #{password}, #{email}, #{phone}, #{status}) </insert> <!-- 插入并获取自增主键(方式二:selectKey) --> <insertid="insertAndReturnId2"parameterType="User"> <selectKeykeyProperty="id"resultType="long"order="AFTER"> SELECT LAST_INSERT_ID() </selectKey> INSERT INTO users (username, password, email, phone, status) VALUES (#{username}, #{password}, #{email}, #{phone}, #{status}) </insert> <!-- 批量插入 --> <insertid="batchInsert"parameterType="java.util.List"> INSERT INTO users (username, password, email, phone, status) VALUES <foreachcollection="list"item="user"separator=","> (#{user.username}, #{user.password}, #{user.email}, #{user.phone}, #{user.status}) </foreach> </insert> <!-- ========== 更新操作 ========== --> <!-- 更新用户 --> <updateid="update"parameterType="User"> UPDATE users SET username = #{username}, password = #{password}, email = #{email}, phone = #{phone}, status = #{status} WHERE id = #{id} </update> <!-- 动态更新(只更新非空字段) --> <updateid="dynamicUpdate"parameterType="User"> UPDATE users <set> <iftest="username != null and username != ''"> username = #{username}, </if> <iftest="password != null and password != ''"> password = #{password}, </if> <iftest="email != null"> email = #{email}, </if> <iftest="phone != null"> phone = #{phone}, </if> <iftest="status != null"> status = #{status}, </if> </set> WHERE id = #{id} </update> <!-- ========== 删除操作 ========== --> <!-- 删除用户 --> <deleteid="deleteById"> DELETE FROM users WHERE id = #{id} </delete> <!-- 批量删除 --> <deleteid="batchDelete"> DELETE FROM users WHERE id IN <foreachcollection="ids"item="id"open="("separator=","close=")"> #{id} </foreach> </delete> </mapper>
4.2 #{} vs ${} 区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<!-- #{}:预编译参数绑定(推荐,防止 SQL 注入) --> <!-- 最终执行:SELECT * FROM users WHERE id = ? --> <selectid="findById"resultType="User"> SELECT * FROM users WHERE id = #{id} </select>
<!-- ${}:直接字符串替换(存在 SQL 注入风险,仅适用于固定值) --> <!-- 最终执行:SELECT * FROM users WHERE id = 1 --> <selectid="findByIdFixed"resultType="User"> SELECT * FROM users WHERE id = ${id} </select>
<!-- 使用 ${} 的典型场景:动态表名、排序字段 --> <selectid="findAll"resultType="User"> SELECT * FROM users ORDER BY ${sortField} ${sortOrder} </select>
<!-- ArticleMapper.xml --> <mappernamespace="com.example.mapper.ArticleMapper"> <!-- 按用户 ID 查询文章列表 --> <selectid="findByUserId"resultType="Article"> SELECT * FROM articles WHERE user_id = #{userId} ORDER BY created_at DESC </select> <!-- 查询某用户最新文章 --> <selectid="findLatestByUserId"resultType="Article"> SELECT * FROM articles WHERE user_id = #{userId} ORDER BY created_at DESC LIMIT 1 </select> </mapper>
<selectid="findUserWithArticles2"resultMap="UserWithArticlesMap2"> SELECT u.id, u.username, u.email, a.id AS article_id, a.title, a.content, a.views FROM users u LEFT JOIN articles a ON u.id = a.user_id WHERE u.id = #{id} </select>
五、动态 SQL:让 SQL 灵动起来
5.1 动态 SQL 核心标签
MyBatis 的动态 SQL 是其最强大的特性之一,可以根据条件动态拼接 SQL:
flowchart TD
A["🔄 动态 SQL 标签"] --> B["if\n条件判断"]
A --> C["where\n智能 WHERE"]
A --> D["set\n智能 SET"]
A --> E["foreach\n循环遍历"]
A --> F["trim\n自定义裁剪"]
A --> G["choose\n选择分支"]
B --> B1["动态添加条件"]
C --> C1["处理多余 AND/OR"]
D --> D1["处理多余 逗号"]
E --> E1["批量操作/IN 查询"]
F --> F1["自定义格式"]
G --> G1["多选一"]
style A fill:#fff3e0
5.2 if 标签:条件判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<!-- 使用 if 标签动态添加查询条件 --> <selectid="findByConditions"resultType="User"> SELECT * FROM users WHERE 1=1 <iftest="username != null and username.trim() != ''"> AND username LIKE CONCAT('%', #{username}, '%') </if> <iftest="status != null"> AND status = #{status} </if> <iftest="email != null"> AND email = #{email} </if> <iftest="createdAfter != null"> AND created_at >= #{createdAfter} </if> ORDER BY created_at DESC </select>
<!-- where 标签:自动处理 WHERE 和多余 AND/OR --> <selectid="findByConditions2"resultType="User"> SELECT * FROM users <where> <iftest="username != null and username != ''"> AND username LIKE CONCAT('%', #{username}, '%') </if> <iftest="status != null"> AND status = #{status} </if> <iftest="email != null"> AND email = #{email} </if> </where> ORDER BY created_at DESC </select>
<!-- set 标签:自动处理 UPDATE 语句中的多余逗号 --> <updateid="dynamicUpdate"parameterType="User"> UPDATE users <set> <iftest="username != null and username != ''"> username = #{username}, </if> <iftest="password != null and password != ''"> password = #{password}, </if> <iftest="email != null"> email = #{email}, </if> <iftest="phone != null"> phone = #{phone}, </if> <iftest="status != null"> status = #{status}, </if> </set> WHERE id = #{id} </update>
<!-- trim 标签:自定义前后缀处理 --> <!-- 等价于 where 标签 --> <trimprefix="WHERE"prefixOverrides="AND |OR "> <iftest="username != null">AND username = #{username}</if> </trim>
<!-- 等价于 set 标签 --> <trimprefix="SET"suffix="WHERE id = #{id}"suffixOverrides=","> <iftest="username != null">username = #{username},</if> <iftest="email != null">email = #{email},</if> </trim>
<!-- 批量删除(IN 查询) --> <deleteid="batchDelete"> DELETE FROM users WHERE id IN <foreachcollection="ids"item="id"open="("separator=","close=")"> #{id} </foreach> </delete>
<!-- 批量更新状态 --> <updateid="batchUpdateStatus"> UPDATE users SET status = #{status} WHERE id IN <foreachcollection="ids"item="id"open="("separator=","close=")"> #{id} </foreach> </update>
<selectid="findUserWithArticles2"resultMap="UserWithArticlesMap2"> SELECT u.id, u.username, a.id AS a_id, a.title, a.content FROM users u LEFT JOIN articles a ON u.id = a.user_id WHERE u.id = #{id} </select>
<!-- 1. 使用分页查询避免全表扫描 --> <selectid="findByPage"resultType="User"> SELECT * FROM users <where> <iftest="status != null"> status = #{status} </if> </where> ORDER BY id DESC LIMIT #{offset}, #{limit} </select>
<!-- 2. 使用 LIMIT 限制返回条数 --> <selectid="findTop10"resultType="User"> SELECT * FROM users ORDER BY created_at DESC LIMIT 10 </select>
<!-- 3. 使用 EXISTS 替代 IN(子查询) --> <!-- 低效:IN 子查询可能先执行完再匹配 --> <selectid="findWithIn"resultType="User"> SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE status = 'paid') </select>
<!-- 高效:EXISTS 只要找到一个匹配就停止 --> <selectid="findWithExists"resultType="User"> SELECT * FROM users u WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.status = 'paid' ) </select>
flowchart TD
A["MyBatis-Plus"] --> B["🌟 无侵入"]
A --> C["🥰 损耗小"]
A --> D["🛡️ 强大的 CRUD"]
A --> E["⚡ 内置通用 Mapper"]
A --> F["🎮 支持 Lambda"]
A --> G["🔌 支持 ActiveRecord"]
B --> B1["只做增强\n不改变 MyBatis"]
C --> C1["启动即会自动\n注入基本 CRUD"]
D --> D1["内置通用\nService/Mapper"]
E --> E1["通用 Mapper\n拿来即用"]
F --> F1["Lambda 表达式\n编写条件更简单"]
G --> G1["实体类\n即可操作数据库"]
// LIKE 查询 wrapper.like(User::getUsername, "test"); // LIKE '%test%' wrapper.likeLeft(User::getEmail, "@gmail.com"); // LIKE '%@gmail.com' wrapper.likeRight(User::getEmail, "test"); // LIKE 'test%'
// IN 查询 wrapper.in(User::getId, Arrays.asList(1L, 2L, 3L));
// BETWEEN 查询 wrapper.between(User::getCreatedAt, startDate, endDate);
// IS NULL / IS NOT NULL wrapper.isNull(User::getEmail); wrapper.isNotNull(User::getPhone);
flowchart TD
A["❓ 常见问题"] --> B["SQL 注入"]
A --> C["N+1 查询"]
A --> D["资源泄漏"]
A --> E["事务失效"]
B --> B1["✅ 使用 #{} 占位符\n拒绝 ${} 拼接"]
C --> C1["✅ 使用嵌套 result\n或 batch 查询"]
D --> D1["✅ 使用 try-with-resources\n自动关闭 SqlSession"]
E --> E1["✅ Spring 声明式事务\n@Transactional"]
style A fill:#fff3e0
style B fill:#ffcdd2
style C fill:#fff3e0
style D fill:#e3f2fd
style E fill:#c8e6c9
mindmap
root((MyBatis 核心知识))
基础入门
JDBC vs MyBatis
核心组件
环境搭建
CRUD 操作
Mapper 接口
Mapper XML
#{} vs ${}
resultMap 映射
动态 SQL
if/where/set
foreach
choose
trim
关联映射
一对一 association
一对多 collection
嵌套 select
嵌套 result
高级特性
类型处理器
延迟加载
缓存机制
MyBatis-Plus
BaseMapper
Lambda 查询
分页插件
自动填充
性能优化
分页查询
批量操作
N+1 问题
SQL 日志
11.2 学习路线建议
flowchart LR
A["第一阶段\n入门"] --> B["第二阶段\n基础CRUD"]
B --> C["第三阶段\n动态SQL"]
C --> D["第四阶段\n关联映射"]
D --> E["第五阶段\n高级特性"]
E --> F["第六阶段\nMyBatis-Plus"]
A --> A1["MyBatis 概述"]
A --> A2["环境搭建"]
B --> B1["Mapper 编写"]
B --> B2["参数映射"]
B --> B3["结果映射"]
C --> C1["if 条件"]
C --> C2["foreach 循环"]
C --> C3["where/set"]
D --> D1["一对一"]
D --> D2["一对多"]
D --> D3["嵌套查询"]
E --> E1["缓存机制"]
E --> E2["延迟加载"]
E --> E3["类型处理器"]
F --> F1["Lambda 查询"]
F --> F2["CRUD 增强"]
F --> F3["分页插件"]
style A fill:#e3f2fd
style B fill:#c8e6c9
style C fill:#fff3e0
style D fill:#f8bbd0
style E fill:#ffcdd2
style F fill:#e3f2fd
11.3 下一步推荐学习
📖 MyBatis 源码解析:深入理解核心原理
📖 MyBatis-Plus 源码:学习增强工具的设计思路
📖 Spring Data JPA:对比学习 Hibernate 风格的 ORM
📖 泛型 CRUD 封装:设计通用的 BaseMapper
📖 分布式数据库:学习分库分表、读写分离方案
💡 写给读者的话:MyBatis 是 Java 后端开发中承上启下的关键框架——向下封装了 JDBC,向上支撑了 Spring Data 等更高级的 ORM。掌握 MyBatis 的原理和使用,能让你在学习和工作中游刃有余。纸上得来终觉浅,绝知此事要躬行,快动手实践吧!🚀