MyBatis基础教程:Java持久层框架入门
上周五晚上 9 点,我正盯着屏幕上一行行红色的报错信息发呆——org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.example.mapper.UserMapper.selectById。那一刻我真的想砸电脑。
但不行啊,下周一就要提测了,而我还得在周末抽空刷行测题(别忘了,我是个正在备考公务员的在职程序员)。自从两个月前跳槽到这家新公司,项目技术栈从我熟悉的 Python/Go 切到了纯 Java 生态,MyBatis 就成了绕不开的一道坎。
说实话,在上一家公司写 Python 的时候,用 SQLAlchemy 或 Django ORM 简直是“所见即所得”;Go 里虽然没有那么高级的 ORM,但 GORM 也足够直白。可 MyBatis?它既不是全自动 ORM,又不像 JDBC 那么裸奔,属于那种“半自动”的中间态——听起来很美好,上手就懵逼。
为啥非得用 MyBatis?
我们组的技术栈比较传统:Spring Boot + MyBatis + MySQL。领导说这是“稳定、可控、便于 DBA 审核 SQL”。产品经理倒是开心了,因为每次改个字段他都能拉着 DBA 开会两小时,美其名曰“数据治理”。
其实我也理解,毕竟我们系统对接的是政务平台,SQL 必须清晰可审计,不能靠 ORM 自动生成一堆黑盒查询。所以 MyBatis 这种“手写 SQL + 自动映射”的模式,反而成了最佳选择。
但问题来了:作为一个刚入职两个月的“新人”,连 mapper.xml 放哪儿都不知道,更别说搞懂 #{} 和 ${} 的区别了。好在我有我的“外挂”——Claude 和 ChatGPT。每天下班前我都会把当天卡住的问题丢给它们,再结合《MyBatis 从入门到精通》这本书(对,就是那本封面像教科书的),一点点啃。
今天这篇技术分享,就是想把我踩过的坑、悟出的经验,用最直白的方式讲清楚。不为别的,就为了下次加班时,能少看一眼 Stack Overflow。
第一步:让 MyBatis 跑起来(别笑,很多人卡在这)
先说环境。我们的项目是 Spring Boot 3.x,用的是 MyBatis-Spring-Boot-Starter。依赖如下:
<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/test_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.model
configuration:
map-underscore-to-camel-case: true
重点来了:mapper-locations 这个配置,决定了 MyBatis 去哪找你的 XML 文件。我第一天就漏了这行,结果启动时报 Invalid bound statement,折腾了俩小时才发现是路径没配。
另外,map-underscore-to-camel-case: true 是神器!数据库字段一般是 user_name,Java 实体类是 userName,开这个配置就自动映射,省得你写 @Results。
写个最简单的 CRUD
假设有个用户表:
CREATE TABLE user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_name VARCHAR(50),
email VARCHAR(100)
);
对应的 Java 实体:
public class User {
private Long id;
private String userName;
private String email;
// getter/setter 省略
}
然后是 Mapper 接口:
@Mapper
public interface UserMapper {
User selectById(Long id);
void insert(User user);
}
注意:接口上要加 @Mapper 注解,否则 Spring 找不到 Bean。或者你也可以在启动类加 @MapperScan("com.example.mapper"),批量扫描。
接着是 UserMapper.xml(放在 resources/mapper/ 下):
<?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="User" parameterType="long">
SELECT id, user_name, email FROM user WHERE id = #{id}
</select>
<insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (user_name, email) VALUES (#{userName}, #{email})
</insert>
</mapper>
这里有几个关键点:
namespace必须和接口全限定名一致;resultType="User"能直接写类名,是因为前面配了type-aliases-package;useGeneratedKeys="true"是为了获取自增主键,keyProperty="id"表示把生成的 ID 回填到user.id字段。
我当时第一次写 insert 时忘了 useGeneratedKeys,结果 user.getId() 一直是 null,还以为是事务没提交……后来查日志发现 SQL 根本没返回 ID。
动态 SQL:比写 Python 字符串还爽?
MyBatis 最让我惊艳的,其实是它的动态 SQL。比如我要根据条件查用户:
<select id="searchUsers" resultType="User">
SELECT id, user_name, email FROM user
<where>
<if test="userName != null and userName != ''">
AND user_name LIKE CONCAT('%', #{userName}, '%')
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
</where>
</select>
<where> 标签会自动去掉开头的 AND,避免语法错误。这比我在 Go 里拼字符串安全多了——再也不用担心 SQL 注入(只要别手贱用 ${})。
说到 ${} 和 #{} 的区别:
| 语法 | 作用 | 风险 |
|---|---|---|
#{value} |
预编译占位符,等价于 ? |
安全 |
${value} |
直接字符串替换 | 高危!易导致 SQL 注入 |
除非你要动态拼表名或列名(比如分表),否则永远用 #{}。有一次我手滑写了 ${id},测试直接报“SQL 注入漏洞”,被安全团队追着问了一天……
性能与可维护性:我的执念
作为一个注重代码可读性的人,我不喜欢把 SQL 全塞进 XML 里搞得乱七八糟。所以我们团队约定:
- 简单查询用 XML;
- 复杂逻辑(比如多表关联、分页聚合)封装成视图或存储过程;
- 所有 SQL 必须加注释,说明业务场景。
比如:
<!-- 查询近30天活跃用户,用于首页推荐 -->
<select id="findActiveUsers" resultType="User">
SELECT u.* FROM user u
INNER JOIN login_log l ON u.id = l.user_id
WHERE l.login_time >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY u.id
</select>
另外,MyBatis 的二级缓存默认是关闭的。我们线上开了 Redis 做分布式缓存,MyBatis 只做一级缓存(SqlSession 级别)。毕竟缓存一致性是个大坑,宁可慢一点,也不能脏读。
对比其他语言生态
说到这儿,忍不住对比下我熟悉的 Python 和 Go:
| 特性 | MyBatis (Java) | SQLAlchemy (Python) | GORM (Go) |
|---|---|---|---|
| 映射方式 | 手写 SQL + 自动映射 | 全自动生成或手写 | 结构体标签 + 方法链 |
| 学习曲线 | 中高(XML + 注解混合) | 中 | 低 |
| SQL 控制力 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐(可通过 text()) | ⭐⭐⭐⭐ |
| 调试难度 | 高(XML 路径、命名空间易错) | 低 | 中 |
MyBatis 的优势在于对 SQL 的完全掌控,适合强监管、高合规的场景(比如政务、金融)。而 Python/Go 的 ORM 更适合快速迭代的互联网产品。
不过说实话,如果让我重新选,我还是更喜欢写 Python——毕竟 User.query.filter(User.name.like('%张%')) 比写 XML 爽多了。但考公路上,稳定压倒一切,Java 生态的“笨重”反而成了优点。
最后:给 fellow 程序员的建议
如果你和我一样,刚接触 MyBatis,记住三点:
- XML 路径和命名空间是玄学,配错就报
BindingException,仔细检查; - 永远用
#{},除非你真的知道自己在干嘛; - 开启 MyBatis 日志:在
application.yml加:
这样能看到实际执行的 SQL 和参数,debug 效率翻倍。logging: level: com.example.mapper: debug
至于我?今晚还得继续刷行测。白天写 Java,晚上背“言语理解”,感觉自己像个分裂的赛博格。但没办法,程序员这行太卷了,考公至少是个退路。
希望这篇分享能帮你少踩几个坑。如果对你有用,欢迎点赞转发——毕竟,每一个还在加班写 CRUD 的程序员,都值得被世界温柔以待。
P.S. 那本《MyBatis 从入门到精通》,我翻烂了前三章,后面基本靠官方文档和 AI。书籍适合打基础,实战还得靠项目喂。共勉!

评论 0