从手写SQL到拥抱MyBatis:一个老顽固的真香现场

正则表达式怪
2026-05-19 08:01
阅读 2014

去年夏天,我还在成都某家“卷又不给钱”的创业公司里,一边喝着冰粉奶茶,一边对着满屏的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 &lt;= #{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>循环插入语句,但没加openclose属性,结果SQL语法报错:

### Error updating database. Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax...

当时我真的想砸电脑。但冷静下来一想:AI是工具,不是大脑。你得知道它哪里可能出错,才能有效利用它。

所以,我现在的开发流程是:

  1. 先用Codeium快速生成基础Mapper和XML;
  2. 手动Review关键逻辑,特别是动态SQL和参数绑定;
  3. 写单元测试覆盖边界条件(比如全空查询、极端值);
  4. 上线前用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

最热最新
暂无评论
正则表达式怪Lv.1
0
影响力
0
文章
0
粉丝