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

半夜部署日记
2025-06-14 00:59
阅读 283

在做后端开发这些年,我常常会遇到一种情况:项目初期为了快速出成果,大家更倾向于用JDBC或者简单的封装来处理数据库交互。但随着业务逻辑变复杂、数据量增长,维护起来越来越吃力。这时候我都会想起一个老朋友 —— MyBatis

今天我想和大家分享一下我是如何从“手写SQL”过渡到使用 MyBatis 的,并结合我在实际项目中的经验,聊聊 MyBatis 到底解决了哪些痛点、它适合什么样的场景,以及我们该如何去理解和使用这个经典又实用的 Java 持久层框架。


一、从痛苦的手动SQL说起

一、从痛苦的手动SQL说起

事情得回到两年前,我当时在一个中型电商项目的初期参与开发。那个时候团队小、时间紧,技术选型也比较保守。我们选择直接使用 JDBC + Map 来进行数据库操作。

当时的想法很简单:

  • 数据结构不复杂,不需要 ORM。
  • 想减少学习成本,避免引入太多中间件。

一开始确实很轻快,写个 DAO 工具类,加个 SQL 拼接工具,配合连接池(比如 HikariCP),也能勉强应付基本 CRUD 和查询。

但没过多久问题就开始爆发了。

遇到的问题包括:

  1. 代码重复严重:增删改查都要重复写打开 connection、prepareStatement、遍历 ResultSet 的流程。
  2. SQL 分散在 Java 中:SQL 字符串散布在各个类里,难管理和调试。
  3. 类型转换繁琐:每次从结果集取值都得手动转成对应 Java 类型。
  4. 事务控制混乱:多线程操作下,很容易出现连接泄漏或事务未正确提交。
  5. 扩展困难:当需要分页查询、批量更新等高级功能时,没有成熟方案支撑。

那段时间,每次看 DB 层代码都觉得头皮发麻。特别是在上线前测试阶段,经常因为某个 SQL 写错字段导致整个接口出错,排查起来要靠日志翻半天。


二、第一次接触 MyBatis:为什么是我选择了它?

二、第一次接触 MyBatis:为什么是我选择了它?

其实一开始我并不是特别想用 MyBatis,毕竟它既不像 Hibernate 那样完全自动化,也不像 Spring Data JPA 那么简洁优雅。但在当时的场景下,我需要一个既能保留 SQL 控制权,又能提高开发效率、易于维护的工具。

我开始调研各种持久层框架:

  • Hibernate:虽然自动生成 SQL 很强大,但对于复杂的业务查询来说不够灵活,性能优化也较难掌控。
  • Spring Data JPA:封装得很好,但对一些定制化查询支持较差,而且对实体模型依赖太强,不太适合我们那种“表结构频繁调整”的项目。
  • MyBatis:看起来像是一个折中方案 —— 不自动映射,但给了你完整的 SQL 控制权;同时又帮你解决了很多底层 JDBC 繁琐的工作。

于是我们决定尝试迁移到 MyBatis。


三、MyBatis 是怎么帮我解决问题的?

迁移过程花了不到一周,但我们团队的编码效率明显提升。这里我结合实际例子来说明几个关键点:

1. SQL 易管理:XML 文件统一存放,告别字符串拼接

原来我们在 Java 代码中是这样写的:

String sql = "SELECT id, name FROM user WHERE status = " + status;
PreparedStatement ps = conn.prepareStatement(sql);

现在我们只需要在 XML 中定义:

<!-- UserMapper.xml -->
<select id="selectUsersByStatus" resultType="User">
    SELECT id, name FROM user WHERE status = #{status}
</select>

再通过 Mapper 接口调用:

public interface UserMapper {
    List<User> selectUsersByStatus(int status);
}

这种方式不仅让 SQL 更清晰,还能通过 <include> 标签复用常见字段,大大提高了可维护性。

2. 自动映射与类型安全:简化对象绑定

