MyBatis基础教程:一次真实项目中的技术选型与落地实践
大家好,我是一名Java后端开发者,在过去几年里参与过多个中大型项目的开发。其中有一段时间,我们团队在重构一个老旧的业务系统时,选择了MyBatis作为持久层框架,整个过程中遇到了不少坑,也积累了一些经验。今天我就想结合实际项目经历,跟大家分享一下我对MyBatis的理解、应用和一些实打实的踩坑经历。
一、为什么选择MyBatis?

事情要从我们公司一个内部管理系统说起。那个系统原本使用的是JDBC + DAO手动编写SQL的方式进行数据操作。随着业务增长,系统的复杂度不断提升,手动维护SQL语句变得越来越难管理,代码可读性差,容易出错。
我们评估过几种持久层方案:
- Hibernate:功能强大但学习成本高,对复杂SQL支持不够灵活,而且ORM映射在性能敏感场景下有时并不友好。
- Spring Data JPA:封装程度太高,控制力下降,不适合我们这种需要大量动态SQL和性能调优的场景。
- JPA + 自定义SQL:虽然可以部分解决灵活性问题,但混合编程风格导致维护困难,最终没有采纳。
最后我们选择了MyBatis——它兼具灵活性与易用性,允许我们完全掌控SQL的同时又能通过配置简化数据库交互过程。最重要的是它足够“轻”,适合嵌入到已有的Spring Boot项目架构中。
于是,我们决定在新版本的重构中全面引入MyBatis。
二、项目背景与挑战

