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

马秀兰
2025-06-28 13:42
阅读 1518

引言:为什么选择MyBatis?

引言:为什么选择MyBatis?

在我从事后端开发的这几年里,接触过不少持久层框架。从传统的JDBC到Hibernate再到如今主流的Spring Data JPA,每种技术都有其适用场景。然而在实际项目中,尤其是在需要对SQL有较强控制能力的业务系统里,MyBatis 始终占据一席之地。

记得我刚加入当前公司时,负责维护一个老项目,它用的就是 MyBatis。当时我对这个“介于ORM和原始SQL之间的桥梁”还很陌生。随着项目的深入,我发现,虽然它不像JPA那样开箱即用,但它的灵活性和性能优势在高并发、复杂查询的场景下真的特别香!

今天,我想结合自己的实际工作经验,来写一篇接地气的 MyBatis基础教程,希望能帮助刚刚入坑的朋友少走一些弯路。


项目背景:电商系统的订单模块重构

项目背景:电商系统的订单模块重构

去年年底我们团队接手了一个电商系统的重构任务,其中有一个重点模块是订单管理。原系统使用的是纯 JDBC + DAO 模式,代码冗长且难以维护,尤其是面对多表联合查询、动态条件查询时,经常需要拼接大量 SQL 字符串,非常容易出错。

举个真实案例:

// 旧版伪代码
public List<Order> findOrders(String status, String keyword) {
    StringBuilder sql = new StringBuilder("SELECT * FROM orders WHERE 1=1");
    if (status != null) {
        sql.append(" AND status='").append(status).append("'");
    }
    if (keyword != null) {
        sql.append(" AND order_no LIKE '%").append(keyword).append("%'");
    }
    // 后续执行query...
}

这段代码看着简单,但在生产环境中动不动就是几十行拼接逻辑,稍不注意就会 SQL 注入或者字段拼错了。而且随着功能越来越多,DAO 层越来越臃肿,根本不敢轻易改动。

于是我们决定引入 MyBatis 来重构整个订单模块。


遇到的问题与挑战

数据流转过程-1

挑战一:如何平滑迁移已有业务?

由于是重构项目,不能中断线上服务,必须采用渐进式替换的方式,逐步将原有 DAO 方法迁移到 MyBatis 中。这就要求新旧两套体系要能并存一段时间。

挑战二:复杂的业务查询条件

订单筛选涉及多个字段组合,比如时间范围、订单状态、用户ID、支付方式等,同时还支持分页查询,这对我们构造动态 SQL 提出了较高要求。

挑战三:数据库事务一致性

订单相关的操作往往需要更新多个表,如订单主表、订单明细表、库存表等,必须保证事务的一致性。而MyBatis本身并不处理事务管理,必须配合 Spring 才能实现。


解决思路与技术方案

针对上述问题,我们的解决思路如下:

  • 使用 MyBatis 作为持久层,配合 Spring Boot 整合事务管理;
  • 将原有的 DAO 接口封装为 Mapper,并利用 MyBatis 的注解或 XML 管理 SQL;
  • 利用 MyBatis 动态 SQL 特性,统一构建灵活的查询语句;
  • 逐步替换原有方法,确保每个接口的功能一致性后再上线。

以下是我们的核心依赖配置(pom.xml):

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.1</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

核心代码实践

示例一:Mapper 接口定义

我们以订单查询为例,定义一个 OrderMapper 接口:

@Mapper
public interface OrderMapper {

    @Select("SELECT * FROM orders WHERE id = #{id}")
    Order selectById(Long id);

    List<Order> selectByCondition(@Param("condition") OrderQuery condition);
}

示例二:XML 中的动态查询

为了应对复杂的查询条件,我们在 XML 文件中编写 SQL:

<!-- OrderMapper.xml -->
<select id="selectByCondition" resultType="Order">
    SELECT *
    FROM orders
    <where>
        <if test="condition.status != null and condition.status != ''">
            AND status = #{condition.status}
        </if>
        <if test="condition.startTime != null">
            AND create_time >= #{condition.startTime}
        </if>
        <if test="condition.endTime != null">
            AND create_time <= #{condition.endTime}
        </if>
        <if test="condition.userId != null">
            AND user_id = #{condition.userId}
        </if>
    </where>
    ORDER BY create_time DESC
