MyBatis基础教程:一次真实项目中的技术选型与落地实践

黄艳
2025-06-24 05:13
阅读 556

大家好,我是一名Java后端开发者,在过去几年里参与过多个中大型项目的开发。其中有一段时间,我们团队在重构一个老旧的业务系统时,选择了MyBatis作为持久层框架,整个过程中遇到了不少坑,也积累了一些经验。今天我就想结合实际项目经历,跟大家分享一下我对MyBatis的理解、应用和一些实打实的踩坑经历。

一、为什么选择MyBatis?

一、为什么选择MyBatis?

事情要从我们公司一个内部管理系统说起。那个系统原本使用的是JDBC + DAO手动编写SQL的方式进行数据操作。随着业务增长,系统的复杂度不断提升,手动维护SQL语句变得越来越难管理,代码可读性差,容易出错。

我们评估过几种持久层方案:

  • Hibernate:功能强大但学习成本高,对复杂SQL支持不够灵活,而且ORM映射在性能敏感场景下有时并不友好。
  • Spring Data JPA:封装程度太高,控制力下降,不适合我们这种需要大量动态SQL和性能调优的场景。
  • JPA + 自定义SQL:虽然可以部分解决灵活性问题,但混合编程风格导致维护困难,最终没有采纳。

最后我们选择了MyBatis——它兼具灵活性与易用性,允许我们完全掌控SQL的同时又能通过配置简化数据库交互过程。最重要的是它足够“轻”,适合嵌入到已有的Spring Boot项目架构中。

于是,我们决定在新版本的重构中全面引入MyBatis。


二、项目背景与挑战

二、项目背景与挑战

1. 项目背景简述

我们的目标是将原来基于Servlet的手工DAO模式迁移到Spring Boot + MyBatis架构上,目标模块包括用户管理、权限分配、日志记录等多个子系统。原有的数据表结构已经存在,并且较为复杂,涉及多表关联、历史数据迁移以及大量的动态查询条件组合。

2. 面临的具体挑战

(1)SQL复用率低,重复代码多

由于原来的逻辑都是硬编码在DAO类中,相同的查询可能在不同位置出现多次,只是参数略有差异。

(2)性能瓶颈初现

某些报表查询接口在高峰期响应时间超过3秒,严重影响用户体验。

(3)复杂的查询条件难以维护

比如筛选订单列表时,有几十个字段的过滤条件,各种组合逻辑让代码臃肿不堪。

(4)事务管理和数据库兼容性问题

我们使用MySQL,而旧系统还有部分Oracle遗留代码,如何保证平滑过渡成了难题。


三、解决方案设计与实现思路

面对上述问题,我们制定了以下改造策略:

1. 统一接口设计与SQL集中管理

我们采用MyBatis的Mapper XML文件统一管理SQL语句,把所有数据访问接口抽象为Mapper接口(Interface),并通过@Mapper注解扫描注册成Spring Bean。

@Mapper
public interface OrderMapper {
    List<Order> selectOrdersByConditions(Map<String, Object> params);
}

对应的XML文件中定义SQL:

<select id="selectOrdersByConditions" resultType="Order">
    SELECT * FROM orders
    <where>
        <if test="status != null">
            AND status = #{status}
        </if>
        <if test="userId != null">
            AND user_id = #{userId}
        </if>
        <!-- 更多条件 -->
    </where>
</select>

这样不仅提升了SQL的可读性和复用率,还方便了后续的性能优化和调试。

2. 动态SQL构建复杂查询

MyBatis的 <if><choose><set> 等标签非常实用,特别是构建条件查询、批量插入和更新的时候。

例如下面这个动态UPDATE语句:

<update id="updateOrderSelective">
    UPDATE orders
    <set>
        <if test="status != null">
            status = #{status},
        </if>
        <if test="remark != null">
            remark = #{remark},
        </if>
    </set>
    WHERE order_id = #{orderId}
</update>

这大大减少了空值覆盖的问题,也避免了手动拼接字符串带来的安全隐患。

3. 性能优化手段并行推进

为了应对慢查询问题,我们做了如下几件事:

  • 使用EXPLAIN分析执行计划,添加合适的索引。
  • 引入MyBatis的二级缓存(搭配Ehcache)提升高频查询效率。
  • 对某些复杂查询使用存储过程封装,减少网络IO。
  • 对分页查询进行了优化,避免使用 LIMIT offset, size 导致性能下滑。

此外,我们在关键接口中加入了SQL打印日志和耗时统计,便于线上监控和排查问题。

4. 抽象通用模板+分页插件

