MyBatis 到底值不值得学?一个滴滴后端老油条的碎碎念

♂罗思宇
2025-12-29 09:05
阅读 1601

上周五晚上十点半,我家猫正趴在我键盘上打呼噜,我还在改一个线上司机端订单状态同步的 Bug。问题不大,就是某个 MySQL 语句在高并发下偶尔锁表,但排查起来却让我重新翻开了尘封已久的 MyBatis 源码。

我是滴滴的后端开发,干了快四年,主要搞司机端的核心业务——从接单、计价到完单结算,全链路都沾过手。远程办公两年多了,代码写得多,头发掉得也多。团队里新人越来越多,前两天还有个刚转 Java 的同事问我:“哥,现在都 2024 年了,还用 MyBatis?Spring Data JPA 不香吗?甚至有人用 Python 写 ORM……”

我笑了笑,没直接回答。但那天晚上修完 Bug 后,突然觉得:是时候聊聊 MyBatis 这个“老古董”了。


被面试官问懵的那一刻

其实我最早接触 MyBatis 是在准备跳槽时被“逼”的。那会儿面一家中厂,面试官冷不丁问:

“MyBatis 的一级缓存和二级缓存有什么区别?在分布式环境下用二级缓存会出什么问题?”

我当时支支吾吾说了点皮毛,回来就赶紧补课。结果入职滴滴后发现——我们生产环境压根没开二级缓存!不是不能用,而是不敢用。分布式系统里缓存一致性太难搞,一不小心就脏读,司机看到的订单状态和乘客端对不上,轻则客诉,重则资损。

这事儿让我明白:框架本身不复杂,复杂的是你怎么用它


MyBatis 到底是个啥?

简单说,MyBatis 是一个 Java 持久层框架,它把 SQL 从 Java 代码里抽出来,放到 XML 或注解里,让你能灵活控制 SQL,又不用手写 JDBC 那堆样板代码。

对比 Hibernate(或者 Spring Data JPA),MyBatis 更“贴近 SQL”,而 Hibernate 更“面向对象”。前者像手动挡,后者像自动挡。在滴滴这种对性能、SQL 可控性要求极高的场景,手动挡反而更稳

举个真实例子:司机端有个接口叫 getNearbyOrders,要查附近 5 公里内未接单的订单。这个查询涉及空间索引、状态过滤、分页,SQL 很复杂。用 JPA 写?光那个 @Query 注解就能写半屏,还容易生成低效 SQL。而 MyBatis 直接写原生 SQL,配合 <if> 标签动态拼接,清晰又高效。

