MyBatis基础教程:从“写死SQL”到优雅的持久层实践

♀孙志强
2025-06-23 17:40
阅读 404

引言:为什么选择MyBatis?

引言:为什么选择MyBatis?

在我刚入行那会儿,公司的一个老项目还在用JDBC直接拼接SQL语句。你可以想象一下那个场景——一个DAO类里充斥着PreparedStatement和各种setString()setInt()的调用,维护起来就像修一条年久失修的老路,每次改动都提心吊胆。

后来我们尝试用了Hibernate,结果又陷入了一个完全不同的泥潭:生成的SQL复杂难控,关联查询多的时候性能奇差,开发调试困难重重。直到我接触到了MyBatis,才算真正理解什么叫“掌控与便捷的平衡”。

这篇文章就想从我的亲身经历出发,带大家从头了解MyBatis这个Java生态中非常实用的持久层框架,并结合实际项目中的经验,聊聊怎么用它写出既稳定又高效的代码。


项目背景与挑战:一次数据迁移任务的“噩梦”

项目背景与挑战:一次数据迁移任务的“噩梦”

去年我参与了一个老旧系统的重构项目,目标是从一套单体架构系统迁移到微服务架构下,其中数据迁移是一个关键环节。我们面对的是多个Oracle数据库表,共计上千万条历史数据需要清洗、转换并导入MySQL新库。

在初期,团队是采用传统DAO+JDBC的方式写的迁移逻辑,效果惨不忍睹:

  • SQL硬编码在Java类中难以复用
  • 多个字段映射手动处理,出错率高
  • 查询复杂后难以维护
  • 分页、动态条件查询要自己封装一堆if判断

整个迁移过程像是一次又一次“人肉ORM”,效率低不说,还总出bug。老板看不下去了,说:“要么引入点能干活的ORM工具,要么咱们改手写SQL算了。”

于是,我们开始调研轻量级ORM框架,并最终选择了MyBatis


解决方案:MyBatis如何让我们的持久层“脱胎换骨”?

解决方案:MyBatis如何让我们的持久层“脱胎换骨”?

1. 架构设计层面的选择

MyBatis不是全自动化ORM(比如Hibernate那种),而是所谓的“半自动ORM”。这意味着:

  • 你需要自己写SQL,但可以通过配置实现映射(ResultMap)
  • 可以灵活控制查询语句,避免生成“笨重”的SQL语句
  • 更容易排查和优化性能瓶颈,适合对性能有要求的场景

这正好符合我们这次项目的需求:我们不想把所有业务逻辑交给框架来处理,但也不愿意回到手动拼接SQL的时代。MyBatis就像是给了我们一双“魔法手套”,既保留了SQL书写的自由度,又能大幅提升开发效率。

2. 数据库设计的配合优化

我们为数据迁移设计了一个新的中间状态数据库,专门用于缓存过渡数据。使用MyBatis后,我们可以非常方便地通过XML文件或注解方式定义SQL语句,并通过接口契约统一访问入口。

例如,一个用户基本信息的读取操作就变成这样:

public interface UserMapper {
    @Select("SELECT id, name, email FROM user_info WHERE status = #{status}")
    List<User> selectByStatus(int status);
}

简洁明了,不需要繁琐的JDBC处理流程。


快速入门实战:搭建你的第一个MyBatis项目

1. 创建Maven项目结构

数据库设计模型-2

如果你是Spring Boot环境,添加依赖如下即可:

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

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
</dependency>

2. 配置数据源(application.yml)

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.model

3. 编写实体类 + Mapper接口

public class Product {
    private Long id;
    private String name;
    private Double price;
    // getter/setter 略
}
@Mapper
public interface ProductMapper {
    Product selectById(Long id);
}

4. 写SQL映射(XML版)

在resources/mapper目录下创建ProductMapper.xml:

<mapper namespace="com.example.mapper.ProductMapper">
    <select id="selectById" resultType="com.example.model.Product">
        SELECT * FROM product WHERE id = #{id}
    </select>
</mapper>

这就是最基础的MyBatis应用了。是不是比纯JDBC简单多了?接下来我们会深入一些更高级的用法。


踩坑实录:那些年我和MyBatis一起走过的弯路

1. XML里SQL写错了,查不出来还不报错!

有一次我在XML里写了个字段名拼错了,比如把 user_name 写成了 user_nmae,MyBatis竟然没报错,只是返回了null值。这让我找了半天才发现问题。

