MyBatis基础教程:Java持久层框架入门

数据迁移苦工
2025-12-15 22:58
阅读 213

上周五晚上十点半,我瘫在工位上盯着屏幕发呆,脑子里还在回放产品经理那句“这个需求很简单,明天上线”。说实话,刚从英国回来那会儿,我还以为国内互联网节奏已经放缓了,结果入职这家跨境电商公司后才发现——卷,还是那个熟悉的卷

我是去年秋天回国的海归硕士,在伦敦念完CS之后,兜兜转转还是回到了北京。坐标中关村,每天通勤一小时挤地铁的日子让我深刻体会到什么叫“用生命换房租”。不过还好,团队氛围还不错,至少运维大哥不会半夜三点打电话叫你修数据库(虽然测试妹子经常在我提测前五分钟告诉我“有个小问题”)。

最近我们组在重构一个商品爬虫系统——没错,就是那种每天从各大平台扒数据、解析、入库的活儿。以前用的是纯JDBC,代码写得跟意大利面条一样,改个字段要翻半天。领导看不下去了,直接拍板:“上MyBatis!这都2024年了,还手写SQL拼接?” 于是,我这个“海龟”就被推上了前线。

为什么是MyBatis?

其实我一开始也纠结过。Spring Data JPA那么香,写个接口就能CRUD,为啥还要折腾XML?但现实狠狠打了我的脸。

我们的爬虫系统需要频繁和不同结构的数据库表打交道——有时候是MySQL,有时候是ClickHouse(别问,问就是历史债)。而且很多查询条件动态得很,比如“如果用户设置了价格区间就加WHERE price BETWEEN x AND y”,这种场景用JPA的Specification写起来简直反人类。

MyBatis的灵活度就高多了。你可以完全掌控SQL,又能享受ORM的便利。特别是在性能优化方面,它给了你足够的操作空间。作为一个对性能优化有点执念的人(可能是被伦敦冬天的网速折磨出来的),这点太重要了。

踩坑实录:从Hello World到生产环境

环境搭建:别信网上那些过时教程

第一步当然是搭环境。我以为照着官网文档走就行,结果发现好多博客还在用mybatis-spring 1.x,而我们现在项目都是Spring Boot 3.x + Java 17。版本不匹配直接报错:

java.lang.NoSuchMethodError: 'void org.apache.ibatis.session.Configuration.setVfsImpl(java.lang.Class)'

查了半天才发现,MyBatis 3.5.10+ 才完全兼容Spring Boot 3。所以兄弟们,千万别直接复制粘贴三年前的教程

最终的pom.xml关键依赖如下:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
</dependency>

Mapper怎么写?注解还是XML?

这个问题困扰了我好几天。团队里有人坚持用注解(@Select, @Update),觉得清爽;也有人推崇XML,说复杂SQL必须分离。

我的结论是:简单CRUD用注解,复杂逻辑用XML

比如爬虫的商品入库,基本就是INSERT,用注解非常干净:

@Mapper
public interface ProductMapper {
    @Insert("INSERT INTO products (url, title, price, source) VALUES (#{url}, #{title}, #{price}, #{source})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(Product product);
}

但一旦涉及到多表关联或者动态条件,XML的优势就出来了。比如我们要根据爬虫任务ID查询所有失败记录,并支持按错误类型筛选:

<select id="selectFailedRecords" resultType="FailedRecord">
    SELECT * FROM crawl_records 
    WHERE task_id = #{taskId} AND status = 'FAILED'
    <if test="errorType != null">
        AND error_type = #{errorType}
    </if>
    ORDER BY created_at DESC
</select>

这种动态SQL用注解写的话,字符串拼接容易出错,可读性也差。XML虽然多了一个文件,但结构清晰,调试方便。

配置文件那些事儿

MyBatis的配置项真的不少。我在本地跑得好好的,一上测试环境就慢如蜗牛。后来发现是没开连接池!

Spring Boot整合MyBatis后,默认用的是HikariCP,但如果不显式配置,参数可能不理想。我们在application.yml里加了这些:

mybatis:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  type-aliases-package: com.example.crawler.model

spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000

特别注意map-underscore-to-camel-case: true,不然数据库字段created_at映射不到Java的createdAt属性,你会收到一堆null值——我当时还以为是爬虫解析错了,debug到凌晨两点。

性能优化实战:别让MyBatis拖后腿

作为对性能优化比较敏感的人,我肯定不能满足于“能跑就行”。以下是我在爬虫项目中总结的几个优化点:

1. 批量插入:拒绝N+1地狱

最初爬虫每抓到一个商品就调一次insert(),结果10万条数据跑了快一个小时。DBA找上门来:“你们是不是在DDoS我们的数据库?”

改成批量插入后,速度提升10倍不止:

@Mapper
public interface ProductMapper {
    @Insert({
        "<script>",
        "INSERT INTO products (url, title, price, source) VALUES ",
        "<foreach collection='list' item='item' separator=','>",
        "(#{item.url}, #{item.title}, #{item.price}, #{item.source})",
        "</foreach>",
        "</script>"
    })
    void batchInsert(@Param("list") List<Product> products);
}

注意这里用了<script>标签包裹,否则MyBatis会把整个语句当作普通字符串处理。

2. 二级缓存:谨慎使用

MyBatis有二级缓存功能,但我在生产环境踩过大坑。有一次开了全局二级缓存,结果爬虫更新商品价格后,前端还是显示旧数据。因为缓存没失效!

现在我的策略是:只对几乎不变的数据开缓存,比如商品分类字典表。而且一定要配合合理的缓存失效策略。

<cache eviction="LRU" flushInterval="300000" size="1024"/>

3. SQL日志:定位慢查询

开发阶段务必开启SQL日志!上面配置里的log-impl就是干这个的。但别在生产环境开——我见过同事忘了关,结果日志文件一天涨了50G,差点把磁盘撑爆。

更好的做法是用Arthas或者SkyWalking这类APM工具监控SQL性能。

和爬虫系统的深度集成

说到爬虫,其实MyBatis在这里帮了大忙。我们的爬虫架构是这样的:

  1. 调度器分发任务到多个爬虫实例
  2. 爬虫抓取并解析数据
  3. 数据通过MyBatis持久化到MySQL
  4. 后续有分析服务消费这些数据

难点在于高并发写入。多个爬虫实例同时往同一个表插数据,很容易锁表。我们做了几件事:

  • 表设计时用了自增主键(避免UUID带来的随机IO)
  • 插入前先做去重检查(用INSERT IGNOREON DUPLICATE KEY UPDATE
  • 关键表加了分区(按日期分区)

另外,为了防止爬虫挂掉导致数据丢失,我们引入了本地队列+定时flush机制。MyBatis的SqlSession可以复用,减少了连接开销:

// 伪代码
List<Product> buffer = new ArrayList<>();
for (Product p : crawledProducts) {
    buffer.add(p);
    if (buffer.size() >= BATCH_SIZE) {
        productMapper.batchInsert(buffer);
        sqlSession.commit(); // 手动提交
        buffer.clear();
    }
}

开发心得:工具链的选择很重要

在整个过程中,我发现工具真的能极大提升效率。除了MyBatis本身,这些配套工具也值得一试:

工具 用途 我的评价
MyBatis Generator 自动生成Mapper和Model 初期神器,但后期要手动调整
MyBatis Plus 增强版MyBatis 功能强大,但学习成本略高
PageHelper 分页插件 简单好用,但要注意SQL兼容性
Druid 数据库连接池+监控 必备!能看到实时SQL性能

特别是Druid的监控页面,让我在双11压测期间快速定位到了一个慢查询——原来有个JOIN没走索引。当时真的想给DBA磕一个。

写在最后

从被领导“逼着”学MyBatis,到现在能熟练用它优化爬虫性能,这段经历让我明白:框架只是工具,理解原理才能驾驭它

回国找工作这段时间,我越来越觉得,国内互联网虽然卷,但技术氛围是真的浓。大家愿意分享、愿意折腾,哪怕是为了应付产品经理的奇葩需求(笑)。这篇技术分享也算是给自己做个记录,顺便帮后来人少踩点坑。

对了,如果你也在做爬虫相关的持久层设计,强烈建议考虑MyBatis。它可能不像JPA那么“高级”,但在灵活性和可控性上,真的香。

最后自嘲一下:写完这篇文章,我的通勤时间又该开始了。希望地铁上别遇到产品经理,不然又要听他说“这个需求很简单”……


附:常见问题速查

Q: MyBatis和Hibernate/JPA怎么选?
A: 如果你需要完全掌控SQL(比如做性能优化、复杂报表),选MyBatis;如果是标准CRUD应用,JPA更省事。

Q: XML写多了会不会难维护?
A: 配合IDEA的MyBatis插件(比如Free MyBatis Plugin),跳转和提示都很方便。而且复杂SQL本来就应该独立管理。

Q: 如何处理数据库迁移?
A: MyBatis本身不提供migration工具,建议搭配Flyway或Liquibase使用。

Q: 单元测试怎么做?
A: 用H2内存数据库+MyBatis-Spring-Boot-Test,几行配置就能跑起来。

希望这篇开发心得对你有帮助。如果有什么问题,欢迎留言讨论——反正我下班路上有时间看手机 😅

评论 0

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