为了提高开发效率,我们抽取了一个基础的BaseMapper<T>,实现了常见的增删改查方法,配合通用工具类处理分页。

我们还集成了PageHelper插件,只需一行代码即可完成分页:

PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAll();
PageInfo<User> pageInfo = new PageInfo<>(users);

这一招在处理前端分页请求时特别好使。


四、踩过的几个坑及解决办法

坑1:模糊查询 %xxx% 的写法错误

刚开始有个同事写了这样的SQL:

WHERE name LIKE '%#{name}%'

结果发现总是不命中索引,后来才意识到应该改成:

WHERE name LIKE CONCAT('%', #{name}, '%')

或者在Java代码中拼接完整参数传入。

启示:MyBatis不是万能的,有些细节还是得自己注意。


坑2:ResultMap 映射字段不匹配

有时候数据库字段命名规范不一致(比如大写、下划线),容易导致字段映射失败。

解决方案是自定义 resultMap

<resultMap id="orderMap" type="Order">
    <id property="id" column="ORDER_ID"/>
    <result property="userName" column="USER_NAME"/>
</resultMap>

或者开启自动映射驼峰命名:

mybatis:
  configuration:
    mapUnderscoreToCamelCase: true

坑3:事务失效

我们有一个需求是要先更新订单状态,然后记录操作日志,这两个操作必须同时成功或失败。

开始的时候没加注解,事务无效,原因是Spring事务默认只拦截public方法,并且要被代理对象调用。

最终正确的做法是在Service层加上:

@Transactional
public void updateOrderAndLog(Order order) {
    orderMapper.update(order);
    logMapper.insert(new Log(...));
}

另外,还要记得事务方法不能被同一个类中的非事务方法直接调用,否则不会走代理,也就无法触发事务。


坑4:生产环境SQL泄露风险

最初没有关闭MyBatis的日志输出,导致控制台打出了完整的SQL语句,包含敏感数据参数,这在测试环境没问题,但在生产环境绝对不能暴露。

解决方式是在生产环境中设置:

logging:
  level:
    com.example.mapper: warn

或者在配置中关闭SQL打印。


五、落地效果与收益总结

整个重构上线之后,取得了不错的成效:

指标项 改造前 改造后
接口平均响应时间 1200ms 680ms
数据访问层代码行数 3000+行 缩减至1500行
SQL错误发生次数/月 10次 小于2次
开发效率 新增接口需1天 半天搞定

而且通过MyBatis良好的扩展性,我们后期接入了Druid进行SQL监控,还做了读写分离的尝试,整体架构更加清晰。


六、几点实战建议和心得体会

作为一名一线开发者,我有几点体会分享给大家:

✅ 合理使用动态SQL,不要滥用

MyBatis最强大的地方就是它的 <if><foreach> 等动态标签,但也要注意合理使用。过于复杂的条件可能会让SQL难以维护和调试。

✅ 分离业务逻辑和数据访问层

即使你再喜欢写SQL,也不要在Service层混杂太多SQL逻辑,保持职责单一,有助于后期维护。

✅ 保持SQL简洁,必要时拆分逻辑

如果某条SQL太长太复杂,考虑是否可以拆分成两个步骤来做,甚至借助中间临时表来缓解压力。

✅ 学会用工具定位性能瓶颈

推荐大家平时多使用:

  • EXPLAIN 分析执行计划
  • SHOW PROFILE 查看SQL各阶段耗时
  • 配合Druid等监控组件进行慢SQL捕捉

这些都比盲猜要靠谱得多。

✅ 多关注社区生态和扩展能力

MyBatis本身并不复杂,但生态很丰富,比如:

  • MyBatis Plus 提供了CRUD自动化
  • MyBatis Generator 自动生成Mapper和实体类
  • PageHelper 支持便捷的分页功能

可以根据项目情况选择合适的插件来提升效率。


结语

说到底,MyBatis并不是一个“银弹”,但它确实为我们提供了一种平衡自由和效率的持久化方式。特别是在面对复杂查询、性能要求高的场景下,它展现出了极强的适应能力。

希望这篇文章能帮助正在学习或使用MyBatis的你少走些弯路,也能在你的项目中带来实实在在的提升。如果你有更好的经验或遇到什么疑难问题,欢迎留言讨论,我们一起成长。

最后送大家一句话共勉:

“代码是写给人看的,偶尔给机器跑一下。”

愿我们都能写出既高效又优雅的代码 🙏


如需获取本文配套的代码示例或工程目录结构,请私信我或留言,我会持续整理相关资料开放分享。

评论 0

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