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

小王的技术栈
2025-06-15 06:19
阅读 750

从 JDBC 到 MyBatis:我在项目中踩过的坑与成长之路

从 JDBC 到 MyBatis:我在项目中踩过的坑与成长之路

我是一个在后端开发领域摸爬滚打了五年的程序员,这几年里接触过很多不同的 Java 框架。要说哪个让我印象最深、用得最多也最有感触的,那肯定是 MyBatis

记得刚工作没多久接的第一个项目是内部的一个订单管理系统,当时团队已经使用了 Spring,但数据访问层依然是纯 JDBC。写了一段时间之后我发现,数据库操作部分的代码冗余度极高,大量重复的 ConnectionPreparedStatementResultSet 处理让我焦头烂额。更别说业务逻辑一复杂,SQL 跟 Java 代码混在一起简直难以维护。当时我就意识到,必须得找个更好的方式来处理数据库交互。

后来我们引入了 MyBatis —— 这个轻量级却功能强大的持久层框架。这篇文章就是基于我的真实项目经验来写的,希望通过自己的踩坑经历,帮助正在学习或者准备上手 MyBatis 的同学少走弯路。


项目背景和遇到的问题

我们当时的系统需要对接一个 MySQL 数据库,核心模块包括用户信息管理、订单创建、库存控制等。因为是公司内部的旧系统改造,所以不能直接上 Hibernate 那种全 ORM 框架,而是选择了灵活可定制的 MyBatis。

最初的问题有几个:

  1. SQL 写在哪里?怎么组织结构?
  2. 怎么把查询结果映射到 Java 对象?
  3. 多表关联查询如何优雅处理?
  4. 事务管理怎么控制?

特别是在第三点上,我第一次做多表 join 查询的时候,返回的数据结构比较复杂,不知道该怎么正确地进行 resultMap 映射。还有就是刚开始没有合理划分 Mapper 接口和 XML 文件的路径结构,导致后期维护起来特别困难。


解决思路和技术选型

我们选择 MyBatis 主要是出于以下几个原因:

  • 灵活可控:可以完全掌控 SQL 的编写,而不是像 Hibernate 那样隐藏太多细节。
  • 性能优化空间大:适合我们这种对性能有一定要求的场景,比如批量插入、分页等。
  • 学习成本相对较低:团队成员都能快速上手,不像 Hibernate 那样概念太多。

结合 Spring Boot 的集成能力(其实项目中期升级到了 Spring Boot),MyBatis 可以非常方便地进行配置管理。我们还引入了 PageHelper 来做分页查询,Lombok 减少样板代码,让整个开发效率提升了不少。

接下来我会从实际开发角度出发,讲讲我是怎么一步步搭建起这个 MyBatis 架构的。


代码实践:基本结构与关键代码

1. 基本工程结构

我们采用的是标准的 Maven 结构:

src
├── main
│   ├── java
│   │   └── com.example.orderapp
│   │       ├── config       // 配置类
│   │       ├── controller     // 控制器
│   │       ├── service        // 服务层接口与实现
│   │       ├── mapper         // Mapper 接口
│   │       └── model          // 实体类
│   ├── resources
│       └── mapper             // XML 映射文件
└── test                       // 单元测试

2. MyBatis 核心配置(application.yml)

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/order_db
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  mapper-locations: classpath:mapper/**/*.xml
  type-aliases-package: com.example.orderapp.model

3. Mapper 接口定义

举个简单的例子:根据用户 ID 获取订单信息。

@Mapper
public interface OrderMapper {
    Order selectById(Long id);

    List<Order> selectByUserId(Long userId);

    int insert(Order order);
}

4. XML 映射文件(OrderMapper.xml)

这是最重要的部分,也是 MyBatis 的灵魂。

