MyBatis 基础教程:Java 持久层框架入门 —— 一个前 PM 转码农的血泪实战笔记
上周五晚上 11 点,我正窝在公司楼下那家 24 小时便利店旁的长椅上啃着冷掉的关东煮(别问,问就是 deadline 逼的),手机突然弹出一条企业微信消息:“线上用户运营数据接口超时,QPS 掉了一半”。作为一个刚从产品经理转后端不到一年、连 git rebase 都还偶尔搞崩的斜杠青年,那一刻我真的想把 Vim 的 .vimrc 文件砸到运维头上。
但冷静下来一查日志,发现是某个 DAO 层用了手写 JDBC,SQL 写得跟意大利面一样乱,连个缓存都没有。那一刻我终于理解为什么我们 Java 后端组老大常说:“选错持久层框架,等于给自己埋雷”。
今天这篇笔记,不讲八股文,就聊聊 MyBatis —— 这个我在被“逼”学完 Spring Data JPA 后,最终选择拥抱的 Java 持久层框架。顺便对比下市面上几个主流选手,给还在纠结技术选型的兄弟们一点参考(也包括曾经的我自己)。
为什么不是 Hibernate?也不是 JPA?
先交代下背景:我之前做产品经理时,天天跟后端扯皮“这个需求能不能三天上线”,结果自己跳槽写代码后才发现,有些坑真的是自己当年挖的。现在我在上海一家做 SaaS 的中型公司,团队用的是标准 Spring Boot + MySQL 技术栈,业务偏 综合运营平台 —— 用户行为分析、活动配置、AB 测试……说白了就是一堆复杂查询 + 高频写入。
刚接手项目时,老代码里混着 Hibernate 和原生 JDBC,测试同学每次提 bug 都会附一句:“你们这 ORM 映射是不是又崩了?” 我一度以为是自己算法没学好(毕竟 LeetCode 只刷到 medium),后来才发现,问题不在算法,而在抽象过度。
Hibernate(以及 Spring Data JPA)讲究“对象关系全自动映射”,听起来很美好,但一旦 SQL 需要优化——比如加个 FORCE INDEX,或者写个窗口函数做用户留存计算——你就得绕过 JPA,直接写 Native Query。更惨的是,JPA 的 N+1 查询问题在线上炸过两次,运维直接拉黑了我的名字。
而 MyBatis 不同。它不替你生成 SQL,而是让你手写 SQL,但通过 XML 或注解把参数绑定、结果映射自动化。控制权在你手里,这对需要精细调优的运营系统来说,简直是救命稻草。
MyBatis 到底香在哪?对比一下就知道
为了说服我们那个“JPA 信仰者”架构师,我偷偷做了个横向对比(别告诉他是我干的):
| 特性 | MyBatis | Spring Data JPA (Hibernate) | 原生 JDBC |
|---|---|---|---|
| SQL 控制粒度 | ⭐⭐⭐⭐⭐(完全手动) | ⭐(自动生成,难干预) | ⭐⭐⭐⭐⭐ |
| 学习曲线 | 中等(需懂 SQL) | 较陡(需懂实体映射规则) | 低(但重复代码多) |
| 性能调优空间 | 极高 | 有限 | 高 |
| 动态 SQL 支持 | 内置 <if>, <foreach> 等 |
需拼接或用 Criteria API | 手动拼接(易错) |
| 缓存机制 | 一级/二级缓存可配 | 一级/二级缓存 | 无 |
| 与复杂算法结合 | 直接嵌入 SQL(如窗口函数) | 困难 | 可以,但繁琐 |
看到没?对于运营类系统——这类对数据灵活性、查询性能要求极高的场景——MyBatis 的“半自动”反而成了优势。你可以把复杂的用户分群逻辑直接写在 SQL 里,而不是在 Java 里循环几百次(别问我怎么知道的)。
实战:5 分钟跑通一个 MyBatis 项目
别被吓到,MyBatis 上手其实很快。我用 Spring Boot + MyBatis 写了个用户行为日志查询接口,核心就三步:
1. 引入依赖(pom.xml)
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2. 写 Mapper 接口
@Mapper
public interface UserActionLogMapper {
List<UserActionLog> selectByUserId(@Param("userId") Long userId);
}
3. 写 SQL(XML 方式)
<!-- resources/mapper/UserActionLogMapper.xml -->
<mapper namespace="com.example.mapper.UserActionLogMapper">
<select id="selectByUserId" resultType="com.example.model.UserActionLog">
SELECT id, user_id, action_type, created_at
FROM user_action_log
WHERE user_id = #{userId}
ORDER BY created_at DESC
LIMIT 100
</select>
</mapper>
搞定!启动项目,接口就能用了。比起 JPA 那一堆 @Entity, @Table, @JoinColumn,是不是清爽多了?
而且注意那个 LIMIT 100 —— 这种防止全表扫描的保护措施,用 MyBatis 写起来毫无心理负担。要是用 JPA,你得在 Repository 里写 Pageable,还得防前端传个 size=999999 导致 OOM。
生产环境踩过的坑 & 运维经验
当然,MyBatis 也不是银弹。我在上线第一个版本时,就因为没开二级缓存,导致同一用户的多次请求反复查 DB,DB CPU 直接飙到 90%。运维大哥在群里 @ 我:“兄弟,你是想让我们半夜去机房给你烧纸吗?”
后来补了两招:
开启二级缓存(谨慎使用):
<cache eviction="LRU" flushInterval="60000" size="512"/>注意:只有读多写少且数据一致性要求不高的场景才适用(比如运营看板数据)。
SQL 审计 + 慢查询监控
我们接入了阿里云的 ARMS,所有 MyBatis 执行的 SQL 都会上报。一旦出现EXPLAIN结果里有Using filesort或rows_examined > 10w,立刻告警。这招救了我好几次——毕竟产品经理出身,SQL 写得再小心,也可能漏个索引。
另外,动态 SQL 是把双刃剑。有一次我用 <foreach> 批量插入用户标签,结果前端传了 10 万个 ID,直接把 MySQL 的 max_allowed_packet 撑爆。后来加了分页批量处理 + 参数校验,才算稳住。
和“算法”有什么关系?
你可能会问:这跟算法有啥关系?别急。
在运营系统里,很多“算法”其实是SQL 算法。比如计算 7 日留存率,传统做法是在 Java 里拉两批用户集合,然后 Set.retainAll() —— 时间复杂度 O(n),内存爆炸。
但用 MyBatis + MySQL 8.0 的窗口函数,一行 SQL 搞定:
SELECT
DATE(created_at) as day,
COUNT(*) as new_users,
COUNT(CASE WHEN DATEDIFF(next_day, created_at) = 7 THEN 1 END) * 1.0 / COUNT(*) as retention_7d
FROM (
SELECT
user_id,
created_at,
LEAD(created_at) OVER (PARTITION BY user_id ORDER BY created_at) as next_day
FROM user_register_log
) t
GROUP BY day;
这种场景下,SQL 本身就是算法的载体。而 MyBatis 让你毫无障碍地把这种“数据库内计算”落地,而不是把海量数据拖到 Java 内存里硬算——后者不仅慢,还容易被运维拉去喝茶。
最后:给转型者的真心话
从产品经理转技术,最大的优势不是懂需求,而是知道哪些地方不能妥协。比如运营同学要实时看活动转化漏斗,你不能说“等我建个数仓明天跑批”,你得让接口秒出结果。
MyBatis 给了我这种掌控感——我知道每一行 SQL 在干什么,我能为每一个字段加上注释,我能在线上紧急情况下直接改 XML 而不用重新打包。
当然,如果你做的是 CRUD 管理后台,JPA 可能更省事。但一旦涉及综合数据处理、高性能查询、灵活 SQL 优化,MyBatis 的性价比就凸显出来了。
现在,我已经能在 Vim 里用 :MyBatisGoToMapper 插件一键跳转 XML 了(虽然还是经常按错成 :q!)。上周那个超时接口,重构后 P99 从 2s 降到 120ms,运营小姐姐请我喝了杯喜茶。
值了。
P.S. 别再问“为什么不直接用 MyBatis-Plus”了——我们试过,但它的 QueryWrapper 在复杂 JOIN 场景下反而限制了 SQL 表达力。有时候,“简单”不等于“灵活”。记住:工具是为人服务的,不是反过来。
(完)

评论 0