MyBatis基础教程:Java持久层框架入门
MyBatis实战入门:从“纯JDBC”到灵活持久层的蜕变之路

开篇:我们是如何走到MyBatis面前的?
那是我加入公司初期,团队正在开发一个内部使用的订单管理系统。项目不算大,但要求快速上线,同时具备一定的灵活性来应对未来可能的需求变更。当时我们的数据访问层完全是基于纯JDBC编写的,也就是所谓的DAO模式:手动拼接SQL、处理Connection、ResultSet等等。
随着业务逻辑逐渐复杂,我们发现代码中出现了大量重复的模板代码,不仅维护困难,而且在处理关联查询时尤为吃力。更糟的是,数据库字段一改,所有相关DAO都要修改,测试也得重新覆盖一遍,效率很低。这让我们意识到,是时候引入一个合适的持久层框架了。
经过调研和讨论,我们最终选择了 MyBatis。理由很简单:它不像Hibernate那样完全隐藏SQL,也不像Spring Data JPA那样依赖实体类与表结构高度一致,而是提供了一个非常灵活的中间层——你可以自由写SQL,又能享受框架带来的便捷性。
接下来,我就想结合那次实际经历,分享一下自己是怎么一步步从“手写JDBC”过渡到使用MyBatis,并从中体会到它的优势和一些需要注意的地方。
问题描述:为什么传统JDBC方式不适用了?
刚开始接手项目时,我们的DAO类大概长这样:
public class OrderDao {
public List<Order> getOrdersByUserId(int userId) throws SQLException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = ConnectionUtil.getConnection();
String sql = "SELECT * FROM orders WHERE user_id = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1, userId);
rs = ps.executeQuery();
List<Order> orders = new ArrayList<>();
while (rs.next()) {
Order order = new Order();
order.setId(rs.getInt("id"));
order.setUserId(rs.getInt("user_id"));
order.setTotalAmount(rs.getBigDecimal("total_amount"));
order.setStatus(rs.getString("status"));
orders.add(order);
}
return orders;
} finally {
// 关闭资源...
}
}
}
这段代码看起来很标准,但有几个问题很快暴露出来:
- 代码重复严重:几乎每个方法都要处理连接、异常捕获、资源关闭,甚至SQL语句。
- 难以扩展:一旦涉及到多张表联查,对象映射变得复杂,处理起来麻烦。
- 易出错:手动设置参数容易搞错顺序或类型;手动关闭资源也很容易忘记关某一项。
- 耦合度高:SQL硬编码在Java类里,修改SQL就得改Java代码,重新编译部署。
这些问题在中小型项目中尤其明显。虽然我们可以封装个基类简化操作,但如果真要做点灵活的东西,比如动态SQL、批量插入、关联对象自动映射等,就有点力不从心了。
解决方案:引入MyBatis,让SQL和Java解耦
第一步:搭建基础环境
MyBatis本身是个轻量级框架,集成起来并不复杂。我们在pom.xml中添加了如下依赖(以Maven为例):
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
然后配置了简单的application.yml文件,指定数据库信息和MyBatis配置路径。
第二步:定义Mapper接口和XML映射文件
这是MyBatis的核心机制之一:通过接口定义方法名,再在对应的XML文件中写SQL。MyBatis会自动完成参数绑定和结果集映射。
比如我们先定义一个OrderMapper接口:
@Mapper
public interface OrderMapper {
List<Order> selectByUserId(int userId);
}
然后写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="selectByUserId" resultType="com.example.model.Order">
SELECT * FROM orders WHERE user_id = #{userId}
</select>
</mapper>
你可能注意到两点:
#{userId}是MyBatis的占位符语法,防止SQL注入。- 不用手动创建Connection、PreparedStatement,这些都由MyBatis帮你做了。
第三步:处理复杂的对象映射
后来我们需要显示订单详情,包括用户信息、商品列表,这就涉及到了多个表联合查询。MyBatis提供了强大的resultMap功能。
<resultMap id="orderDetailResultMap" type="com.example.dto.OrderDetail">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<collection property="items" ofType="com.example.dto.OrderItem">
<result property="productName" column="product_name"/>
<result property="price" column="price"/>
</collection>
</resultMap>
<select id="getOrderDetails" resultMap="orderDetailResultMap">
SELECT
o.id, u.name AS user_name,
i.product_name, i.price
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN order_items i ON o.id = i.order_id
WHERE o.id = #{orderId}
</select>
这种方式比手动拼对象高效多了,而且清晰直观。
第四步:实现动态SQL
有个需求是根据多种条件筛选订单(如时间区间、状态、用户ID),这时候MyBatis的 <if> 标签就派上用场了:
<select id="searchOrders" parameterType="map" resultType="Order">
SELECT * FROM orders
<where>
<if test="userId != null">
AND user_id = #{userId}
</if>
<if test="startDate != null">
AND create_time >= #{startDate}
</if>
<if test="endDate != null">
AND create_time <= #{endDate}
</if>
<if test="status != null and status != ''">
AND status = #{status}
</if>
</where>
</select>
你看,只要传入Map参数,就可以根据不同的键是否为空来自动生成SQL语句,不需要我们手动拼接字符串,既安全又优雅。
第五步:事务管理
我们之前在JDBC中需要手动调用conn.setAutoCommit(false),现在MyBatis配合Spring Boot可以通过注解轻松搞定:
@Service
public class OrderService {
@Transactional
public void cancelOrder(int orderId) {
orderMapper.updateStatus(orderId, "CANCELLED");
inventoryService.releaseInventory(orderId);
}
}
这个注解会在方法调用前开启事务,在方法成功返回后提交,抛异常则回滚,极大地提升了事务控制的可维护性。
效果总结:重构后的好处显而易见
将原有的DAO模块替换为MyBatis之后,整体效果非常显著:
- 代码简洁程度提升明显:原本几十行的DAO方法现在只需要几行接口加XML即可。
- 开发效率提升:特别是动态SQL和自动映射大大减少了样板代码的编写量。
- 维护成本降低:SQL集中管理在XML里,调整起来方便,不用改动Java代码。
- 团队协作顺畅:新人上手快,SQL和Java职责分离明确,便于分工。
更重要的是,我们终于可以腾出手来专注做核心业务逻辑了!
经验分享:我在使用MyBatis过程中踩过的坑
虽然MyBatis确实解决了不少问题,但在实际应用中也不是完全没有坑,下面是我整理的一些经验教训,希望你能少走弯路。
1. XML中的SQL别太复杂,否则调试困难
有一次我们为了优化一次报表查询,把一堆子查询都放在XML里写,结果一跑就卡顿。后来排查发现是SQL性能问题,但由于嵌套太多,调试起来特别费劲。
✅ 建议:复杂的SQL尽量拆分或考虑用视图/存储过程,或者交给DBA优化后再交由MyBatis调用。
2. 多数据源情况下注意事务传播问题
我们有一个业务场景需要同时更新两个不同库的数据,一开始没注意事务传播机制,导致其中一个库更新成功另一个失败却无法回滚。
✅ 建议:使用分布式事务框架(如Seata),或者尽量避免跨库事务。MyBatis默认只支持单库事务。
3. 注意延迟加载(Lazy Load)的使用
MyBatis支持关联对象的延迟加载,但要注意不是所有场景都适合启用,特别是在REST API中返回数据时不小心触发N+1查询问题。
✅ 建议:谨慎使用lazyLoadingEnabled,必要时进行预加载或者使用JOIN一次性获取所需数据。
4. 别忽略日志输出和性能监控
最初我们没有在开发环境打开SQL日志,很多地方写了错误的SQL也没能及时发现。
✅ 建议:
- 在
application.yml中配置MyBatis的log4j日志级别,打印执行SQL; - 使用类似Druid这样的数据库连接池监控SQL执行耗时。
技术趋势与MyBatis的定位思考
如今,ORM框架百花齐放,有Hibernate、Spring Data JPA、以及新兴的Kotlin协程式数据库框架如Exposed、Exposed等。但在我所在的项目团队中,MyBatis依然是主流选择。
为什么?因为很多时候我们依然需要对SQL拥有绝对的掌控权,尤其是在面对高并发场景时。JPA那种自动产生的SQL虽然省事,但遇到性能瓶颈往往不如手写SQL那么可控。
当然,我也在尝试了解一些更现代化的框架,比如:
- MyBatis Plus:增强了CRUD能力,适合基础业务;
- MyBatis Dynamic SQL:更现代的Java DSL风格构建SQL;
- JOOQ:提供了更强的类型安全性,不过学习成本略高。
但我个人认为,掌握原生MyBatis依然是Java开发者必备的一项技能。因为它足够轻量,且灵活性极高,适配各种项目体量。
最后的小感悟:技术是用来解决问题的,不是用来炫技的
回想当初我们还在苦苦维护那些JDBC代码的时候,总想着“我能自己搞定”,后来才明白,真正的成熟是知道什么时候该借助工具的力量。
就像我们今天聊的MyBatis,它并不是万能的银弹,但它的确解决了我们在开发过程中的真实痛点:SQL与Java的耦合、事务控制、对象映射、动态SQL等问题。
如果你也在做一个Java项目,遇到了类似的困境,不妨试试看MyBatis。不要被它古老的XML吓退,它背后蕴藏的是无数企业级项目的实战经验。
这篇文章写到这里也算是我对过去两年技术成长的一个小结。如果你正准备学习MyBatis,或者已经在用了但遇到了一些困惑,欢迎留言交流,我们一起成长 🌟。
写作不易,转载请注明出处!
作者:一个热爱写代码的老码农 | 简书/B站/掘金同步更新

评论 0