<select id="selectNearbyOrders" resultType="Order">
  SELECT id, passenger_id, start_lat, start_lng, status
  FROM orders
  WHERE status = #{status}
    AND (6371 * acos(cos(radians(#{driverLat}))
         * cos(radians(start_lat))
         * cos(radians(start_lng) - radians(#{driverLng}))
         + sin(radians(#{driverLat}))
         * sin(radians(start_lat)))) <= 5
  ORDER BY created_at DESC
  LIMIT #{limit}
</select>

看,地理距离计算直接塞进 SQL,数据库一层搞定,比拉到 Java 层算快多了。


动态 SQL:MyBatis 的灵魂

很多人觉得 MyBatis 就是个 SQL 模板引擎,但真正体现功力的是它的 动态 SQL 能力

比如我们有个司机信用分查询接口,前端传一堆筛选条件:城市、车型、注册时间范围、是否完成过夜间单……这些条件可能有、可能没有。用传统 JDBC 写?得拼字符串,还容易 SQL 注入。

MyBatis 的 <where><if><foreach> 组合拳一套下来,干净利落:

<select id="queryDriverCredit" resultType="DriverCredit">
  SELECT driver_id, credit_score, city, vehicle_type
  FROM driver_credit
  <where>
    <if test="city != null">
      AND city = #{city}
    </if>
    <if test="vehicleTypes != null and vehicleTypes.size() > 0">
      AND vehicle_type IN
      <foreach item="type" collection="vehicleTypes" open="(" separator="," close=")">
        #{type}
      </foreach>
    </if>
    <if test="registerStartTime != null">
      AND register_time >= #{registerStartTime}
    </if>
  </where>
</select>

注意 <where> 标签会自动去掉开头的 AND,避免语法错误。这种细节,才是 MyBatis 被大厂青睐的原因。


缓存陷阱:别被“高性能”忽悠了

前面提过缓存。MyBatis 默认开启一级缓存(SqlSession 级别),同一个 SqlSession 里重复查询会走缓存。听起来很美,但在 Web 应用中,每个请求通常新建一个 SqlSession,所以一级缓存基本失效。

二级缓存是 Mapper 级别的,多个 SqlSession 共享。但问题来了:如果你的服务部署了多个实例(当然会),二级缓存只在本机生效,根本做不到集群共享!除非你外接 Redis 做分布式缓存,但那就不是 MyBatis 自己的事了。

所以我们团队的共识是:关掉二级缓存,自己用 Spring Cache + Redis 控制缓存逻辑。虽然多写几行代码,但可控、可监控、可降级。


和 Python 对比?别闹了

有同事开玩笑说:“你看人家 Python,Django ORM 一行代码搞定增删改查,多优雅!”

我只能回他一句:“你是在写博客,还是在扛双11流量?”

Python 的 ORM 确实简洁,但牺牲了对 SQL 的精细控制。在高并发、低延迟的场景下,ORM 自动生成的 N+1 查询、隐式 JOIN、慢 SQL 防不胜防。而 MyBatis 让你每一条 SQL 都在眼皮底下,出了问题直接看执行计划,定位快如闪电。

去年双11前夕,我们有个聚合查询突然变慢。用 MyBatis 日志一扒,发现是某字段没走索引。加个复合索引,QPS 从 800 直接飙到 5000。要是用全自动 ORM,估计得先翻半天文档才知道怎么 hint 索引。


面试题高频考点整理

既然提到面试,我顺手整理几个 MyBatis 高频题,都是我被问过或问过别人的:

问题 关键点
#{}和${}的区别 #{} 预编译防注入,${} 直接替换(慎用)
resultType 和 resultMap 简单映射用前者,复杂关系(嵌套、驼峰)用后者
如何实现分页? 用 PageHelper 插件,底层改写 SQL 加 LIMIT
插件(Plugin)原理 基于 JDK 动态代理,拦截 Executor/StatementHandler
MyBatis 如何防止 SQL 注入? 主要靠 #{} 的 PreparedStatement 机制

特别提醒:别背答案,要理解源码。比如 #{} 为啥安全?因为 MyBatis 最终调用的是 PreparedStatement.setString(),参数被当作值而非 SQL 片段处理。


生产环境踩坑实录

最后分享两个血泪教训:

  1. 驼峰命名没开,字段对不上
    数据库用 user_id,Java 用 userId,结果查出来全是 null。解决:全局配置 mapUnderscoreToCamelCase=true

  2. 批量插入 OOM
    foreach 插 10 万条数据,内存爆了。正确做法:分批提交,每 1000 条 flush 一次 SqlSession。

SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
    OrderMapper mapper = session.getMapper(OrderMapper.class);
    for (int i = 0; i < orders.size(); i++) {
        mapper.insert(orders.get(i));
        if (i % 1000 == 0) {
            session.commit();
            session.clearCache(); // 清理一级缓存,防内存泄漏
        }
    }
    session.commit();
} finally {
    session.close();
}

结语:老框架,新价值

MyBatis 诞生十几年了,有人说它过时。但在我眼里,它就像一把趁手的螺丝刀——不花哨,但关键时刻从不掉链子

在滴滴这种业务复杂、性能敏感的环境里,我们需要的是可控、透明、高效的工具。MyBatis 恰好满足。它不替你做决定,而是把选择权交给你——这正是专业开发者需要的。

所以,别管什么 Python ORM 多优雅,也别迷信“全自动”。真正的高手,既会用框架,也敢写 SQL

好了,猫已经把我的咖啡杯打翻了,代码还没 push。这篇文章就写到这儿吧。希望对你有用,至少下次面试别再被缓存问题问住。

(P.S. 如果你在用 MyBatis Plus,那是另一回事了——不过那是下一篇文章的故事了。)

评论 0

最热最新
暂无评论
♂罗思宇Lv.1
0
影响力
0
文章
0
粉丝