MyBatis真没那么难,别被面试官吓到了

云上便利店
2025-12-28 09:34
阅读 1786

上周五晚上十点半,我还在公司改一个用户增长实验的SQL逻辑。成都这会儿早就夜深人静了,办公室只剩我和隔壁组一个运维兄弟——他说他在等K8s上一个Pod自动恢复,结果等到花儿都谢了。我俩一边啃泡面一边吐槽:为啥现在连做用户增长都要懂底层数据层?但现实就是这么骨感。

我是小红书干了两年推荐算法的工程师,按理说应该天天和Embedding、CTR预估打交道。可实际上,我们组去年开始搞“端到端增长闭环”,不仅要设计策略,还得自己写接口、查数据、调SQL。产品经理一句“这个漏斗能不能再细化一点”,背后就是一堆DAO层代码要重写。于是MyBatis,这个我大学只在教材里见过的名字,硬生生被我用成了日常工具。

今天这篇,不讲八股文,就聊聊我在实战中怎么把MyBatis从“黑盒”变成“趁手的刀”。尤其如果你正在求职,或者刚入职发现老项目全是MyBatis XML,别慌,看完这篇至少能挺过第一个CR(Code Review)。


为什么不是JPA?也不是裸JDBC?

先说个扎心事实:国内大厂,尤其是业务驱动型的公司(比如我们小红书),MyBatis的统治力远超Spring Data JPA。不是JPA不好,而是它太“理想化”了。一旦你的SQL需要动态拼接、复杂关联、或者要针对MySQL做极致优化,JPA那套“对象映射万能论”就容易翻车。

举个真实例子:我们做用户召回实验时,要根据设备型号、地域、活跃天数等10+个维度动态过滤用户。用JPA写Specification?写到一半我就想砸键盘。而MyBatis的<if><foreach>标签,配合XML,三行搞定。

再说裸JDBC?拜托,2024年了,谁还手动写Connection.prepareStatement()?又不是在写区块链节点同步逻辑(虽然我们组还真有人在搞链上行为分析,但那是另一个故事了)。

所以结论很现实:MyBatis = 控制力 + 灵活性 + 国内生态支持。求职面试问它,不是因为多高深,而是因为它代表了你对“数据怎么来的”有基本掌控力。


起手式:一个最简Demo跑起来

别一上来就看源码!先让代码跑起来才有信心。假设你有个用户表:

CREATE TABLE `user_growth` (
  `id` BIGINT NOT NULL AUTO_INCREMENT,
  `uid` VARCHAR(32) NOT NULL,
  `register_day` DATE,
  `active_days` INT DEFAULT 0,
  PRIMARY KEY (`id`)
);

对应的Java实体:

public class UserGrowth {
    private Long id;
    private String uid;
    private LocalDate registerDay;
    private Integer activeDays;
    // getter/setter 略
}

然后是MyBatis的核心三件套:

  1. Mapper接口(定义方法)
  2. XML映射文件(写SQL)
  3. 配置注册(告诉Spring在哪)
// UserGrowthMapper.java
@Mapper
public interface UserGrowthMapper {
    UserGrowth selectByUid(@Param("uid") String uid);
    
