MyBatis基础教程:Java持久层框架入门
上周五晚上十点半,我瘫在工位上盯着屏幕发呆,脑子里还在回放产品经理那句“这个需求很简单,明天上线”。说实话,刚从英国回来那会儿,我还以为国内互联网节奏已经放缓了,结果入职这家跨境电商公司后才发现——卷,还是那个熟悉的卷。
我是去年秋天回国的海归硕士,在伦敦念完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在这里帮了大忙。我们的爬虫架构是这样的:
- 调度器分发任务到多个爬虫实例
- 爬虫抓取并解析数据
- 数据通过MyBatis持久化到MySQL
- 后续有分析服务消费这些数据
难点在于高并发写入。多个爬虫实例同时往同一个表插数据,很容易锁表。我们做了几件事:
- 表设计时用了自增主键(避免UUID带来的随机IO)
- 插入前先做去重检查(用
INSERT IGNORE或ON 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