MyBatis基础教程:Java持久层框架入门

前端里的光
2025-06-30 03:23
阅读 643

MyBatis实战入门:从“纯JDBC”到灵活持久层的蜕变之路

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 {
            // 关闭资源...
        }
    }
}

这段代码看起来很标准,但有几个问题很快暴露出来:

  1. 代码重复严重:几乎每个方法都要处理连接、异常捕获、资源关闭,甚至SQL语句。
  2. 难以扩展:一旦涉及到多张表联查,对象映射变得复杂,处理起来麻烦。
  3. 易出错:手动设置参数容易搞错顺序或类型;手动关闭资源也很容易忘记关某一项。
  4. 耦合度高: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

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