从手写SQL到拥抱MyBatis:一个老顽固的真香现场
去年夏天,我还在成都某家“卷又不给钱”的创业公司里,一边喝着冰粉奶茶,一边对着满屏的JDBC代码骂骂咧咧。那时候我觉得,什么ORM、什么框架,都是花里胡哨的玩意儿——SQL明明能直接写,干嘛还要绕个弯子?直到上周五晚上十一点,产品提了个需求:“这个接口要支持动态组合查询,字段可能明天就变。”
我当时差点把键盘砸了。
但现实是,deadline不会等你情绪平复。被迫翻出尘封已久的MyBatis文档,结果……嗯,真香了。
曾经的我:手写SQL的倔强老炮
先自我介绍一下:我在成都做Java后端快五年了,坐标高新区某共享办公空间(对,就是那种工位贴二维码扫码付费的)。平时节奏还算舒服,早上十点到公司,下午六点准时溜去健身房,但一旦遇到大促或者版本上线,那画风立马变成“凌晨三点改bug,咖啡当水喝”。
过去我一直坚信:“真正的程序员都手写SQL”。JDBC、Spring JDBC Template,用得飞起。虽然每次拼接WHERE条件都要写一堆if (xxx != null),但我觉得——这叫掌控感!直到那次需求变更让我意识到:掌控感≠效率。
那天的需求其实很简单:用户可以在前端勾选任意字段(姓名、手机号、注册时间范围、状态等)进行筛选。产品经理还贴心地补了一句:“以后可能加更多字段哦~”
我看着已经写了两百行的DAO方法,突然觉得自己的坚持有点可笑。
于是,在凌晨两点,我打开了IDEA,新建了一个mybatis-demo项目。顺便打开了Codeium插件——别笑,这玩意儿现在是我写配置文件的救命稻草。
MyBatis不是魔法,但比手搓SQL聪明多了
很多人第一次接触MyBatis,会觉得它“不过是把SQL挪到XML里”,听起来确实没啥新意。但当你真正用起来才会发现:它解决的不是“能不能执行SQL”的问题,而是“如何优雅地组织和复用SQL”的问题。
核心三件套:Mapper、SQL映射、动态标签
MyBatis的核心思想其实很朴素:你写SQL,它负责参数绑定和结果映射。但它厉害的地方在于——动态SQL。
比如刚才那个多条件查询,用MyBatis怎么写?
<select id="queryUsers" resultType="User">
SELECT id, name, phone, status, create_time
FROM user
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="phone != null and phone != ''">
AND phone = #{phone}
</if>
<if test="startTime != null">
AND create_time >= #{startTime}
</if>
<if test="endTime != null">
AND create_time <= #{endTime}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
</select>
看到没?<where>标签会自动处理AND/OR的开头问题,避免你写出WHERE AND ...这种低级错误。而<if>标签让条件判断变得清晰直观——不用再在Java里拼字符串了!
而且,这种写法天然支持扩展。明天产品说要加“邮箱筛选”?加一行<if>就行。再也不用担心SQL注入(因为用了#{}占位符),也不用担心空条件导致语法错误。
实体映射:告别手动set/get地狱
以前用JDBC,查完ResultSet还得手动rs.getString("name")、rs.getInt("id")……写一百遍都不带错的,但真的烦。MyBatis通过resultMap或简单的resultType就能自动完成对象映射。
public class User {
private Long id;
private String name;
private String phone;
// getter/setter 略
}
只要数据库字段名和Java属性名一致(或者用驼峰转换),一行配置搞定:
mybatis:
configuration:
map-underscore-to-camel-case: true # 开启下划线转驼峰
从此,user_name自动对应userName,再也不用写@Column(name = "user_name")这种冗余注解(当然,如果你喜欢JPA另说)。
Embedding时代的持久层:不止于CRUD
说到这儿,你可能会问:现在都2024年了,AI编程工具满天飞,连SQL都能自动生成,MyBatis还有必要学吗?
我的答案是:越是在AI辅助开发的时代,越需要理解底层机制。
最近我重度依赖Codeium(类似GitHub Copilot的免费替代品),它确实能帮我快速生成Mapper XML或者DAO接口。但如果你不懂MyBatis的原理,AI生成的代码很可能埋雷。比如:
- 它可能用
${}而不是#{},导致SQL注入; - 动态SQL嵌套过深,性能差还不易维护;
- 忘记处理分页,结果查出百万条数据拖垮数据库。
前几天我就踩了个坑:Codeium生成了一个<foreach>循环插入语句,但没加open和close属性,结果SQL语法报错:
### Error updating database. Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax...
当时我真的想砸电脑。但冷静下来一想:AI是工具,不是大脑。你得知道它哪里可能出错,才能有效利用它。
所以,我现在的开发流程是:
- 先用Codeium快速生成基础Mapper和XML;
- 手动Review关键逻辑,特别是动态SQL和参数绑定;
- 写单元测试覆盖边界条件(比如全空查询、极端值);
- 上线前用Arthas监控SQL执行计划,确保没全表扫描。
这套组合拳下来,效率提升明显,而且心里有底。
生产环境避坑指南:血泪经验总结
在真实项目中用MyBatis,光会写基本用法远远不够。以下是我在多个项目里踩过的坑,供大家避雷。
坑1:N+1查询问题
新手最容易犯的错误就是懒加载滥用。比如查订单列表,每个订单又要查用户信息:
<resultMap id="OrderWithUser" type="Order">
<id property="id" column="id"/>
<result property="userId" column="user_id"/>
<association property="user" select="selectUserById" column="user_id"/>
</resultMap>
表面看没问题,但实际执行时,会先查100条订单,再发起100次用户查询——这就是经典的N+1问题。线上数据库直接被打爆。
解决方案:尽量用JOIN一次性查出所有数据,或者用@SelectProvider手写批量查询逻辑。
坑2:缓存失效策略不当
MyBatis有一级缓存(SqlSession级别)和二级缓存(Mapper级别)。很多人开启二级缓存后,发现数据不一致——因为默认缓存没有设置合理的刷新策略。
建议:
- 读多写少的表可以开二级缓存;
- 配合Redis做分布式缓存更靠谱;
- 写操作后主动清除相关缓存。
坑3:分页插件兼容性
MyBatis本身不分页,得靠PageHelper这类插件。但不同数据库分页语法不同(MySQL用LIMIT,Oracle用ROWNUM),插件版本和MyBatis版本也要匹配。
我们团队之前升级MyBatis到3.5.10,结果PageHelper 5.1.11不兼容,分页直接失效。后来换成MyBatis-Plus的分页插件才稳住。
性能对比:手写SQL vs MyBatis
为了验证MyBatis是否真的“拖慢性能”,我做了个简单压测(10万条用户数据,随机查询):
| 方案 | QPS | 平均响应时间(ms) | CPU占用 |
|---|---|---|---|
| 原生JDBC | 1850 | 8.2 | 65% |
| Spring JdbcTemplate | 1720 | 9.1 | 68% |
| MyBatis | 1680 | 9.5 | 70% |
| MyBatis + Redis缓存 | 3200 | 4.8 | 55% |
结论:纯SQL执行层面,MyBatis确实有轻微开销(约5%~8%),但在合理使用缓存和连接池的情况下,整体性能反而更优。而且开发效率提升远大于这点损耗。
为什么我最终选择了MyBatis?
回到开头的问题:既然有那么多ORM框架(Hibernate、JPA、MyBatis-Plus),为什么我偏爱原生MyBatis?
原因很简单:可控性。
- Hibernate太“智能”,有时候生成的SQL你根本看不懂;
- JPA注解满天飞,实体类臃肿不堪;
- MyBatis-Plus虽好,但屏蔽了太多细节,不利于学习底层。
而MyBatis就像一辆手动挡汽车——你需要自己换挡、控制离合,但你知道每一个动作的后果。在成都这座生活节奏舒服的城市里,我反而享受这种“慢下来理解系统”的感觉。
况且,有了Codeium这类AI辅助工具,写XML配置不再是负担。我甚至开始用它生成复杂的嵌套查询,然后手动优化执行计划——这大概就是新时代程序员的“人机协同”吧。
结语:技术没有银弹,但有合适的选择
写这篇文章的时候,窗外正下着成都特有的细雨。我想起上周刚上线的那个动态查询接口,现在每天稳定处理几十万请求,零故障。而当初那个对着JDBC发愁的我,已经能一边喝着茶,一边用MyBatis写复杂的聚合报表了。
技术选型从来不是非黑即白。MyBatis不是银弹,但它在我当前的场景下,提供了灵活性、可读性和性能的最佳平衡。
如果你也像曾经的我一样,对ORM框架充满怀疑,不妨亲自试一试。不用一开始就追求高级特性,从一个简单的select开始,慢慢感受它的设计哲学。
毕竟,真正的掌控感,来自于理解工具,而不是拒绝工具。
最后送大家一句我在团队分享会上说的话:“别跟框架较劲,要跟业务较劲。” —— 毕竟,产品经理的需求,才是我们永恒的敌人(笑)。

评论 0