《从外包到甲方:我在深夜写 MyBatis 教程时,老婆发来一条消息》
上周五晚上11点27分,我正对着 IntelliJ IDEA 里一堆 @Mapper 注解发呆,手机突然震动。是老婆发来的微信:“今天又没视频,你还在改 bug 吗?”
我叹了口气,回了个“嗯”,然后继续调试那个该死的动态 SQL。窗外北京昌平的夜风呼呼作响,房租3500的房子暖气不太给力,我裹着去年双十一买的加绒睡衣,手边是半杯凉透的速溶咖啡——这大概就是我跳槽进甲方后的生活缩影。
一、从外包到甲方:不只是薪资的变化
去年十月,我终于从一家干了三年的外包公司跳槽进了某头部互联网公司的运营中台团队。月薪从15k涨到了22k,五险一金按全额交,还有年度体检和带薪年假。听起来很香,对吧?
但没人告诉你,甲方的代码质量要求高得离谱。入职第一天,组长扔给我一个需求:“给运营后台加个用户行为分析接口,用 MyBatis 写,别整那些花里胡哨的 JPA。”
我当时心里一咯噔——在上家公司,我们基本靠 MyBatis-Plus 自动生成 CRUD,连 XML 都很少写。现在要手写原生 MyBatis?还涉及复杂查询?我差点想说“哥,能让我先看看项目结构吗?”
但话到嘴边又咽了回去。毕竟,这是我好不容易争取来的机会。当初和老婆商量跳槽时,她就说:“你在外包三年,再不进甲方,技术栈都要锈掉了。” 我知道她说得对。外包项目节奏快、需求杂,但技术深度几乎为零。每天就是接需求、改字段、上线、背锅,循环往复。
而甲方不一样。这里讲究综合能力:不仅要会写代码,还要理解业务逻辑、考虑性能、兼顾可维护性。尤其是我们做的是运营系统,数据量大、查询复杂,稍有不慎就会拖垮整个报表。
二、MyBatis 不是 CRUD 工具,而是“翻译官”
很多人以为 MyBatis 就是个数据库操作框架,无非就是 select * from user where id = #{id}。但真正在甲方做运营系统,你会发现它更像是 Java 和 SQL 之间的“翻译官”。
举个例子:上周我需要写一个接口,统计不同渠道(微信、APP、H5)在最近7天的用户活跃度,并按小时粒度聚合。这个需求如果用 JPA,可能得写一堆 Specification 或者自定义 Repository,但用 MyBatis,反而更直接。
我新建了一个 UserActivityMapper.xml,里面写了这样的动态 SQL:
<select id="selectHourlyActiveUsers" resultType="map">
SELECT
DATE_FORMAT(create_time, '%Y-%m-%d %H:00:00') AS hour,
channel,
COUNT(DISTINCT user_id) AS active_count
FROM user_activity_log
WHERE create_time >= #{startTime}
AND create_time < #{endTime}
<if test="channels != null and channels.size() > 0">
AND channel IN
<foreach collection="channels" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</if>
GROUP BY hour, channel
ORDER BY hour DESC
</select>
看起来简单,但背后全是坑。比如:
DATE_FORMAT在 MySQL 里没问题,但测试环境用的是 H2,直接报错;IN子句传空列表会生成IN (),SQL 语法错误;- 分页查询时,如果按小时聚合,总条数怎么算?
这些问题在外包时根本不会遇到——因为需求简单,数据量小,错了就重跑。但在甲方,一个慢查询可能让整个运营看板卡死,影响几百人的决策。
三、凌晨三点,我和老婆的视频通话
那天晚上,我卡在一个 resultMap 的嵌套映射问题上。前端需要返回一个复杂的 JSON 结构,包含用户基本信息、最近三次登录设备、以及所属标签组。我一开始想用 @Results 注解硬编码,结果发现字段一多就乱成一锅粥。
最后还是回归 XML,用 <association> 和 <collection> 做了三层嵌套。代码写完时,已经是凌晨2点48分。我正准备关机,老婆打来了视频。
她刚结束加班,眼睛有点红。“你又熬夜了?”她问。
“嗯,一个 MyBatis 映射搞了半天。”我苦笑。
“我记得你以前不是说 MyBatis 很简单吗?”
“那是外包的时候,”我说,“现在才知道,基础不牢,地动山摇。以前觉得会写个 selectById 就算会 MyBatis,现在发现,真正的难点在于怎么把复杂的业务逻辑,干净利落地映射到 SQL 和 Java 对象之间。”
她沉默了一会儿,说:“你比以前认真了。”
我鼻子一酸。是啊,因为我知道,这次不能再糊弄了。这份工作,不仅关系到我的职业发展,也关系到我们能不能早点结束异地。上个月她问我:“如果明年还异地,你会不会后悔跳槽?” 我说不会,因为这是成长的必经之路。
四、写这篇教程,是为了不让更多人踩坑
其实我一直想写一篇 MyBatis 基础教程,不是那种复制粘贴官网文档的水文,而是真正从实战出发,告诉新人:在真实项目中,MyBatis 到底该怎么用。
所以,我整理了几个关键点,希望对你有用:
1. 别迷信注解,XML 更适合复杂场景
虽然 @Select 看起来简洁,但一旦涉及动态 SQL、多表关联、嵌套结果,XML 的可读性和可维护性远超注解。我们团队明确规定:超过 5 行的 SQL,必须用 XML。
2. resultMap 是你的朋友,不是敌人
很多人怕写 resultMap,觉得麻烦。但它是解决“对象-关系阻抗失配”的利器。比如,数据库字段是 user_name,Java 属性是 userName,用 resultMap 一次配置,终身受益。
3. 动态 SQL 要测试边界条件
<if>、<foreach> 看似简单,但空集合、null 值、空字符串都会导致 SQL 语法错误。务必在单元测试中覆盖这些 case。
4. 性能监控不能少
在甲方,每个 SQL 都要经过慢查询日志分析。我们用 MyBatis 的 Interceptor 拦截执行时间,超过 200ms 的自动告警。别等到线上 OOM 了才后悔。
5. 和 DBA 保持沟通
运营系统的 SQL 往往涉及大表扫描。有一次我写了个 LIKE '%keyword%',DBA 直接找上门:“你这查询没索引,全表扫,1000万数据,知道要多久吗?” 从那以后,我写 SQL 前必先问 DBA。
五、Java 开发的本质,是解决问题
有人说,现在都2024年了,还学 MyBatis?Spring Data JPA、QueryDSL、甚至 ORM 框架都更高级。但我想说,在Java 企业级开发中,MyBatis 依然是不可替代的。
为什么?因为它给了你控制力。你可以精确控制每一条 SQL,优化每一个 join,调整每一个索引。在数据密集型的运营系统中,这种控制力就是生命线。
而且,MyBatis 的学习曲线其实很平缓。你不需要理解 Hibernate 的一级二级缓存、脏检查、延迟加载等复杂概念,只要会写 SQL,就能上手。这也是为什么它在国内如此流行——实用主义永远是工程师的第一准则。
六、写在最后:技术之外,是生活
写完这篇教程,已经是凌晨1点。我关掉电脑,看了眼手机,老婆已经睡了。她的头像旁边显示“在线”,但聊天框里最后一句是:“早点睡,别熬太晚。”
我笑了笑,关灯躺下。
从外包到甲方,薪资涨了,压力也大了,但内心却更踏实了。因为我知道,自己正在做一件“正确的事”——不是为了炫技,不是为了卷,而是为了综合提升自己的工程能力,为了有一天,能堂堂正正地对她说:“我们可以在一起了。”
如果你也在外包,或者刚进甲方,正在被 MyBatis 的各种细节折磨,我想说:别怕。每一个 #{} 背后,都是你成长的印记;每一个 resultMap 里,都藏着你未来的底气。
技术这条路,没有捷径。但只要方向对了,哪怕慢一点,也终会到达。
就像我和老婆,虽然现在每周只能见一次,但每次见面,她都说:“你眼里有光了。”
或许,这就是坚持的意义。
P.S. 这篇 MyBatis 基础教程,献给所有在深夜 debug 的 Java 开发者。愿你的 SQL 从不报错,愿你的 merge request 一次通过,愿你早日结束异地,拥抱所爱之人。

评论 0