    void insert(UserGrowth user);
}
<!-- UserGrowthMapper.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xiaohongshu.growth.mapper.UserGrowthMapper">

    <select id="selectByUid" resultType="com.xiaohongshu.growth.model.UserGrowth">
        SELECT * FROM user_growth WHERE uid = #{uid}
    </select>

    <insert id="insert" parameterType="com.xiaohongshu.growth.model.UserGrowth">
        INSERT INTO user_growth(uid, register_day, active_days)
        VALUES (#{uid}, #{registerDay}, #{activeDays})
    </insert>
</mapper>

注意几个坑:

  • namespace 必须是接口全路径
  • #{} 是预编译占位符,防SQL注入;${} 是直接拼接,慎用!
  • resultType 写全类名,或者配别名(但新手别折腾别名,容易迷路)

在Spring Boot里,只要加个@MapperScan("com.xiaohongshu.growth.mapper"),剩下的MyBatis-Spring-Boot-Starter全包了。


实战踩坑:动态SQL与性能陷阱

去年双11前,我们临时加了个需求:根据运营配置的标签组合筛选高潜用户。产品经理给了个Excel,里面有“近7天登录 + 安卓 + 成都 + 曝光>100”这种规则。

我一开始图快,写了N个if-else拼SQL,结果上线后数据库CPU飙到90%。DBA半夜打电话骂我:“你这是要把主库干挂?”

后来改成MyBatis的<where> + <if>

<select id="selectHighPotentialUsers" resultType="UserGrowth">
    SELECT * FROM user_growth
    <where>
        <if test="minActiveDays != null">
            AND active_days >= #{minActiveDays}
        </if>
        <if test="city != null and city != ''">
            AND city = #{city}
        </if>
        <if test="platformList != null and platformList.size > 0">
            AND platform IN
            <foreach collection="platformList" item="item" open="(" separator="," close=")">
                #{item}
            </foreach>
        </if>
    </where>
</select>

关键是<where>会自动去掉开头的AND,避免语法错误。而<foreach>处理IN查询,安全又简洁。

但注意:IN列表太大(比如上万ID)会拖垮MySQL。我们后来加了分页拆批处理,每次查1000个,用CompletableFuture并行拉取——这才是真正的“用户增长工程化”。


和云原生/K8s有啥关系?

你可能会问:你不是熟悉K8s吗?MyBatis和容器有毛关系?

还真有!我们在K8s上部署服务时,数据库连接池是关键资源。MyBatis本身不管理连接,靠的是底层DataSource(比如HikariCP)。

曾经有个事故:服务扩容到50个Pod,每个Pod默认Hikari最大连接数10,结果瞬间打爆数据库连接上限(max_connections=200)。DB直接拒绝新连接,整个增长实验中断。

解决方案:

  1. 限制单Pod连接数spring.datasource.hikari.maximum-pool-size=4
  2. 全局连接监控:用Prometheus抓取hikaricp_connections_active指标
  3. 优雅下线:Pod终止前,先调/actuator/shutdown,让MyBatis释放连接

所以说,写好DAO只是第一步,生产环境的稳定性才是工程师的终极考题


求职建议:别只会写CRUD

现在很多校招生简历写着“熟悉MyBatis”,结果一问二级缓存原理就懵。其实面试官不指望你背源码,但至少得知道:

问题 初级回答 高级回答
MyBatis如何防止SQL注入? 用#{} #{}底层是PreparedStatement占位符,${}是字符串拼接,仅用于动态表名/列名
一级缓存和二级缓存在哪? 不清楚 一级缓存是SqlSession级别,默认开启;二级缓存是Mapper级别,需显式开启,且要求实体可序列化
批量插入怎么优化? 循环insert <foreach>拼INSERT VALUES (...), (...), ... 或者用MyBatis-Plus的saveBatch

另外,结合业务场景谈优化,比背八股文强一百倍。比如:“我在做用户行为日志入库时,用MyBatis的ExecutorType.BATCH模式,将10万条记录插入从3分钟降到8秒。”


最后说两句

MyBatis真的不难,它就是一个让你既享受ORM便利,又保留SQL控制权的工具。我在小红书这两年,从算法岗被迫“全栈化”,反而让我更理解数据流的全链路——毕竟,再牛的推荐模型,也得靠靠谱的DAO层喂数据。

如果你正在求职,别怕问“基础”问题。能把MyBatis讲清楚的人,往往也懂得权衡、取舍和落地——而这,正是工程师最值钱的能力。

对了,成都今天又下雨了,我准备下班去吃火锅。代码可以慢慢调,但毛肚不能久煮。共勉!

评论 0

最热最新
暂无评论
云上便利店Lv.1
0
影响力
0
文章
0
粉丝