MyBatis基础教程:Java持久层框架入门
在做后端开发这些年,我常常会遇到一种情况:项目初期为了快速出成果,大家更倾向于用JDBC或者简单的封装来处理数据库交互。但随着业务逻辑变复杂、数据量增长,维护起来越来越吃力。这时候我都会想起一个老朋友 —— MyBatis。
今天我想和大家分享一下我是如何从“手写SQL”过渡到使用 MyBatis 的,并结合我在实际项目中的经验,聊聊 MyBatis 到底解决了哪些痛点、它适合什么样的场景,以及我们该如何去理解和使用这个经典又实用的 Java 持久层框架。
一、从痛苦的手动SQL说起

事情得回到两年前,我当时在一个中型电商项目的初期参与开发。那个时候团队小、时间紧,技术选型也比较保守。我们选择直接使用 JDBC + Map 来进行数据库操作。
当时的想法很简单:
- 数据结构不复杂,不需要 ORM。
- 想减少学习成本,避免引入太多中间件。
一开始确实很轻快,写个 DAO 工具类,加个 SQL 拼接工具,配合连接池(比如 HikariCP),也能勉强应付基本 CRUD 和查询。
但没过多久问题就开始爆发了。
遇到的问题包括:
- 代码重复严重:增删改查都要重复写打开 connection、prepareStatement、遍历 ResultSet 的流程。
- SQL 分散在 Java 中:SQL 字符串散布在各个类里,难管理和调试。
- 类型转换繁琐:每次从结果集取值都得手动转成对应 Java 类型。
- 事务控制混乱:多线程操作下,很容易出现连接泄漏或事务未正确提交。
- 扩展困难:当需要分页查询、批量更新等高级功能时,没有成熟方案支撑。
那段时间,每次看 DB 层代码都觉得头皮发麻。特别是在上线前测试阶段,经常因为某个 SQL 写错字段导致整个接口出错,排查起来要靠日志翻半天。
二、第一次接触 MyBatis:为什么是我选择了它?

其实一开始我并不是特别想用 MyBatis,毕竟它既不像 Hibernate 那样完全自动化,也不像 Spring Data JPA 那么简洁优雅。但在当时的场景下,我需要一个既能保留 SQL 控制权,又能提高开发效率、易于维护的工具。
我开始调研各种持久层框架:
- Hibernate:虽然自动生成 SQL 很强大,但对于复杂的业务查询来说不够灵活,性能优化也较难掌控。
- Spring Data JPA:封装得很好,但对一些定制化查询支持较差,而且对实体模型依赖太强,不太适合我们那种“表结构频繁调整”的项目。
- MyBatis:看起来像是一个折中方案 —— 不自动映射,但给了你完整的 SQL 控制权;同时又帮你解决了很多底层 JDBC 繁琐的工作。
于是我们决定尝试迁移到 MyBatis。
三、MyBatis 是怎么帮我解决问题的?
迁移过程花了不到一周,但我们团队的编码效率明显提升。这里我结合实际例子来说明几个关键点:
1. SQL 易管理:XML 文件统一存放,告别字符串拼接
原来我们在 Java 代码中是这样写的:
String sql = "SELECT id, name FROM user WHERE status = " + status;
PreparedStatement ps = conn.prepareStatement(sql);
现在我们只需要在 XML 中定义:
<!-- UserMapper.xml -->
<select id="selectUsersByStatus" resultType="User">
SELECT id, name FROM user WHERE status = #{status}
</select>
再通过 Mapper 接口调用:
public interface UserMapper {
List<User> selectUsersByStatus(int status);
}
这种方式不仅让 SQL 更清晰,还能通过 <include> 标签复用常见字段,大大提高了可维护性。
2. 自动映射与类型安全:简化对象绑定
之前我们要手动从 ResultSet 中一个个取值:
while (rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
...
}
MyBatis 可以自动将结果映射为对象,甚至可以嵌套关联查询映射:
<resultMap id="userResultMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<association property="address" columnPrefix="addr_"
javaType="Address">
<id property="id" column="id"/>
<result property="detail" column="detail"/>
</association>
</resultMap>

