MyBatis踩坑记:从零搭建Java持久层的那些事儿

生产环境勿扰
2026-01-14 21:10
阅读 327

上周五晚上十点,我正戴着耳机听着《晴天》改一个线上Bug,突然收到产品组发来的紧急需求——“这个接口性能太差,明天上线前必须优化完”。我盯着IDE里那堆手写JDBC的代码,心里默默问候了产品经理全家(开玩笑的,其实他人挺好的)。那一刻,我下定决心:必须上MyBatis

作为杭州某厂(不点名,反正不是网易就是阿里系)的一名普通一本CS大四应届生,我已经拿到offer等入职了。最近在实习期间被安排接手一个老系统,代码里充斥着ConnectionPreparedStatementResultSet,看得我头皮发麻。更离谱的是,SQL语句直接拼在Java字符串里,连个参数校验都没有。这哪是代码,这是祖传文物啊!

为什么选MyBatis?而不是Hibernate或者Go?

说实话,团队里也有人提过用Hibernate,但被我们后端组长一句话怼回去了:“我们又不是要写论文,搞什么全自动ORM?我们需要的是可控性。” 说得对!在电商这种高并发场景下,SQL的每一毫秒都得精打细算。MyBatis正好介于裸写JDBC和全自动ORM之间——你写SQL,它帮你处理映射,完美。

至于Go?虽然我们公司新项目确实在用Go(毕竟杭州这边Go生态越来越火),但这个老系统是Java栈,重写成本太高。而且我入职后大概率还是写Java,所以先把MyBatis玩明白才是正道。

环境搭建:别被配置劝退

我第一次配MyBatis的时候,光是mybatis-config.xml就折腾了两个小时。后来发现,Spring Boot + MyBatis 才是真香组合。直接加个依赖:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

然后在application.yml里配个数据源:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/product_db?useUnicode=true&characterEncoding=utf8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.demo.entity

搞定!比手写JDBC连接池省了不知道多少行代码。

写个Mapper:告别SQL拼接地狱

以前那种代码:

String sql = "SELECT * FROM products WHERE name LIKE '%" + productName + "%'";

不仅有SQL注入风险,还丑得要死。现在用MyBatis,XML里这么写:

<!-- ProductMapper.xml -->
<select id="searchProducts" resultType="Product">
    SELECT id, name, price, stock
    FROM products
    WHERE name LIKE CONCAT('%', #{productName}, '%')
</select>

Java接口就一行:

public interface ProductMapper {
    List<Product> searchProducts(@Param("productName") String productName);
}

注意:这里用#{}而不是${}!前者会自动转义,后者就是纯字符串替换,容易被注入。我上周差点因为这个被安全扫描扫出来,吓出一身冷汗。

动态SQL:if、foreach真香

产品需求总是变来变去。比如这个查询接口,一开始只要按名称搜,后来又要加价格区间、库存状态、分类ID……如果用传统方式,得写一堆if判断拼SQL,维护起来想哭。

MyBatis的动态SQL救我狗命:

<select id="complexSearch" resultType="Product">
    SELECT id, name, price, stock, category_id
    FROM products
    WHERE 1=1
    <if test="productName != null and productName != ''">
        AND name LIKE CONCAT('%', #{productName}, '%')
    </if>
    <if test="minPrice != null">
        AND price >= #{minPrice}
    </if>
    <if test="maxPrice != null">
        AND price <= #{maxPrice}
    </if>
    <if test="categoryIds != null and categoryIds.size > 0">
        AND category_id IN
        <foreach collection="categoryIds" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </if>
</select>

特别是<foreach>,处理IN查询太方便了。以前手写得循环拼字符串,现在一行搞定,还自动防注入。

性能优化:别让MyBatis背锅

有人说“MyBatis慢”,其实锅不在框架,而在你怎么用。我在压测时发现一个接口QPS只有50,查了半天,原来是没开二级缓存,而且每次查询都拉全表。

解决方案:

  1. 合理使用缓存:对不常变的数据(比如商品分类)开启二级缓存
  2. 分页查询:千万别SELECT *,用LIMIT控制返回量
  3. 索引优化:确保WHERE条件字段有索引

MyBatis分页推荐用PageHelper插件,几行代码搞定:

PageHelper.startPage(1, 10); // 第1页,每页10条
List<Product> products = productMapper.searchProducts("手机");

与Go项目的对比思考

有意思的是,我们团队另一个小组在用Go写新服务,他们用的是GORM。我发现两者思路很像:都是让开发者专注SQL本身,而不是对象关系映射的魔法。只不过MyBatis更“显式”——SQL写在XML或注解里,一目了然;而GORM通过链式调用生成SQL,更“隐式”。

在调试时,MyBatis的日志输出特别清晰,能看到完整SQL和参数,排查问题快很多。这点比某些“全自动”ORM强太多。

最后一点真心话

作为一个即将正式入职的萌新,我深刻体会到:工具只是工具,核心还是对业务和数据的理解。MyBatis再好,如果你写的SQL本身就有N+1查询问题,照样慢成狗。

现在那个老系统已经重构了一半,接口响应时间从800ms降到80ms,产品组终于不再半夜@我了。虽然过程中踩了无数坑(比如XML命名空间写错、驼峰转换没开、事务没配……),但每解决一个,都感觉自己离“合格程序员”又近了一步。

对了,如果你也在杭州,也在用MyBatis,欢迎交流!说不定以后还能在阿里园区或网易门口偶遇,一起吐槽产品需求呢 😄


附:MyBatis vs 原生JDBC 关键对比

维度 原生JDBC MyBatis
代码量 多(连接、关闭、异常处理) 少(自动管理)
SQL注入风险 高(需手动处理) 低(#{}自动转义)
可维护性 差(SQL散落在Java中) 好(集中管理)
性能控制 完全手动 精细可控
学习曲线 低(但重复劳动多) 中(需理解映射机制)

总之,别再手写JDBC了,除非你想体验“修仙”般的加班生活。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