MyBatis入门:一个架构师的实战总结
作为一名Java后端开发老兵,我接触过不少持久层框架,从早期的Hibernate到如今广泛使用的Spring Data JPA、JOOQ等等。但在实际项目中使用频率最高、灵活性最强的,还得是MyBatis。
这篇文章不打算讲太多理论(网上资料已经够多了),我想以自己亲身经历的一个真实项目为例,和大家分享一下MyBatis是如何在实战中帮助我们解决问题的,以及我在使用过程中踩过的坑和一些经验教训。
背景介绍:为什么选了MyBatis?

去年我们团队接手了一个金融行业客户的订单管理系统重构项目。原来的老系统是用Hibernate写的,逻辑复杂、性能差,尤其是数据量大之后慢得不行。更糟的是,很多业务逻辑都封装在实体类里,导致维护成本越来越高。
我们评估过后,决定采用MyBatis作为新的持久层框架,主要出于以下几点考虑:
- 灵活控制SQL:金融系统的查询条件复杂多变,需要对SQL有更高的可控性;
- 轻量级且解耦:不像Hibernate那样侵入性强,POJO不需要继承特定类或加注解;
- 适合微服务架构:MyBatis可以很好地配合Spring Boot进行快速开发,适配未来的服务化拆分;
- 已有数据库设计成熟:表结构已稳定运行多年,不想为了ORM再去调整表设计。
于是,我们开启了MyBatis之旅。
遇到的挑战:不是所有的“简单”都真的那么简单

刚开始的时候,我们都觉得用MyBatis应该很简单——毕竟它只是个半自动的ORM框架嘛。但真正开始写的时候才发现,有些地方比想象中麻烦。
1. SQL管理和复用是个头疼的问题
我们在项目初期把所有SQL都写在XML里,但随着表越来越多、语句越来越复杂,XML文件变得越来越庞大。查找修改都很困难,特别是联合查询、动态SQL这些部分,稍不注意就容易出错。
2. 数据库字段与Java对象映射不够直观
虽然我们可以配置resultMap,但如果字段命名风格不统一(比如下划线 vs 驼峰),或者表关联比较深,很容易出现空值或类型转换问题。
3. 分页处理方式五花八门
刚开始大家都是自己拼LIMIT语句,结果测试发现不同数据库分页语法不一样,MySQL和PostgreSQL写法不一致,后期改起来很痛苦。
解决方案:MyBatis + 插件 + 最佳实践

面对这些问题,我们并没有退缩,而是逐步建立了一套基于MyBatis的开发规范,并引入了一些插件来提升效率。
1. 使用MyBatis-Plus简化CRUD操作
我们没有完全自己手写每个增删改查,而是采用了社区活跃、功能丰富的扩展框架——MyBatis-Plus。它提供了很多便捷方法,比如:
userMapper.selectList(new QueryWrapper<User>().eq("age", 18));
这极大地提升了开发效率,尤其是在做基础查询时省去了大量样板代码。
不过我们也意识到不能过度依赖MP的自动SQL生成能力,特别是一些复杂的关联查询还是需要手写SQL。
2. 动态SQL管理:XML+SQL片段抽取
针对SQL复用问题,我们采用了以下策略:
- 每张表一个XML文件,按模块归类;
- 抽取公共SQL片段,如常用的WHERE条件、JOIN部分;
- 使用
<include>标签提高可读性和复用性;
例如定义一个常用分页:
<sql id="pagination">
LIMIT ${pageSize} OFFSET ${(pageNum - 1) * pageSize}
</sql>
然后在具体查询中引用:
<select id="queryUsers" resultType="...">
SELECT * FROM user
<where>
<if test="name != null">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
</where>
<include refid="pagination"/>
</select>
这样既保持了灵活性,也方便后期维护。
3. 统一字段映射机制
为了避免字段名混乱带来的问题,我们做了两件事:
- 所有Java实体类默认使用驼峰命名规则;
- 使用
mapUnderscoreToCamelCase = true自动映射数据库的下划线字段; - 复杂映射单独配置
resultMap。
另外,在项目初始化阶段就明确制定了字段命名规范,并由Code Review强制执行,避免“谁想怎么写都行”。
实战中的代码结构设计

