MyBatis 入门没那么难,别被 ORM 吓退了

优雅的梦想家
2026-05-10 20:00
阅读 1123

去年双11大促刚结束那会儿,我坐在成都办公室的工位上,一边听着 Lo-fi Beats,一边盯着屏幕上一堆 SQL 报错信息发呆。那天晚上本来想早点回家吃火锅,结果被测试同学拉进了一个“紧急会议室”——说是某个核心商品查询接口在压测时直接崩了,报错是 java.sql.SQLException: Cursor closed

当时我心里一咯噔:坏了,又是数据库连接池和游标的问题。后来排查发现,团队里新来的实习生用原生 JDBC 手写了一堆查询逻辑,结果在分页处理时忘了正确关闭 ResultSet,导致连接泄漏。上线前没人 review 这段代码,差点酿成线上事故。

这事之后,我们组痛定思痛,决定全面引入 MyBatis 来统一数据访问层。作为阿里 P7 前端工程师(没错,前端也得懂点后端,尤其在云原生架构下前后端边界越来越模糊),我主动揽下了推动这件事的任务——毕竟谁让我天天跟 K8s、Sidecar、Service Mesh 打交道呢?数据库这关,躲不过去。

今天这篇,就聊聊 MyBatis 的入门那些事儿。不搞花里胡哨的理论堆砌,就讲怎么用、怎么避坑、怎么在真实项目里跑起来。


为什么选 MyBatis?

先说清楚,我不是来吹 MyBatis 多牛的。JPA、Hibernate、Spring Data JPA 都有各自的优势。但在高并发、强定制、SQL 性能敏感的场景下(比如双11每秒几万笔订单),MyBatis 的“半自动 ORM”反而成了优势

它不像 Hibernate 那样试图完全屏蔽 SQL,而是让你掌控 SQL,同时又帮你省掉大量模板代码(比如手动 set/get、资源关闭)。你写的 XML 或注解里的 SQL,就是最终执行的 SQL——这对性能调优、慢查询分析太友好了。

而且,MyBatis 对 Cursor(游标) 的支持非常关键。在处理大数据量分页或流式读取时,传统 List<T> 一次性加载全表数据很容易 OOM,而 MyBatis 的 Cursor 接口配合数据库游标,可以实现内存恒定的流式处理。

⚠️ 注意:不是所有数据库都支持服务端游标。MySQL 默认不开启,需要显式配置 useCursorFetch=true 并设置 fetchSize


快速上手:三步搞定 CRUD

假设我们有个商品表 product

CREATE TABLE product (
    id BIGINT PRIMARY KEY,
    name VARCHAR(100),
    price DECIMAL(10,2),
    stock INT
);

第一步:引入依赖

Maven 里加上:

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.13</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
</dependency>

如果你用 Spring Boot,直接加 mybatis-spring-boot-starter 更香。

第二步:写 Mapper 接口

public interface ProductMapper {
    Product selectById(Long id);
    
    List<Product> selectAll();
    
    void insert(Product product);
    
    void update(Product product);
    
    void delete(Long id);
}

注意:不需要实现类! MyBatis 在运行时动态生成代理。

第三步:写 SQL 映射(XML 方式)

<!-- ProductMapper.xml -->
<mapper namespace="com.example.mapper.ProductMapper">
    <select id="selectById" resultType="Product" parameterType="long">
        SELECT * FROM product WHERE id = #{id}
    </select>

    <select id="selectAll" resultType="Product">
        SELECT * FROM product
    </select>

    <insert id="insert" parameterType="Product">
        INSERT INTO product(id, name, price, stock)
        VALUES (#{id}, #{name}, #{price}, #{stock})
    </insert>

    <!-- 其他略 -->
</mapper>

配上 mybatis-config.xml 或 Spring Boot 的 application.yml,基本就能跑起来了。


关于 Cursor:别再让 ResultSet 溜走

回到开头那个事故。MyBatis 是怎么帮我们避免 Cursor closed 的?

答案是:自动资源管理 + 显式 Cursor API

普通查询返回 List<T>,MyBatis 内部会自动打开连接、执行查询、映射结果、关闭资源。但当你需要流式处理时,可以用:

try (Cursor<Product> cursor = sqlSession.selectCursor("selectAll")) {
    for (Product p : cursor) {
        // 逐条处理,内存不会爆炸
        process(p);
    }
}

这里的关键是:

  • selectCursor 返回的是 Cursor,不是 List
  • 必须用 try-with-resources 确保关闭
  • 数据库驱动要支持服务端游标(MySQL 需要 ?useCursorFetch=true&defaultFetchSize=1000

我们后来在商品同步任务中用这个方案处理百万级数据,内存占用从 2GB 降到 100MB,稳得一批。


配置建议:生产环境别裸奔

本地跑通只是开始,上生产得讲究。以下是我们在阿里内部沉淀的一些最佳实践:

配置项 推荐值 说明
mapUnderscoreToCamelCase true 数据库下划线字段自动转驼峰
logImpl SLF4J 日志用 SLF4J,别用 StdOut
defaultExecutorType REUSE 重用 PreparedStatement,减少编译开销
localCacheScope SESSION 开启一级缓存(谨慎使用)
jdbcTypeForNull NULL 避免 Oracle 等数据库空值报错

另外,强烈建议开启 SQL 日志。双11压测时,我们就是靠日志发现某条 SQL 没走索引,及时加了联合索引才扛住流量洪峰。


和前端工程师有啥关系?

可能有人问:你是前端,写啥 MyBatis?

其实在云原生时代,全栈能力越来越重要。我在成都这边负责的很多项目都是 Serverless + 微服务架构,前端同学经常要写 BFF(Backend For Frontend)层,里面就涉及数据库操作。

而且,懂 MyBatis 能让你更好地和后端沟通。比如当接口慢的时候,你可以直接看 SQL 日志,判断是慢查询还是网络问题,而不是只会甩锅给“后端接口太慢”。

上周五晚上,我还用 MyBatis 写了个小工具,批量导出用户行为数据到 CSV——边听周杰伦边写,效率飞起。这种小工具在日常运维、数据分析中特别实用。


最后一点真心话

MyBatis 不是银弹,但它足够轻量、灵活、可控。对于需要精细控制 SQL 的业务系统(比如电商、金融),它依然是首选。

别被“ORM 框架”这几个字吓到。本质上,它就是一个帮你写 JDBC 模板代码的工具,让你专注业务逻辑。

记住:工具为人服务,不是人被工具绑架。无论是 MyBatis、K8s 还是 Cursor,理解其背后的设计思想,比死记 API 重要得多。

对了,下次双11,希望我的代码别再半夜报警了……不然火锅真的吃不上了。

评论 0

最热最新
暂无评论
优雅的梦想家Lv.1
0
影响力
0
文章
0
粉丝