之前我们要手动从 ResultSet 中一个个取值:

while (rs.next()) {
    User user = new User();
    user.setId(rs.getInt("id"));
    user.setName(rs.getString("name"));
    ...
}

MyBatis 可以自动将结果映射为对象,甚至可以嵌套关联查询映射:

<resultMap id="userResultMap" type="User">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <association property="address" columnPrefix="addr_"
                 javaType="Address">
        <id property="id" column="id"/>
        <result property="detail" column="detail"/>
    </association>
</resultMap>


![数据库设计模型-1](https://code-guide.oss.shanghai.autogptai.club/common/file/download?name=date2025061400/eeb988c3-cccc-4fd1-91c1-4725bd7bf356.jpg)


<select id="findUserWithAddress" resultMap="userResultMap">
    SELECT u.id, u.name, 
           a.id AS addr_id, a.detail AS addr_detail
    FROM user u
    LEFT JOIN address a ON u.address_id = a.id
    WHERE u.id = #{userId}
</select>

这比手动映射干净太多了,而且不容易出错。

3. 事务控制与连接池集成:让开发安心

在 MyBatis 中,我们可以通过 SqlSession 或 Spring 整合后使用注解的方式处理事务:

@Transactional
public void updateUserAndOrder(User user, Order order) {
    userMapper.update(user);
    orderMapper.update(order);
}

底层由 MyBatis 帮我们自动管理连接和事务,不用再担心连接泄露、事务中断这些低级错误。


四、生产环境中的实际应用案例

在我们项目后期的订单模块改造中,遇到了一个典型的性能瓶颈:高并发下单场景下的库存锁定与扣减

当时的表结构设计是一个简单的 stock 表,记录每个商品的可用库存:

product_id stock
1001 20

原来的实现方式是在 Java 中先读取当前库存,判断是否充足,然后减一插入订单。这种方式在单机环境下没问题,但在分布式高并发下就容易出现“超卖”。

使用 MyBatis 加锁机制优化

我们将库存扣减的逻辑移到数据库层,在 MyBatis 的 SQL 中加上乐观锁:

UPDATE stock SET stock = stock - 1
WHERE product_id = #{productId} AND stock > 0

并在 DAO 中返回影响行数:

int rowsAffected = stockMapper.decreaseStock(productId);
if (rowsAffected == 0) {
    throw new RuntimeException("库存不足");
}

这种写法简单、高效,而且利用数据库的原子性保证了线程安全。再加上 MyBatis 提供的缓存机制,大大提升了整体系统稳定性。


五、关于 MyBatis 的性能与架构建议

说到架构层面的设计,我觉得 MyBatis 作为持久层框架是非常灵活的,但也需要你在以下几个方面多注意:

1. 合理使用延迟加载

有些场景下,比如用户关联地址信息、订单信息较多,建议开启延迟加载:

<association property="orders" column="id"
             fetchType="lazy"
             columnPrefix="order_"
             select="findOrdersByUserId"/>

这样能避免一次性加载大量无用的数据,提高接口响应速度。

2. 正确使用二级缓存

MyBatis 本身提供了一级缓存(默认开启)和二级缓存:

<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

对于只读或读多写少的数据(如城市列表、分类数据),开启二级缓存能显著减少数据库压力。

但需要注意几点:

  • 不能用于实时性要求高的数据;
  • 要考虑缓存穿透、击穿、雪崩等问题,可以结合 Redis 缓存一起使用;
  • 在分布式环境下,各节点之间的缓存不同步会导致脏读,建议关闭二级缓存或采用共享缓存。

3. SQL 性能优化不容忽视

虽然 MyBatis 不强制你必须写得好 SQL,但它也不会替你优化语句。很多初学者会写出类似这样的 SQL:

SELECT * FROM orders WHERE user_id IN
(SELECT user_id FROM users WHERE ...)

这种嵌套子查询在数据量大时会非常慢。正确的做法是改写为 JOIN 查询,或者合理索引。

MyBatis 提供了 <choose> <when> 等动态 SQL 元素,可以帮助我们根据条件生成不同的执行计划,而不是一股脑地把所有条件都拼上:

<select id="searchOrders" parameterType="map" resultType="Order">
    SELECT * FROM orders
    <where>
        <if test="userId != null">
            AND user_id = #{userId}
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
    </where>
</select>

这类动态 SQL 非常适合构建查询接口。


六、运维层面的经验分享

除了编码之外,上线后的运维也是我关注的重点之一。

1. SQL 日志监控

线上出了问题,很多时候都是 SQL 执行异常。我们可以开启 MyBatis 的打印功能:

# application.yml(Spring Boot 项目)
logging:
  level:
    com.example.mapper: debug

这样可以在日志中看到每条执行的 SQL,方便追踪性能瓶颈。

2. 配置文件分离

我们在不同环境中(开发/测试/生产)使用的数据库配置往往不同,因此推荐将 mybatis-config.xml 和数据库连接参数抽离出来:

# application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=123456

而将 mapper 文件集中在 resources 目录下统一管理。

3. MyBatis 插件机制善加利用

MyBatis 支持拦截器(Interceptor),我们可以借此做一些全局性的操作:

  • SQL 性能统计:记录每个查询的耗时;
  • 分库分表路由:根据主键动态选择访问哪个数据库;
  • 审计日志:记录谁在什么时间修改了哪条数据;
  • 数据脱敏:对敏感字段进行加密或替换。

比如一个简单的 SQL 日志拦截器:

@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})})
public class SqlInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return invocation.proceed();
        } finally {
            long cost = System.currentTimeMillis() - start;
            System.out.println("SQL execute time: " + cost + "ms");
        }
    }
}

