MyBatis基础教程:Java持久层框架入门
从 JDBC 到 MyBatis:我在项目中踩过的坑与成长之路

我是一个在后端开发领域摸爬滚打了五年的程序员,这几年里接触过很多不同的 Java 框架。要说哪个让我印象最深、用得最多也最有感触的,那肯定是 MyBatis。
记得刚工作没多久接的第一个项目是内部的一个订单管理系统,当时团队已经使用了 Spring,但数据访问层依然是纯 JDBC。写了一段时间之后我发现,数据库操作部分的代码冗余度极高,大量重复的 Connection、PreparedStatement 和 ResultSet 处理让我焦头烂额。更别说业务逻辑一复杂,SQL 跟 Java 代码混在一起简直难以维护。当时我就意识到,必须得找个更好的方式来处理数据库交互。
后来我们引入了 MyBatis —— 这个轻量级却功能强大的持久层框架。这篇文章就是基于我的真实项目经验来写的,希望通过自己的踩坑经历,帮助正在学习或者准备上手 MyBatis 的同学少走弯路。
项目背景和遇到的问题
我们当时的系统需要对接一个 MySQL 数据库,核心模块包括用户信息管理、订单创建、库存控制等。因为是公司内部的旧系统改造,所以不能直接上 Hibernate 那种全 ORM 框架,而是选择了灵活可定制的 MyBatis。
最初的问题有几个:
- SQL 写在哪里?怎么组织结构?
- 怎么把查询结果映射到 Java 对象?
- 多表关联查询如何优雅处理?
- 事务管理怎么控制?
特别是在第三点上,我第一次做多表 join 查询的时候,返回的数据结构比较复杂,不知道该怎么正确地进行 resultMap 映射。还有就是刚开始没有合理划分 Mapper 接口和 XML 文件的路径结构,导致后期维护起来特别困难。
解决思路和技术选型
我们选择 MyBatis 主要是出于以下几个原因:
- 灵活可控:可以完全掌控 SQL 的编写,而不是像 Hibernate 那样隐藏太多细节。
- 性能优化空间大:适合我们这种对性能有一定要求的场景,比如批量插入、分页等。
- 学习成本相对较低:团队成员都能快速上手,不像 Hibernate 那样概念太多。
结合 Spring Boot 的集成能力(其实项目中期升级到了 Spring Boot),MyBatis 可以非常方便地进行配置管理。我们还引入了 PageHelper 来做分页查询,Lombok 减少样板代码,让整个开发效率提升了不少。
接下来我会从实际开发角度出发,讲讲我是怎么一步步搭建起这个 MyBatis 架构的。
代码实践:基本结构与关键代码
1. 基本工程结构
我们采用的是标准的 Maven 结构:
src
├── main
│ ├── java
│ │ └── com.example.orderapp
│ │ ├── config // 配置类
│ │ ├── controller // 控制器
│ │ ├── service // 服务层接口与实现
│ │ ├── mapper // Mapper 接口
│ │ └── model // 实体类
│ ├── resources
│ └── mapper // XML 映射文件
└── test // 单元测试
2. MyBatis 核心配置(application.yml)
spring:
datasource:
url: jdbc:mysql://localhost:3306/order_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/**/*.xml
type-aliases-package: com.example.orderapp.model
3. Mapper 接口定义
举个简单的例子:根据用户 ID 获取订单信息。
@Mapper
public interface OrderMapper {
Order selectById(Long id);
List<Order> selectByUserId(Long userId);
int insert(Order order);
}
4. XML 映射文件(OrderMapper.xml)
这是最重要的部分,也是 MyBatis 的灵魂。
<mapper namespace="com.example.orderapp.mapper.OrderMapper">
<resultMap id="BaseResultMap" type="com.example.orderapp.model.Order">
<id column="order_id" property="id"/>
<result column="user_id" property="userId"/>
<result column="product_code" property="productCode"/>
<result column="amount" property="amount"/>
<result column="status" property="status"/>
</resultMap>
<select id="selectById" resultMap="BaseResultMap">
SELECT * FROM orders WHERE order_id = #{id}
</select>
<select id="selectByUserId" resultMap="BaseResultMap">
SELECT *
FROM orders
WHERE user_id = #{userId}
</select>
<insert id="insert">
INSERT INTO orders (user_id, product_code, amount, status)
VALUES (#{userId}, #{productCode}, #{amount}, #{status})
</insert>
</mapper>
可以看到,这里定义了查询语句和字段映射关系,这就是所谓的“手动 ORM”。
踩坑经验分享
在整个开发过程中,有几个坑是我亲身体验过的,现在回想起来还挺有意思的。
1. 多表关联映射问题
一开始我要查询用户的订单列表,并显示用户姓名。于是我写了如下 SQL:
SELECT o.*, u.name as user_name
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.user_id = #{userId}
这时候我发现没法直接用原来的 BaseResultMap,因为里面缺少 userName 字段。怎么办呢?
我最初的做法是在 XML 中新增了一个 <result> 标签来处理额外字段:
<resultMap id="OrderWithUser" type="...">
<result property="userName" column="user_name"/>
...
</resultMap>
但随着业务复杂度增加,这样的硬编码很不友好,也不便于复用。
解决方法:
于是我们采用了 Java 注解的方式,配合实体类属性注解来做映射。例如:
public class OrderWithUserInfo {
private Long id;
private String productCode;
private Integer amount;
@Column(name = "user_name")
private String userName;
}
同时,在 MyBatis 中通过别名匹配字段,不再依赖 XML 的强耦合写法。
2. 分页查询卡顿问题
我们的订单数据越来越多,使用 LIMIT 分页时性能逐渐变差。尤其是在大数据量下,offset 分页会变得越来越慢。
解决办法:
我们改用了 游标分页(Cursor-based Pagination)。即每次记录最后一条数据的 ID 或者时间戳,下次请求时带上该值进行定位。例如:
SELECT *
FROM orders
WHERE create_time > #{lastTime}
ORDER BY create_time ASC
LIMIT 20
这种方式大大提高了查询效率,尤其在数据量大的情况下。
3. Mapper 扫描不到
有时候你明明写了 Mapper,但在启动时报错说找不到某个方法。这个问题其实挺常见的。
常见原因:
- 没加
@Mapper注解 - 没有在启动类上加
@MapperScan - Mapper XML 路径配置错误
- 包名跟类型别名设置冲突
建议做法:
统一使用 Spring Boot 提供的自动扫描方式:
@SpringBootApplication
@MapperScan("com.example.orderapp.mapper")
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}