</select>

这样可以非常优雅地处理各种参数组合,同时也避免了字符串拼接的风险。

示例三:事务控制

我们通过 Spring 的事务管理器来控制事务:

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Transactional
    public void updateOrderStatus(Long orderId, String newStatus) {
        Order order = orderMapper.selectById(orderId);
        order.setStatus(newStatus);
        orderMapper.updateOrder(order);

        // 更新关联的明细表或者其他表,此处略去具体逻辑
    }
}

只要加了 @Transactional 注解,Spring 就会自动开启事务并处理回滚,大大简化了事务控制的复杂度。


踩坑经验分享

坑点一:XML 中的标签嵌套错误

有时候 <if> 标签没写对,特别是没有闭合,会导致 XML 解析失败。比如:

<if test="xxx">AND a=1</if> <!-- 没写结束标签 -->

这个问题刚开始查了很久才定位到,建议大家一定要注意格式规范,最好使用 IDE 插件辅助校验。

坑点二:参数绑定异常

比如传入了一个对象,但是在 XML 中直接用了 #{userId},而不是 #{condition.userId},就会提示找不到属性。

解决方案:使用 @Param 注解显式命名参数。

坑点三:事务失效

如果事务方法被同一个类中的非事务方法调用,事务是不会生效的。原因是你调用的是 this 对象的方法,Spring AOP 无法拦截。这种情况需要用 AopContext.currentProxy() 或者重新设计结构。


实施效果与收益

完成重构后,整个订单模块的可维护性和性能都有了明显提升:

  • 代码更简洁:原来的几十行拼接 SQL 被替换成了清晰的 XML 查询;
  • 扩展性更强:新增查询字段只需在 XML 和 Query 类中添加判断即可;
  • 性能提升:MyBatis 更加贴近数据库操作,减少了不必要的 ORM 映射损耗;
  • 故障排查更快:日志可以直接打印出实际执行的 SQL,方便调试。

上线后观察了一段时间,QPS 提升了约 20%,GC 压力也有所下降。


经验总结与建议

如果你也在考虑引入 MyBatis,以下几点是我踩坑之后总结的经验:

  1. 合理使用动态 SQL
    不要贪图省事把所有逻辑塞进 <if> 里面,适当拆分 SQL 可以提高可读性和维护效率。

  2. 实体类和映射文件保持一致
    尤其是字段名和数据库列名要对应好,可以通过 mapUnderscoreToCamelCase 自动转换,也可以手动定义 resultMap

  3. 善用日志插件(如 Log4j2、MyBatis-Plus 提供的日志)
    开发阶段务必打开 SQL 日志输出,有助于快速发现执行异常或慢查询。

  4. 谨慎对待事务粒度
    太粗的事务容易导致锁竞争,太细又可能导致数据不一致,根据业务需求权衡使用。

  5. 结合 MyBatis-Plus 可以进一步提效
    如果你的项目不需要太多自定义 SQL,可以考虑用 MyBatis-Plus 替代原生 MyBatis,内置了很多常见 CRUD 方法。


写在最后

回顾这次 MyBatis 的应用实践,我觉得它是一个非常适合 Java 后端工程师掌握的基础技能。相比 Hibernate/JPA 的“全自动”风格,MyBatis 的“半自动”让我们在 SQL 上拥有更高的自由度,尤其适合中大型项目中对性能和灵活性有双重要求的场景。

作为一个过来人,我也走过不少弯路。希望这篇文章能够帮你绕过一部分坑,在学习 MyBatis 的路上走得更稳一点。

如果你正在学习或者准备尝试 MyBatis,欢迎留言交流,我们可以一起探讨更多实战技巧!

评论 0

最热最新
暂无评论
马秀兰Lv.1
0
影响力
0
文章
0
粉丝