MyBatis基础教程:Java持久层框架入门
去年考研查分那天,我盯着屏幕看了整整五分钟——政治58,英语52,专业课崩得比我的发际线还快。那一刻我突然意识到,深圳湾的晚霞再美,也照不进图书馆四楼那个没空调的自习室了。收拾好书包,投了简历,三周后面试进了南山科技园一家做电商中台的小厂(不是腾讯,但离腾讯大厦走路十分钟,每天都能看见鹅厂人排队拿免费咖啡,羡慕得我啃煎饼果子都少加了个蛋)。
入职第一周,mentor扔给我一个任务:“把老系统里那坨用JDBC手写的DAO层重构掉,现在改个字段要改八处SQL,测试那边天天在群里@你。” 我打开项目,满屏的PreparedStatement和ResultSet,连个连接池都是手动管理的。当时真的想砸电脑——这代码是谁写的?哦,是我自己昨天写的初版。
于是,MyBatis 这个名字被提上了日程。
为啥是 MyBatis?不是 JPA?不是 Hibernate?
我们团队技术栈偏“务实派”——老板原话是:“别整那些花里胡哨的ORM自动映射,SQL必须可控,性能不能靠猜。” 所以像 Spring Data JPA 这种“魔法太多”的方案直接被否了。而 MyBatis 的核心哲学特别对味:你写 SQL,我来执行,别的不多管。
简单说,MyBatis 就是个 SQL 模板引擎 + 对象映射器。它不会替你生成 SQL(这点和 Hibernate 完全相反),而是让你把 SQL 写在 XML 或注解里,然后自动把结果集映射成 Java 对象。既保留了 SQL 的灵活性,又省去了手写 rs.getString("name") 的祖传代码。
对我们这种天天和复杂查询、多表关联、分库分表打交道的后端来说,简直是救命稻草。
快速上手:三步走,告别 JDBC 祖传代码
第一步:引入依赖(Maven)
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<!-- 如果用 Spring Boot -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
💡 小贴士:Spring Boot 项目直接用 starter,自动配置 SqlSessionFactory 和 MapperScanner,省心到哭。
第二步:写个实体类(Java Bean)
public class User {
private Long id;
private String username;
private Integer age;
// getter / setter 省略(Lombok 保命)
}
第三步:定义 Mapper 接口 + SQL 映射
这里有两种写法,我偏好 XML 方式,因为 SQL 复杂时可读性高,还能被 DBA 直接 review。
// UserMapper.java
public interface UserMapper {
User selectById(Long id);
List<User> selectByUsername(String username);
void insert(User user);
}
对应的 UserMapper.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.example.mapper.UserMapper">
<select id="selectById" resultType="com.example.model.User">
SELECT id, username, age FROM user WHERE id = #{id}
</select>
<select id="selectByUsername" resultType="User">
SELECT * FROM user WHERE username LIKE CONCAT('%', #{username}, '%')
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user(username, age) VALUES(#{username}, #{age})
</insert>
</mapper>
注意几个细节:
resultType可以写全限定名,也可以配别名(在 mybatis-config.xml 里注册)useGeneratedKeys="true"是为了拿到数据库自增 ID 并回填到 Java 对象里——上线第一天我就忘了这个,导致后续逻辑拿不到主键,差点背锅#{}是预编译占位符,防 SQL 注入;${}是字符串替换,慎用!
踩坑实录:那些让我加班到凌晨的“小惊喜”
坑1:Mapper 扫描不到,报 Invalid bound statement
原因千奇百怪,但最常见的是:
- XML 文件没放在 resources 下对应 package 路径(比如接口在
com.example.mapper,XML 也得在resources/com/example/mapper/) - Spring Boot 没加
@MapperScan("com.example.mapper") - XML 的 namespace 写错了(必须和接口全路径一致)
上周五晚上十点,我就因为 namespace 少了个字母,在 VSCode 里反复检查半小时,最后靠 Ctrl+Shift+F 全局搜索才发现。那一刻我默默打开了 B 站《程序员防脱发指南》。
坑2:对象属性是驼峰,数据库字段是下划线,映射失败
比如 Java 字段叫 createTime,数据库是 create_time。默认情况下 MyBatis 不会自动转换。
解决方案:开启 驼峰命名自动映射
# application.yml
mybatis:
configuration:
map-underscore-to-camel-case: true
或者在 mybatis-config.xml 里配:
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
坑3:动态 SQL 写错,线上慢查询爆炸
产品经理临时加需求:“用户列表要支持按多个条件组合筛选”。于是我写了如下 XML:
<select id="searchUsers" resultType="User">
SELECT * FROM user
WHERE 1=1
<if test="username != null">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
</select>
看起来没问题?但当所有参数都为 null 时,SQL 变成 SELECT * FROM user WHERE 1=1 —— 全表扫描!双11期间流量一上来,DB CPU 直接飙到 90%,运维在群里疯狂艾特我。
正确做法:用 <where> 标签,它会自动去掉多余的 AND,且当内部无条件时,整个 WHERE 不生成。
<select id="searchUsers" resultType="User">
SELECT * FROM user
<where>
<if test="username != null">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
</where>
</select>
从此以后,我对 <where>、<trim>、<foreach> 这些标签敬若神明。
性能与资源管理:别让 MyBatis 成为你的拖油瓶
MyBatis 本身很轻量,但用不好照样吃光你的 JVM 内存。分享几个生产经验:
| 配置项 | 推荐值 | 说明 |
|---|---|---|
default-fetch-size |
1000 | 控制 ResultSet 一次从 DB 拉多少行,避免 OOM |
local-cache-scope |
STATEMENT | 避免跨语句缓存污染(尤其在长事务中) |
| 开启二级缓存 | 谨慎! | 仅适用于只读或低频更新数据,否则缓存一致性难维护 |
另外,SQL 本身才是性能瓶颈。MyBatis 不会优化你的 SQL,所以:
- 多表 JOIN 要有索引
- 避免
SELECT * - 分页用
LIMIT+ 合理偏移(大数据量考虑游标分页)
我们组有个不成文规定:所有新 SQL 上线前,必须过 DBA 的 EXPLAIN 审核。有一次我写了 ORDER BY RAND(),被 DBA 直接打回来:“你是想让 MySQL 全表排序到明年吗?”
结语:从考研失败到写出第一行 MyBatis 代码
现在回头看,考研失败未必是坏事。如果当初上岸了,可能还在实验室调参,而不是在深圳的夏夜里,一边吹着空调,一边用 MyBatis 把复杂的业务逻辑变成优雅的 SQL。
MyBatis 的魅力在于它的“克制”——它不试图解决所有问题,而是做好一件事:让 Java 和数据库之间的对话更清晰、更可控。对于需要深度掌控 SQL 的后端开发者来说,它是值得信赖的伙伴。
如果你也在重构祖传代码,或者刚入职被扔进“屎山”项目,不妨试试 MyBatis。至少,它能让你少写 80% 的样板代码,多留点时间去抢鹅厂门口的免费咖啡。
最后附一句 mentor 的忠告:“框架只是工具,懂原理才能不被坑。”
所以,下次我们聊聊 MyBatis 的插件机制和 Executor 源码——毕竟,底层原理才是咱程序员的硬通货。
(完)
作者:一个从自习室转战工位的 Java 程序员,现居深圳,日常在 VSCode 里装 50+ 插件,梦想是写出零 Bug 的代码(虽然从未实现)。

评论 0