<mapper namespace="com.example.orderapp.mapper.OrderMapper">
    
    <resultMap id="BaseResultMap" type="com.example.orderapp.model.Order">
        <id column="order_id" property="id"/>
        <result column="user_id" property="userId"/>
        <result column="product_code" property="productCode"/>
        <result column="amount" property="amount"/>
        <result column="status" property="status"/>
    </resultMap>

    <select id="selectById" resultMap="BaseResultMap">
        SELECT * FROM orders WHERE order_id = #{id}
    </select>

    <select id="selectByUserId" resultMap="BaseResultMap">
        SELECT *
        FROM orders
        WHERE user_id = #{userId}
    </select>

    <insert id="insert">
        INSERT INTO orders (user_id, product_code, amount, status)
        VALUES (#{userId}, #{productCode}, #{amount}, #{status})
    </insert>
</mapper>

可以看到,这里定义了查询语句和字段映射关系,这就是所谓的“手动 ORM”。


踩坑经验分享

在整个开发过程中,有几个坑是我亲身体验过的,现在回想起来还挺有意思的。

1. 多表关联映射问题

一开始我要查询用户的订单列表,并显示用户姓名。于是我写了如下 SQL:

SELECT o.*, u.name as user_name
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.user_id = #{userId}

这时候我发现没法直接用原来的 BaseResultMap,因为里面缺少 userName 字段。怎么办呢?

我最初的做法是在 XML 中新增了一个 <result> 标签来处理额外字段:

<resultMap id="OrderWithUser" type="...">
    <result property="userName" column="user_name"/>
    ...
</resultMap>

但随着业务复杂度增加,这样的硬编码很不友好,也不便于复用。

解决方法:

于是我们采用了 Java 注解的方式,配合实体类属性注解来做映射。例如:

public class OrderWithUserInfo {
    private Long id;
    private String productCode;
    private Integer amount;

    @Column(name = "user_name")
    private String userName;
}

同时,在 MyBatis 中通过别名匹配字段,不再依赖 XML 的强耦合写法。

2. 分页查询卡顿问题

我们的订单数据越来越多,使用 LIMIT 分页时性能逐渐变差。尤其是在大数据量下,offset 分页会变得越来越慢。

解决办法:

我们改用了 游标分页(Cursor-based Pagination)。即每次记录最后一条数据的 ID 或者时间戳,下次请求时带上该值进行定位。例如:

SELECT * 
FROM orders 
WHERE create_time > #{lastTime} 
ORDER BY create_time ASC 
LIMIT 20

这种方式大大提高了查询效率,尤其在数据量大的情况下。

3. Mapper 扫描不到

有时候你明明写了 Mapper,但在启动时报错说找不到某个方法。这个问题其实挺常见的。

常见原因:

  • 没加 @Mapper 注解
  • 没有在启动类上加 @MapperScan
  • Mapper XML 路径配置错误
  • 包名跟类型别名设置冲突

建议做法:

统一使用 Spring Boot 提供的自动扫描方式:

@SpringBootApplication
@MapperScan("com.example.orderapp.mapper")
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

API接口文档-1

并且确保 mybatis.mapper-locations 配置指向了正确的 XML 路径。


效果总结

引入 MyBatis 后,项目的开发效率显著提升:

  • SQL 更加清晰、易维护,尤其是通过 XML 管理后,DBA 也能参与 review。
  • 使用 PageHelper 后分页变得简单,虽然后来换成了自定义的游标方案。
  • 通过 resultMap 和别名映射,Java 对象与数据库之间的映射更加直观。
  • 性能得到了保障,特别是在做数据量较大的查询时,比之前的 JDBC 实现快了不少。

另外,我们在部署到生产环境后也积累了一些运维经验。


生产环境中的小贴士

1. SQL 日志打印要谨慎

在测试环境中开启 SQL 打印很方便调试:

logging:
  level:
    com.example.orderapp.mapper: debug

但在生产环境中一定要关闭或限制日志级别,避免日志爆炸。

2. 动态 SQL 要小心拼接

MyBatis 的 <if><choose> 等动态标签很好用,但如果嵌套过多、逻辑太复杂,会让 SQL 变得难以调试。建议保持每个 <if> 的逻辑单一,并在单元测试中尽量覆盖所有分支。

3. 使用二级缓存要慎重

MyBatis 支持一级和二级缓存,特别是二级缓存可以跨 SqlSession 共享数据。不过要注意失效机制和一致性问题。对于高并发更新频繁的表,不要轻易开启。

4. 批量操作尽量自己实现

虽然 MyBatis 本身支持 <foreach> 来做循环插入,但性能并不理想。我们可以手动拼接批处理语句,例如使用 addBatch() + executeBatch(),然后配合事务控制。


给新手的一些建议

如果你刚开始学 MyBatis,下面几点可能会对你有帮助:

  • 先理解传统 JDBC 的流程,再过渡到 MyBatis,有助于理解封装背后的工作原理。
  • 不要一开始就追求“完全自动化”,MyBatis 的精髓在于“半自动化”的灵活性。
  • XML 和接口分离,命名统一规范。推荐使用类似 UserMapper.java + UserMapper.xml 这样的命名方式。
  • 多查文档,官方中文文档写得很详细,GitHub 上也有大量 demo 可参考。
  • 善用工具,比如 IDEA 插件可以帮你自动跳转到对应的 XML 方法、生成 result map 等。

尾声:MyBatis 是否还值得学?

现在很多人说 ORM 框架都应该用 JPA、Hibernate,或者干脆直接使用 Spring Data JPA。但我觉得 MyBatis 依然有它独特的魅力,尤其是在中大型项目中,当性能、灵活性和 SQL 可控性成为瓶颈时,它依旧是最优的选择之一。

而且现在很多新的中间件也在向 MyBatis 靠拢,比如 Druid、ShardingSphere,它们都很好地兼容 MyBatis。社区活跃度也很高,各种插件层出不穷。

总之,希望这篇结合我多年实战经验写出来的文章,能帮你在学习 MyBatis 的路上少踩几个坑,走得更快一些。如果有任何疑问或者想探讨的地方,欢迎留言交流!

共勉!

评论 0

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