MyBatis基础教程:从“手写JDBC”到优雅操作数据库的实战之路
作为一名在互联网公司工作的后端开发工程师,我每天都要和数据库打交道。刚入行那会儿,我写的代码基本是直接用JDBC连接数据库,结果不仅代码冗长复杂,而且维护起来极其痛苦。后来有幸接触到了MyBatis这个Java持久层框架,它改变了我对数据层开发的看法。
今天我就想结合自己在项目中使用MyBatis的经验,来写一篇接地气的基础教程,分享一下我是如何一步步掌握这个工具,并在工作中解决实际问题的。
项目背景与问题起因

事情要从我们团队之前做的一个电商后台系统说起。那是一个商品管理系统,包含用户管理、订单管理、库存管理等多个模块。刚开始的时候,为了快速验证业务逻辑,我直接使用了最原始的JDBC方式来做数据库交互。
比如,我需要查询某个用户的订单信息:
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
String sql = "SELECT * FROM orders WHERE user_id = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1, userId);
rs = ps.executeQuery();

while (rs.next()) {
Order order = new Order();
order.setId(rs.getInt("id"));
order.setUserId(rs.getInt("user_id"));
order.setAmount(rs.getDouble("amount"));
// ... 其他字段赋值
orders.add(order);
}

} catch (Exception e) {
// 异常处理
} finally {
// 关闭资源
}
这短短几十行代码看着简单,但背后隐藏的问题很多:
- 重复性高:几乎每个DAO类都会复制粘贴这段样板代码
- 维护成本高:一旦表结构变更,所有涉及的SQL语句都得跟着改
- 容易出错:资源未关闭、SQL注入等问题频发
- 扩展性差:后续要做分页查询、动态SQL、事务管理时非常麻烦
随着功能越来越多,我和同事们经常为了一个简单的数据库操作修改大量代码。这种低效且脆弱的架构,严重影响了我们的迭代速度。
解决方案:引入MyBatis

我们决定重构数据访问层,选用MyBatis作为ORM框架。虽然当时团队里不少人对MyBatis也不是特别熟,但大家都有一个共识:不能再继续手动写JDBC了!
初识MyBatis
MyBatis 是一个基于 Java 的持久层框架,它不像 Hibernate 这样的全自动 ORM 框架那样“重”,而是更加灵活,允许你通过 XML 文件或注解来定义 SQL 查询。它的理念很明确:SQL 应该由开发者控制,而不是框架自动生成。
我们在 pom.xml 中添加了以下依赖(Spring Boot 项目):
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
然后配置了数据库连接信息:
spring:
datasource:
url: jdbc:mysql://localhost:3306/my_shop
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
这样就能让 Spring 自动为我们创建 SqlSessionFactory 和 SqlSession 实例了。
简单示例:告别JDBC模板代码
我们先来看一个最简单的例子:查询某个用户的订单列表。
1. 创建实体类 Order.java
public class Order {
private int id;
private int userId;
private double amount;
private LocalDateTime createTime;
// getter/setter略
}
2. 创建Mapper接口 OrderMapper.java
@Mapper
public interface OrderMapper {
List<Order> selectOrdersByUserId(int userId);
}
3. 编写XML映射文件 OrderMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.OrderMapper">
<select id="selectOrdersByUserId" resultType="Order">
SELECT *
FROM orders
WHERE user_id = #{userId}
</select>
</mapper>
这样,当我们调用 orderMapper.selectOrdersByUserId(userId) 时,MyBatis 就会自动执行对应的 SQL 并将结果映射成 Order 对象列表。
是不是感觉比以前清爽多了?最关键的是不用再写那些 try-catch-close 资源释放的代码,更不用手动赋值字段!
动态 SQL:应对复杂查询场景
后来我们在做库存管理的时候,遇到了一个多条件筛选的需求:用户可以通过商品名、状态、分类等不同组合来过滤库存记录。
这时候传统的拼接 SQL 方式很容易出错,而 MyBatis 提供的 <if>、<choose>、<where> 标签就显得非常好用了。
例如:
<select id="queryStocks" parameterType="map" resultType="Stock">
SELECT *
FROM stocks
<where>
<if test="name != null and name.trim() != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="categoryId != null">
AND category_id = #{categoryId}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
</select>
这样一来,无论前端传哪些参数,都能正确构建出对应的 SQL 语句,避免了大量的字符串拼接工作,也减少了 SQL 注入的风险。
一对多关联映射:复杂关系也能轻松搞定
我们在做订单详情页时发现一个订单可能关联多个商品,这就涉及到了对象之间的嵌套关系。
MyBatis 支持通过 <association> 和 <collection> 来实现复杂的对象映射。
例如订单和商品是一对多的关系:
public class Order {
private int id;
private List<Product> products; // 一个订单对应多个商品
}
public class Product {
private int id;
private String name;
private double price;
}
XML映射如下:
<resultMap id="orderWithProducts" type="Order">
<id property="id" column="id"/>
<collection property="products" ofType="Product">
<id property="id" column="product_id"/>
<result property="name" column="product_name"/>
<result property="price" column="product_price"/>
</collection>
</resultMap>
<select id="getOrderDetail" resultMap="orderWithProducts">
SELECT
o.id,
p.id AS product_id,
p.name AS product_name,
p.price AS product_price
FROM orders o
LEFT JOIN order_products op ON o.id = op.order_id
LEFT JOIN products p ON p.id = op.product_id
WHERE o.id = #{orderId}
</select>
通过这样的配置,MyBatis 就会自动帮我们把商品集合装进 Order 对象中。以前这些逻辑我们要手动处理,现在一行代码都不用写。
多表联查 vs 单表分次查?
不过这里也带来了一个疑问:我们应该尽量使用一次 join 完成数据加载,还是分开多次查询比较好?
我们在压测的时候发现,在一些高并发场景下,过多的 join 操作会导致 MySQL CPU 占用飙升。特别是在订单详情这种关联三四张表的情况下,性能下降明显。
于是我们做了个小优化:将主表数据单独查出来,再根据 ID 批量查子表的数据。通过 Redis 缓存热点数据 + 合理的数据库索引设计,整体效率反而更高。
这也让我意识到,不要盲目追求一次 SQL 查询完成所有数据获取,合理拆分并利用缓存策略才是提升性能的关键。
效果总结:从“累死”到“优雅”