教训总结

  • 在开发时一定要开启日志打印SQL语句(log4j或logback)
  • 使用 resultType 的时候,字段必须严格匹配
  • 推荐初期使用 resultMap 来显式映射字段

2. 动态SQL使用不当导致查询慢

我们在做分页查询时,一开始写了类似这样的SQL:

<select id="searchProducts" resultType="Product">
    SELECT * FROM product
    <where>
        <if test="name != null and name.length() > 0">
            AND name LIKE '%${name}%'
        </if>
    </where>
</select>

虽然功能正常了,但在数据量大时发现很慢。原来 ${} 是字符串替换,无法预编译,极易引发SQL注入和性能问题。

优化建议

  • 使用 #{} 占位符
  • 模糊查询改写成:
AND name LIKE CONCAT('%', #{name}, '%')

或者使用MyBatis的 <bind> 标签预处理参数。

3. 批量插入效率太低

我们刚开始做数据导入时,是循环调用单条插入的方法,结果几十万条数据跑了两个小时还没完。

后来改成批量插入,使用MyBatis的foreach语法:

<insert id="batchInsert">
    INSERT INTO product (name, price) VALUES
    <foreach collection="products" item="product" separator=",">
        (#{product.name}, #{product.price})
    </foreach>
</insert>

配合JDBC URL增加参数:

rewriteBatchedStatements=true

一下子插入速度提升了十倍不止。


效果对比:用MyBatis前 vs 后

指标 原JDBC方式 MyBatis方式
开发效率 慢,重复劳动多 提升50%以上
维护难度 高,耦合严重 明显降低
性能表现 差异不大(合理使用) 更好控制
错误率 显著下降

最直观的变化是:原本一周的工作,现在两三天就能搞定;更重要的是,团队协作也顺畅了很多,因为每个人都知道去哪里找对应的SQL,不用再翻几十个Java类来找一句拼接的INSERT语句。


实战经验分享:给初学者的一些建议

负载均衡配置-1

✅ 小贴士一:学会“动静结合”

MyBatis的最大优势就是动态SQL。比如:

  • <if>:条件判断
  • <choose>/<when>:分支逻辑
  • <foreach>:循环处理集合
  • <set>:更新部分字段

这些标签用好了,可以让你的SQL更清晰,也可以省掉大量冗余Java代码。

✅ 小贴士二:合理使用ResultMap

如果你的字段名与列名差异较大,或者涉及联合查询映射多个对象,建议使用<resultMap>来显式定义映射规则:

<resultMap id="userWithRoleMap" type="User">
    <id property="id" column="user_id"/>
    <result property="name" column="user_name"/>
    <association property="role" javaType="Role">
        <id property="id" column="role_id"/>
        <result property="name" column="role_name"/>
    </association>
</resultMap>

✅ 小贴士三:关注日志输出与事务管理

别等到上线才去加日志,开发阶段就应该打开SQL日志输出:

在logback.xml中加入:

<logger name="com.example.mapper" level="DEBUG"/>

另外,对于需要保证一致性的操作,务必用@Transactional包裹多个Mapper调用,否则可能出现脏数据。


当前趋势下的思考:MyBatis还有未来吗?

很多人可能会问:现在很多Spring Data JPA、Querydsl甚至Kotlin的Exposed也出来了,MyBatis是不是该被淘汰了?

我的观点是:否!

  • 灵活性:相比JPA来说,MyBatis更适合需要深度定制SQL的场景
  • 性能控制:你可以精准控制每条SQL的执行路径,便于分析性能瓶颈
  • 学习成本低:相比Hibernate的庞大体系,MyBatis更容易上手,适合中小型项目快速迭代

当然,在大型项目中也可以考虑混合使用,比如:

  • 对于CRUD简单的模块使用Spring Data JPA
  • 对性能敏感或查询复杂的模块使用MyBatis

结语:技术没有银弹,只有适配场景的解

回顾这一年多的MyBatis使用历程,我最大的感悟是:工具只是手段,解决问题才是核心。你可能今天用MyBatis,明天用Hibernate,后天换成JOOQ,只要你理解背后的思想,都能写出高质量的代码。

希望这篇来自一线实战的经验文章,能帮你少走弯路,快速掌握MyBatis的基础和进阶技巧。

如果你也在用MyBatis,欢迎留言交流你踩过的坑,我们一起成长!

评论 0

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