1. 项目背景简述
我们的目标是将原来基于Servlet的手工DAO模式迁移到Spring Boot + MyBatis架构上,目标模块包括用户管理、权限分配、日志记录等多个子系统。原有的数据表结构已经存在,并且较为复杂,涉及多表关联、历史数据迁移以及大量的动态查询条件组合。
2. 面临的具体挑战
(1)SQL复用率低,重复代码多
由于原来的逻辑都是硬编码在DAO类中,相同的查询可能在不同位置出现多次,只是参数略有差异。
(2)性能瓶颈初现
某些报表查询接口在高峰期响应时间超过3秒,严重影响用户体验。
(3)复杂的查询条件难以维护
比如筛选订单列表时,有几十个字段的过滤条件,各种组合逻辑让代码臃肿不堪。
(4)事务管理和数据库兼容性问题
我们使用MySQL,而旧系统还有部分Oracle遗留代码,如何保证平滑过渡成了难题。
三、解决方案设计与实现思路
面对上述问题,我们制定了以下改造策略:
1. 统一接口设计与SQL集中管理
我们采用MyBatis的Mapper XML文件统一管理SQL语句,把所有数据访问接口抽象为Mapper接口(Interface),并通过@Mapper注解扫描注册成Spring Bean。
@Mapper
public interface OrderMapper {
List<Order> selectOrdersByConditions(Map<String, Object> params);
}
对应的XML文件中定义SQL:
<select id="selectOrdersByConditions" resultType="Order">
SELECT * FROM orders
<where>
<if test="status != null">
AND status = #{status}
</if>
<if test="userId != null">
AND user_id = #{userId}
</if>
<!-- 更多条件 -->
</where>
</select>
这样不仅提升了SQL的可读性和复用率,还方便了后续的性能优化和调试。
2. 动态SQL构建复杂查询
MyBatis的 <if>、<choose>、<set> 等标签非常实用,特别是构建条件查询、批量插入和更新的时候。
例如下面这个动态UPDATE语句:
<update id="updateOrderSelective">
UPDATE orders
<set>
<if test="status != null">
status = #{status},
</if>
<if test="remark != null">
remark = #{remark},
</if>
</set>
WHERE order_id = #{orderId}
</update>
这大大减少了空值覆盖的问题,也避免了手动拼接字符串带来的安全隐患。
3. 性能优化手段并行推进
为了应对慢查询问题,我们做了如下几件事:
- 使用
EXPLAIN分析执行计划,添加合适的索引。 - 引入MyBatis的二级缓存(搭配Ehcache)提升高频查询效率。
- 对某些复杂查询使用存储过程封装,减少网络IO。
- 对分页查询进行了优化,避免使用
LIMIT offset, size导致性能下滑。
此外,我们在关键接口中加入了SQL打印日志和耗时统计,便于线上监控和排查问题。
4. 抽象通用模板+分页插件
为了提高开发效率,我们抽取了一个基础的BaseMapper<T>,实现了常见的增删改查方法,配合通用工具类处理分页。
我们还集成了PageHelper插件,只需一行代码即可完成分页:
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAll();
PageInfo<User> pageInfo = new PageInfo<>(users);
这一招在处理前端分页请求时特别好使。
四、踩过的几个坑及解决办法
坑1:模糊查询 %xxx% 的写法错误
刚开始有个同事写了这样的SQL:
WHERE name LIKE '%#{name}%'
结果发现总是不命中索引,后来才意识到应该改成:
WHERE name LIKE CONCAT('%', #{name}, '%')
或者在Java代码中拼接完整参数传入。
启示:MyBatis不是万能的,有些细节还是得自己注意。
坑2:ResultMap 映射字段不匹配
有时候数据库字段命名规范不一致(比如大写、下划线),容易导致字段映射失败。
解决方案是自定义 resultMap:
<resultMap id="orderMap" type="Order">
<id property="id" column="ORDER_ID"/>
<result property="userName" column="USER_NAME"/>
</resultMap>
或者开启自动映射驼峰命名:
mybatis:
configuration:
mapUnderscoreToCamelCase: true
坑3:事务失效
我们有一个需求是要先更新订单状态,然后记录操作日志,这两个操作必须同时成功或失败。
开始的时候没加注解,事务无效,原因是Spring事务默认只拦截public方法,并且要被代理对象调用。
最终正确的做法是在Service层加上:
@Transactional
public void updateOrderAndLog(Order order) {
orderMapper.update(order);
logMapper.insert(new Log(...));
}
另外,还要记得事务方法不能被同一个类中的非事务方法直接调用,否则不会走代理,也就无法触发事务。
坑4:生产环境SQL泄露风险
最初没有关闭MyBatis的日志输出,导致控制台打出了完整的SQL语句,包含敏感数据参数,这在测试环境没问题,但在生产环境绝对不能暴露。
解决方式是在生产环境中设置:
logging:
level:
com.example.mapper: warn
或者在配置中关闭SQL打印。
五、落地效果与收益总结
整个重构上线之后,取得了不错的成效:
| 指标项 | 改造前 | 改造后 |
|---|---|---|
| 接口平均响应时间 | 1200ms | 680ms |
| 数据访问层代码行数 | 3000+行 | 缩减至1500行 |
| SQL错误发生次数/月 | 10次 | 小于2次 |
| 开发效率 | 新增接口需1天 | 半天搞定 |
而且通过MyBatis良好的扩展性,我们后期接入了Druid进行SQL监控,还做了读写分离的尝试,整体架构更加清晰。
六、几点实战建议和心得体会
作为一名一线开发者,我有几点体会分享给大家:
✅ 合理使用动态SQL,不要滥用
MyBatis最强大的地方就是它的 <if>、<foreach> 等动态标签,但也要注意合理使用。过于复杂的条件可能会让SQL难以维护和调试。
✅ 分离业务逻辑和数据访问层
即使你再喜欢写SQL,也不要在Service层混杂太多SQL逻辑,保持职责单一,有助于后期维护。
✅ 保持SQL简洁,必要时拆分逻辑
如果某条SQL太长太复杂,考虑是否可以拆分成两个步骤来做,甚至借助中间临时表来缓解压力。
✅ 学会用工具定位性能瓶颈
推荐大家平时多使用:
EXPLAIN分析执行计划SHOW PROFILE查看SQL各阶段耗时- 配合Druid等监控组件进行慢SQL捕捉
这些都比盲猜要靠谱得多。
✅ 多关注社区生态和扩展能力
MyBatis本身并不复杂,但生态很丰富,比如:
- MyBatis Plus 提供了CRUD自动化
- MyBatis Generator 自动生成Mapper和实体类
- PageHelper 支持便捷的分页功能
可以根据项目情况选择合适的插件来提升效率。
结语
说到底,MyBatis并不是一个“银弹”,但它确实为我们提供了一种平衡自由和效率的持久化方式。特别是在面对复杂查询、性能要求高的场景下,它展现出了极强的适应能力。
希望这篇文章能帮助正在学习或使用MyBatis的你少走些弯路,也能在你的项目中带来实实在在的提升。如果你有更好的经验或遇到什么疑难问题,欢迎留言讨论,我们一起成长。
最后送大家一句话共勉:
“代码是写给人看的,偶尔给机器跑一下。”
愿我们都能写出既高效又优雅的代码 🙏
如需获取本文配套的代码示例或工程目录结构,请私信我或留言,我会持续整理相关资料开放分享。

评论 0