<select id="findUserWithAddress" resultMap="userResultMap">
SELECT u.id, u.name,
a.id AS addr_id, a.detail AS addr_detail
FROM user u
LEFT JOIN address a ON u.address_id = a.id
WHERE u.id = #{userId}
</select>
这比手动映射干净太多了,而且不容易出错。
3. 事务控制与连接池集成:让开发安心
在 MyBatis 中,我们可以通过 SqlSession 或 Spring 整合后使用注解的方式处理事务:
@Transactional
public void updateUserAndOrder(User user, Order order) {
userMapper.update(user);
orderMapper.update(order);
}
底层由 MyBatis 帮我们自动管理连接和事务,不用再担心连接泄露、事务中断这些低级错误。
四、生产环境中的实际应用案例
在我们项目后期的订单模块改造中,遇到了一个典型的性能瓶颈:高并发下单场景下的库存锁定与扣减。
当时的表结构设计是一个简单的 stock 表,记录每个商品的可用库存:
| product_id | stock |
|---|---|
| 1001 | 20 |
原来的实现方式是在 Java 中先读取当前库存,判断是否充足,然后减一插入订单。这种方式在单机环境下没问题,但在分布式高并发下就容易出现“超卖”。
使用 MyBatis 加锁机制优化
我们将库存扣减的逻辑移到数据库层,在 MyBatis 的 SQL 中加上乐观锁:
UPDATE stock SET stock = stock - 1
WHERE product_id = #{productId} AND stock > 0
并在 DAO 中返回影响行数:
int rowsAffected = stockMapper.decreaseStock(productId);
if (rowsAffected == 0) {
throw new RuntimeException("库存不足");
}
这种写法简单、高效,而且利用数据库的原子性保证了线程安全。再加上 MyBatis 提供的缓存机制,大大提升了整体系统稳定性。
五、关于 MyBatis 的性能与架构建议
说到架构层面的设计,我觉得 MyBatis 作为持久层框架是非常灵活的,但也需要你在以下几个方面多注意:
1. 合理使用延迟加载
有些场景下,比如用户关联地址信息、订单信息较多,建议开启延迟加载:
<association property="orders" column="id"
fetchType="lazy"
columnPrefix="order_"
select="findOrdersByUserId"/>
这样能避免一次性加载大量无用的数据,提高接口响应速度。
2. 正确使用二级缓存
MyBatis 本身提供了一级缓存(默认开启)和二级缓存:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
对于只读或读多写少的数据(如城市列表、分类数据),开启二级缓存能显著减少数据库压力。
但需要注意几点:
- 不能用于实时性要求高的数据;
- 要考虑缓存穿透、击穿、雪崩等问题,可以结合 Redis 缓存一起使用;
- 在分布式环境下,各节点之间的缓存不同步会导致脏读,建议关闭二级缓存或采用共享缓存。
3. SQL 性能优化不容忽视
虽然 MyBatis 不强制你必须写得好 SQL,但它也不会替你优化语句。很多初学者会写出类似这样的 SQL:
SELECT * FROM orders WHERE user_id IN
(SELECT user_id FROM users WHERE ...)
这种嵌套子查询在数据量大时会非常慢。正确的做法是改写为 JOIN 查询,或者合理索引。
MyBatis 提供了 <choose> <when> 等动态 SQL 元素,可以帮助我们根据条件生成不同的执行计划,而不是一股脑地把所有条件都拼上:
<select id="searchOrders" parameterType="map" resultType="Order">
SELECT * FROM orders
<where>
<if test="userId != null">
AND user_id = #{userId}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
</select>
这类动态 SQL 非常适合构建查询接口。
六、运维层面的经验分享
除了编码之外,上线后的运维也是我关注的重点之一。
1. SQL 日志监控
线上出了问题,很多时候都是 SQL 执行异常。我们可以开启 MyBatis 的打印功能:
# application.yml(Spring Boot 项目)
logging:
level:
com.example.mapper: debug
这样可以在日志中看到每条执行的 SQL,方便追踪性能瓶颈。
2. 配置文件分离
我们在不同环境中(开发/测试/生产)使用的数据库配置往往不同,因此推荐将 mybatis-config.xml 和数据库连接参数抽离出来:
# application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=123456
而将 mapper 文件集中在 resources 目录下统一管理。
3. MyBatis 插件机制善加利用
MyBatis 支持拦截器(Interceptor),我们可以借此做一些全局性的操作:
- SQL 性能统计:记录每个查询的耗时;
- 分库分表路由:根据主键动态选择访问哪个数据库;
- 审计日志:记录谁在什么时间修改了哪条数据;
- 数据脱敏:对敏感字段进行加密或替换。
比如一个简单的 SQL 日志拦截器:
@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})})
public class SqlInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
long cost = System.currentTimeMillis() - start;
System.out.println("SQL execute time: " + cost + "ms");
}
}
}
这类插件一旦接入,就能帮助我们在不改动业务代码的前提下完成许多基础设施能力。
七、总结:MyBatis 是一把好刀,但也得会用
经过几年的实战沉淀,我对 MyBatis 的理解也在不断加深。它不是一个完美的 ORM 框架,但它足够灵活,足够轻量,足够贴近开发者的真实需求。
如果你正在做一个小型或中型项目,希望保持对 SQL 的控制权,同时又不想被繁琐的 JDBC 操作所束缚,那么 MyBatis 是一个非常好的选择。
当然,它也有它的局限:
- 需要自己维护 SQL;
- 学习曲线略陡(尤其是动态 SQL 和结果集映射);
- 对实体映射的支持不如 Hibernate/JPA。
所以我的建议是:
- 如果你追求极致开发效率,并且数据模型稳定,可以选择 JPA;
- 如果你需要高性能定制查询,同时又希望有良好的 SQL 管理能力,那么 MyBatis 更合适;
- 如果你是刚入行的开发者,建议先掌握 JDBC 的基本原理,再学 MyBatis,不然你会很难理解它的设计理念。
最后说一点自己的体会吧:编程这件事,工具只是手段,核心还是理解业务和数据。
MyBatis 不会替你写 SQL,但只要你愿意花心思去打磨每一个查询语句,它就能让你的系统更加稳健、高效、易维护。
附录:个人 MyBatis 学习路线图
- 掌握 MyBatis 基本语法(XML 映射、接口绑定)
- 理解一级缓存、二级缓存原理及使用场景
- 学习动态 SQL 写法(if、choose、foreach)
- 实践结果集映射(一对一、一对多)
- 尝试使用插件机制(如分页、性能分析)
- 结合 Spring Boot 使用 MyBatis Starter
- 了解 MyBatis Generator 快速生成代码
- 实战演练:完成一个完整的小型管理系统(CRUD+权限)
希望这篇经验分享对你有所帮助。如果你也在使用 MyBatis,欢迎留言交流你的使用心得 😊

评论 0