并且确保 mybatis.mapper-locations 配置指向了正确的 XML 路径。
效果总结
引入 MyBatis 后,项目的开发效率显著提升:
- SQL 更加清晰、易维护,尤其是通过 XML 管理后,DBA 也能参与 review。
- 使用 PageHelper 后分页变得简单,虽然后来换成了自定义的游标方案。
- 通过 resultMap 和别名映射,Java 对象与数据库之间的映射更加直观。
- 性能得到了保障,特别是在做数据量较大的查询时,比之前的 JDBC 实现快了不少。
另外,我们在部署到生产环境后也积累了一些运维经验。
生产环境中的小贴士
1. SQL 日志打印要谨慎
在测试环境中开启 SQL 打印很方便调试:
logging:
level:
com.example.orderapp.mapper: debug
但在生产环境中一定要关闭或限制日志级别,避免日志爆炸。
2. 动态 SQL 要小心拼接
MyBatis 的 <if>、<choose> 等动态标签很好用,但如果嵌套过多、逻辑太复杂,会让 SQL 变得难以调试。建议保持每个 <if> 的逻辑单一,并在单元测试中尽量覆盖所有分支。
3. 使用二级缓存要慎重
MyBatis 支持一级和二级缓存,特别是二级缓存可以跨 SqlSession 共享数据。不过要注意失效机制和一致性问题。对于高并发更新频繁的表,不要轻易开启。
4. 批量操作尽量自己实现
虽然 MyBatis 本身支持 <foreach> 来做循环插入,但性能并不理想。我们可以手动拼接批处理语句,例如使用 addBatch() + executeBatch(),然后配合事务控制。
给新手的一些建议
如果你刚开始学 MyBatis,下面几点可能会对你有帮助:
- 先理解传统 JDBC 的流程,再过渡到 MyBatis,有助于理解封装背后的工作原理。
- 不要一开始就追求“完全自动化”,MyBatis 的精髓在于“半自动化”的灵活性。
- XML 和接口分离,命名统一规范。推荐使用类似
UserMapper.java+UserMapper.xml这样的命名方式。 - 多查文档,官方中文文档写得很详细,GitHub 上也有大量 demo 可参考。
- 善用工具,比如 IDEA 插件可以帮你自动跳转到对应的 XML 方法、生成 result map 等。
尾声:MyBatis 是否还值得学?
现在很多人说 ORM 框架都应该用 JPA、Hibernate,或者干脆直接使用 Spring Data JPA。但我觉得 MyBatis 依然有它独特的魅力,尤其是在中大型项目中,当性能、灵活性和 SQL 可控性成为瓶颈时,它依旧是最优的选择之一。
而且现在很多新的中间件也在向 MyBatis 靠拢,比如 Druid、ShardingSphere,它们都很好地兼容 MyBatis。社区活跃度也很高,各种插件层出不穷。
总之,希望这篇结合我多年实战经验写出来的文章,能帮你在学习 MyBatis 的路上少踩几个坑,走得更快一些。如果有任何疑问或者想探讨的地方,欢迎留言交流!
共勉!

评论 0