我们的项目整体采用Spring Boot + MyBatis的结构,大致如下:
src/
├── main/
│ ├── java/
│ │ └── com.example.project/
│ │ ├── controller/ // 控制器
│ │ ├── service/ // 业务逻辑接口
│ │ ├── mapper/ // Mapper接口
│ │ └── model/ // 数据模型(POJO)
│ │
│ └── resources/
│ ├── mapper/ // XML文件放置在此目录
│ └── application.yml // 配置文件
一个典型的Mapper接口:
public interface UserMapper {
List<User> queryUsers(@Param("name") String name,
@Param("pageNum") int pageNum,
@Param("pageSize") int pageSize);
}

对应的XML:
<select id="queryUsers" resultType="com.example.project.model.User">
SELECT *
FROM user
<where>
<if test="name != null">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
</where>
LIMIT #{pageSize} OFFSET #{pageNum}
</select>

这里有个小技巧:我们在SQL中用了LIMIT #{pageSize} OFFSET #{pageNum},但实际传入的时候要计算偏移量。这点后来通过封装PageUtil工具类统一处理了。
踩过的坑:你永远不知道下一个SQL会不会崩溃
说说我印象最深的几个“坑”,希望大家少走弯路。
1. 动态SQL拼接错误
一开始有个同事写了这样的逻辑:
<if test="ids != null and ids.size() > 0">
AND id IN (#{ids})
</if>
结果运行时报错说参数无法绑定。后来才意识到,#{ids}其实只能适用于单个值,而如果是集合要用<foreach>循环输出:
<if test="ids != null and ids.size() > 0">
AND id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
这个坑让我意识到:不要迷信模板写法,要理解底层是怎么处理参数的。
2. 日志打印SQL太难看
默认的日志打印非常丑,很难看清执行的SQL内容。后来我们换成了log4j2加上mybatis-logger,并配置了打印格式,还集成了Druid监控面板,方便实时查看慢查询和执行计划。
3. 分页插件使用不当
之前我们手动拼LIMIT语句,后来引入了PageHelper插件:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
但要注意使用姿势:
PageInfo<User> pageInfo = PageHelper.startPage(pageNum, pageSize).doSelectPageInfo(() -> {
return userMapper.queryUsers(name);
});
PageHelper一定要紧跟查询语句,否则会影响后续的SQL!
效果总结:性能和体验双提升
经过几个月的打磨和优化,最终效果还是相当不错的:
| 指标 | 旧系统 | 新系统 |
|---|---|---|
| 单个查询平均响应时间 | 850ms | 270ms |
| 系统吞吐量(TPS) | 350 | 980 |
| 代码可维护性 | 差(Hibernate侵入性强) | 好(清晰分离SQL与逻辑) |
| 开发效率 | 中等 | 高 |
尤其是SQL层面的调优变得容易多了。我们可以通过Druid看哪个语句慢、有没有命中索引、有没有全表扫描等问题,这些都是以前用Hibernate时很难做到的。
我的经验建议:别怕手写SQL,关键是你怎么组织它
如果你也在准备用MyBatis,或者已经在用但总觉得差点意思,那我给你几个建议:
✅ 明确职责边界:SQL归SQL,业务归业务
我们经常能看到有人把SQL直接写在Service层,甚至Controller里面。千万别这么干!MyBatis的核心优势就在于能让我们清晰地分离DAO层逻辑。
✅ 合理使用动态SQL,别滥用
MyBatis的动态SQL非常好用,但也容易写出一堆嵌套判断的复杂逻辑。建议拆分为多个独立的<sql>片段,提高可读性。
✅ 封装通用操作,减少重复代码
对于常见的CRUD操作,不妨封装成BaseMapper或BaseService,减少无意义的重复工作。
✅ 引入日志和监控工具
推荐搭配Druid、Prometheus、SkyWalking这一套生态,既能监控SQL执行情况,也能跟踪整个链路性能,排查问题快很多。
✅ 提前规划字段映射规则
建议尽早统一字段命名风格,并在数据库设计和代码模型之间建立一套清晰的映射关系,避免后续频繁调整。
结语:MyBatis不是银弹,但它是值得信赖的武器
说实话,MyBatis并不是万能的。如果你追求全自动的ORM映射,可能更适合选择Spring Data JPA。但如果你更注重对SQL的掌控力、需要高性能查询、并且愿意为灵活性付出一点学习成本,那MyBatis绝对是一个值得长期投入的技术栈。
在这次项目重构中,我不仅重新认识了MyBatis的强大之处,也更加坚定了“技术要落地、架构要实用”的理念。希望这篇来自真实战场的文章,能对你有所帮助。
如有任何问题欢迎留言交流,我们一起成长!

评论 0