这类插件一旦接入,就能帮助我们在不改动业务代码的前提下完成许多基础设施能力。


七、总结:MyBatis 是一把好刀,但也得会用

经过几年的实战沉淀,我对 MyBatis 的理解也在不断加深。它不是一个完美的 ORM 框架,但它足够灵活,足够轻量,足够贴近开发者的真实需求。

如果你正在做一个小型或中型项目,希望保持对 SQL 的控制权,同时又不想被繁琐的 JDBC 操作所束缚,那么 MyBatis 是一个非常好的选择

当然,它也有它的局限:

  • 需要自己维护 SQL;
  • 学习曲线略陡(尤其是动态 SQL 和结果集映射);
  • 对实体映射的支持不如 Hibernate/JPA。

所以我的建议是:

  • 如果你追求极致开发效率,并且数据模型稳定,可以选择 JPA;
  • 如果你需要高性能定制查询,同时又希望有良好的 SQL 管理能力,那么 MyBatis 更合适;
  • 如果你是刚入行的开发者,建议先掌握 JDBC 的基本原理,再学 MyBatis,不然你会很难理解它的设计理念。

最后说一点自己的体会吧:编程这件事,工具只是手段,核心还是理解业务和数据。

MyBatis 不会替你写 SQL,但只要你愿意花心思去打磨每一个查询语句,它就能让你的系统更加稳健、高效、易维护。


附录:个人 MyBatis 学习路线图

  1. 掌握 MyBatis 基本语法(XML 映射、接口绑定)
  2. 理解一级缓存、二级缓存原理及使用场景
  3. 学习动态 SQL 写法(if、choose、foreach)
  4. 实践结果集映射(一对一、一对多)
  5. 尝试使用插件机制(如分页、性能分析)
  6. 结合 Spring Boot 使用 MyBatis Starter
  7. 了解 MyBatis Generator 快速生成代码
  8. 实战演练:完成一个完整的小型管理系统(CRUD+权限)

希望这篇经验分享对你有所帮助。如果你也在使用 MyBatis,欢迎留言交流你的使用心得 😊

评论 0

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