自从我们全面采用 MyBatis 之后,系统的数据访问层发生了翻天覆地的变化:
- 开发效率大大提高:以前写个查询要半小时,现在十分钟就能搞定。
- 代码可读性和可维护性变强了:SQL 和 Java 代码分离,便于维护和测试。
- 错误率大幅降低:自动映射避免了字段赋值错误,动态 SQL 减少了拼接问题。
- 性能优化更容易进行:通过查看 SQL 日志,我们可以快速定位慢查询和索引缺失问题。
更关键的是,我们在生产环境运行了一段时间后,MySQL 性能表现稳定,没有出现过严重的 DB 负载问题。
当然,这一切的前提是我们对 MyBatis 有深入的理解和良好的使用习惯。下面我再分享几个我在使用 MyBatis 过程中的经验和建议。
我的经验分享 & 使用建议

1. 不要滥用 #{} 和 ${}
这是新手最容易踩坑的地方之一。#{} 是预编译的方式,可以防止 SQL 注入;而 ${} 是字符串替换,在某些特殊情况下必须用,但一定要注意安全性。
举个例子:如果你想动态排序字段,可以这么写:
<select id="getAllOrders" resultType="Order">
SELECT * FROM orders
ORDER BY ${sortField} ${sortOrder}
</select>
但前提是 sortField 和 sortOrder 必须经过白名单校验,否则极易被攻击。推荐的做法是在服务层做字段合法性检查,而不是完全交给 SQL 层去处理。
2. 合理使用 ResultMap
有时候我们会遇到数据库字段和 Java 属性命名不一致的情况。MyBatis 提供了 <result> 标签来手动指定映射关系,避免字段匹配不上导致的空值问题。
举个例子,如果数据库中有字段名是 create_time,而 Java 类中属性是 createTime,就可以这样配置:
<resultMap id="orderResultMap" type="Order">
<id property="id" column="id"/>
<result property="createTime" column="create_time"/>
</resultMap>
这样即使命名风格不一致,也能正常映射。
3. 分页查询小心性能陷阱
我们在做后台管理页面时常常需要用到分页功能,常见的写法是:
SELECT * FROM users LIMIT #{pageNum}, #{pageSize}
但如果数据量过大(比如上百万条),这种偏移式的分页会导致性能急剧下降。MyBatis 默认并不强制限制这种写法,但在生产环境中,我们采用了“滚动分页”的方式(即使用上次查询的最后一个 ID 作为起点),有效解决了性能问题。
4. 结合 AOP 做日志打印和异常处理
我们项目中加了一个切面,用来拦截所有的数据库操作,打印 SQL 语句和执行耗时,方便排查线上问题:
@Aspect
@Component
public class MyBatisLogAspect {
@Around("execution(* com.example.mapper.*.*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
return result;
}
}
有了这套机制之后,我们能第一时间发现慢 SQL,及时进行优化。
5. 合理规划数据库索引
MyBatis 只是一个 SQL 执行引擎,真正的性能优化还得靠数据库本身的支持。我们在项目初期阶段就会和 DBA 一起规划好索引,特别是在频繁查询的字段上建立合适的复合索引,这对整个系统的响应时间影响非常大。
写在最后
其实 MyBatis 的内容远不止这些,本文只是介绍了我日常开发中最常用的一些核心功能。如果你刚刚开始接触它,不妨从最基础的 CRUD 开始练起,然后逐步尝试动态 SQL、关联映射等高级功能。
技术的成长往往是从“能用”到“会用”,再到“懂原理”。我也曾经历过那段一边查文档一边调试的时期,但现在回头看,MyBatis 让我把更多的精力集中在业务逻辑的设计上,而不是繁琐的数据操作上。
如果你正打算学习 MyBatis,希望这篇文章能给你一点方向和信心。记住,工具的本质是为了提高生产力,而不是增加复杂度。掌握了它,你会感受到一种前所未有的“掌控感”。
加油吧,未来的 Java 高手!

评论 0