MyBatis基础教程:Java持久层框架入门
引言:为什么选择MyBatis?

我至今还记得刚进入互联网公司那会儿接手的第一个项目,是一个用户中心服务模块的重构任务。当时系统使用的ORM框架是Hibernate,虽然功能齐全、封装得很完整,但随着业务复杂度的上升,尤其是在处理高并发写入和复杂的查询条件时,我们发现Hibernate生成的SQL有时候难以优化,甚至会出现一些性能陷阱。
于是团队决定在新项目中尝试使用MyBatis作为数据访问层框架。起初我还挺犹豫的,毕竟从“全自动”切换到“半手动”,意味着要自己写很多SQL。但在深入使用之后,逐渐体会到了它的灵活性和可控制性,也让我真正理解了什么叫“SQL自由”。
今天这篇文章,我想结合我在实际项目开发中对MyBatis的应用经验,带你一起从零开始了解这个轻量而强大的持久层框架,并分享一些真实踩过的坑和踩完之后的心得体会。
项目背景与问题描述:一个典型的高并发写操作场景

我们的目标项目是一个电商平台的订单中心,用户下单后需要将订单信息写入多个表,包括:
orders订单主表order_items子订单项user_wallet用户余额变更记录
这些写操作需要在事务中完成。原来的实现采用Spring + Hibernate,每次插入数据都需要通过Entity对象映射,还要频繁调用save/update方法,导致代码臃肿且难以定位SQL执行瓶颈。
更严重的是,由于Hibernate自动维护实体状态,在高并发下容易出现脏读、重复提交等问题。我们在压测过程中就遇到了数据库死锁的情况,排查起来非常困难。
我们需要一个能精确控制SQL语句、方便调试性能瓶颈、并且适应复杂业务逻辑的数据访问层工具。MyBatis就成了我们技术选型中的一个重要选项。
解决方案:引入MyBatis做持久层设计
架构设计思路
我们采用分层架构,大致分为:
Controller -> Service -> Mapper (DAO) <-> MyBatis XML/注解
这样设计的好处在于:
- Controller 层只负责请求接收和参数校验;
- Service 层专注于业务逻辑,可以管理事务边界;
- Mapper 层交给MyBatis来对接数据库,由开发者编写SQL语句,提高可控性。
我们还引入了Spring Boot整合MyBatis Starter,简化了配置流程。同时为了保证数据一致性,在Service层开启声明式事务(@Transactional)来包裹多个DB操作。
另外,我们在Mapper中使用XML文件而不是纯注解的方式,是因为:
- SQL复杂时注解不便于阅读和维护;
- XML模板方式支持动态SQL,非常适合构建多条件查询或批处理逻辑;
- 有利于将SQL从Java代码中抽离出来,便于DBA审阅和优化。
代码实践:MyBatis核心用法示例
下面我通过几个关键的代码片段,来演示我们在项目中是如何使用MyBatis进行开发的。
数据库表结构简述(以订单为主)
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
order_no VARCHAR(32) NOT NULL UNIQUE,
total_amount DECIMAL(10, 2),
status TINYINT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE order_items (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id BIGINT,
product_id BIGINT,
price DECIMAL(10, 2),
quantity INT,
FOREIGN KEY (order_id) REFERENCES orders(id)
);
Java实体类定义(Order.java / OrderItem.java)
public class Order {
private Long id;
private Long userId;
private String orderNo;
private BigDecimal totalAmount;
private Integer status;
private Date createdAt;
// getter/setter 略
}
Mapper接口定义(OrderMapper.java)
public interface OrderMapper {
void insert(Order order);
void batchInsertItems(@Param("items") List<OrderItem> items);
@Select("SELECT * FROM orders WHERE user_id = #{userId}")
List<Order> findByUserId(Long userId);
}
XML映射文件(OrderMapper.xml)
<mapper namespace="com.example.mapper.OrderMapper">
<insert id="insert">
INSERT INTO orders (user_id, order_no, total_amount, status)
VALUES (#{userId}, #{orderNo}, #{totalAmount}, #{status})
</insert>
<insert id="batchInsertItems">
INSERT INTO order_items (order_id, product_id, price, quantity)
VALUES
<foreach collection="items" item="item" separator=",">
(#{item.orderId}, #{item.productId}, #{item.price}, #{item.quantity})
</foreach>
</insert>
</mapper>
可以看到,借助 <foreach> 这样的标签,我们可以很优雅地处理批量插入的问题。
踩坑经验:那些年我们一起掉进的坑
别看MyBatis简单易用,其实它也有很多细节需要注意。下面是我在开发过程中亲身踩过的一些坑,值得你注意。

1. 动态SQL拼接错误 —— 不小心用了 <if test=""></if> 的空字符串判断
MyBatis 的 <if test=""> 是基于OGNL表达式解析的,如果我们写成:
<if test="orderNo == ''"></if>
在某些版本里会抛异常,因为String类型不能直接比较。正确做法应该是:
<if test='orderNo != null and orderNo.trim() != ""'></if>
这其实是开发中经常忽视的一个小点,特别是在构造动态查询条件时,很容易漏掉非空判断导致报错。
2. 批量插入失败 —— JDBC URL没启用rewriteBatchedStatements
刚开始的时候我们在测试环境跑得顺风顺水,但上线后大批量插入时效率奇差无比。经过排查发现,原来是JDBC连接串没有加上rewriteBatchedStatements=true参数。
正确的MySQL连接URL应该类似如下:
spring.datasource.url=jdbc:mysql://localhost:3306/ecommerce?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
这个参数允许JDBC驱动把多个INSERT语句合并成一个批次提交,性能提升非常明显。
3. MyBatis日志显示乱码或未输出SQL —— 日志配置不到位
如果你没有配置好日志输出,可能会遇到两个常见问题:
- 控制台看不到执行的SQL语句;
- SQL语句中有中文乱码;
我们推荐使用Log4j或者Slf4j+Logback组合。具体配置MyBatis的日志行为,可以在application.properties中增加:
logging.level.com.example.mapper=debug
这样就能看到完整的SQL打印出来了。
效果总结:引入MyBatis后的收益

自从我们将项目的核心数据访问层更换为MyBatis以后,整体来看:
- SQL可见性大幅提升:再也不用像以前一样猜测Hibernate到底执行了哪条语句。
- 性能明显提升:特别是批量插入和更新场景下,相比之前提升了30%以上。
- 调试变得轻松:通过日志能看到具体的SQL执行过程,配合explain分析执行计划也很方便。
- 团队协作顺畅:DBA也可以参与SQL审核,减少线上风险。
最关键的是,当我们需要针对热点表做一些定制化索引优化时,SQL层面完全透明,修改起来也非常灵活。
经验分享:给Java后端开发者的建议
作为一个已经写了四年Java后端的老司机,我真心建议大家:
不要迷信“全自动”的ORM框架,像Hibernate这种重型ORM,适合快速CRUD的小项目,但对于大型复杂系统来说,往往“自动化带来的不是便利,而是隐患”。
学MyBatis先学会SQL。很多人一上来就想抄各种高级特性,结果写出一堆动态SQL却搞不清楚底层逻辑。记住一句话:“写得清楚的SQL才是好SQL。”
合理利用二级缓存和延迟加载。不过也要注意场景。例如订单相关数据变化频繁,就不适合开启二级缓存,否则可能引发数据不一致问题。
注意事务管理粒度。我们在早期曾犯过将事务包裹得过大的错误,导致大量资源被占用、并发下降。建议根据实际情况拆分事务块,必要时使用
@Transactional(propagation = Propagation.REQUIRES_NEW)控制粒度。做好SQL审计和监控。生产环境一定要有慢SQL采集机制,比如通过阿里云的PolarDB慢查询日志、或MySQL自身的slow log。MyBatis的SQL日志也要配合APM工具统一上报。
尾声:不止是MyBatis
最后想说,MyBatis只是我们通往高效数据访问之路的一个工具,而不是终点。近年来,我也在关注MyBatis Plus等增强型框架,它们进一步简化了CRUD逻辑,同时保留了MyBatis原有的灵活性。
未来如果有兴趣,我也会继续分享这类MyBatis生态组件的实际应用案例。当然,无论技术如何演进,写好SQL、理解数据库原理,始终是每一个后端工程师必须修炼的基本功。
希望这篇文章对你在学习或使用MyBatis的过程中有所启发。如果有什么问题或想法,欢迎留言交流!
——一位热爱编码的后端开发者

评论 0