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

云计算Code
2025-06-24 08:13
阅读 577

从JDBC到MyBatis:一次真实项目中持久层的蜕变之路

从JDBC到MyBatis:一次真实项目中持久层的蜕变之路

开篇:为什么会讲这个话题?

作为一个做了将近十年 Java 后端开发的技术负责人,我经历过最“原始”的时代:所有的数据库操作都靠手写 JDBC,SQL 和业务代码混杂在一起,维护成本高、错误频发。直到有一天,我们在一个中型电商平台重构项目中遇到了数据库访问瓶颈,才真正下定决心去优化我们的持久层设计。

也正是那个时候,我们开始系统性地引入 MyBatis。这篇文章我会以第一人称的方式,结合我们实际项目的演进过程,带你走一遍我们当时从零开始学习和应用 MyBatis 的全过程,顺便也会穿插一些踩过的坑和解决方法,希望你读完之后对 MyBatis 的理解和使用能有一个更实在的认知。


项目背景:传统JDBC带来的痛苦回忆

2021年,我们接到一个电商平台重构项目。老系统是用纯 JDBC 实现的数据访问层,随着用户量和订单量增长,性能问题逐渐暴露出来:

  • 数据库操作代码重复度极高
  • SQL 和业务逻辑耦合严重,改个字段要动多个地方
  • 每次新增表都要重新写 CRUD 模板代码
  • 多表关联查询时结果集处理复杂且容易出错

举个例子,当时一个商品详情页需要连查 product, sku, category, stock 四张表,光是结果映射那块代码就有两百多行,稍有不慎就 NullPointerException。

这种情况下,我们就得选一个新的持久层框架。那时候主流的选择有 Hibernate、MyBatis,还有部分团队在试 JPA + Spring Data。


问题描述:到底该选哪种持久层技术?

我们当时调研了几个主流选项:

技术 优点 缺点
Hibernate 全自动 ORM,几乎不用写 SQL 性能难控制,动态查询拼装困难,学习曲线陡峭
JPA(Spring Data) 易用性强,与 Spring 生态无缝整合 对复杂查询支持差,性能调优难度大
MyBatis 灵活性高,手动控制 SQL,性能可控 需要自己写 SQL,不如 Hibernate 方便

我们做了一个小型 Demo,分别用 Hibernate、JPA 和 MyBatis 实现同一个商品模块的查询逻辑,最终决定选择 MyBatis,主要是出于以下几点考虑:

  1. 我们团队已经积累了不少 SQL 经验,更愿意保留对 SQL 的掌控;
  2. 查询需求复杂,经常涉及多表聚合、分页、排序等,Hibernate 的 Criteria API 不够灵活;
  3. 对性能要求较高,特别是在首页推荐和商品搜索这类高频接口上;
  4. 已有的数据结构比较复杂,ORM 自动映射风险太高,容易产生 N+1 查询问题。

解决方案:MyBatis 是怎么被“请”进来的

我们选择了 MyBatis + MyBatis-Spring-Boot-Starter 的组合,整个接入过程其实挺顺利的,但也确实经历了一些调整期的小波折。整个过程中,有几个关键点我觉得值得分享:

✅ 第一步:基础依赖引入

我们在 Spring Boot 项目中直接添加了依赖:

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

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

✅ 第二步:配置数据源和 MyBatis

application.yml 中我们这样配置:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/ecommerce?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver


![数据库设计模型-1](https://code-guide.oss.shanghai.autogptai.club/common/file/download?name=date2025062408/d527c5b5-cc8d-462b-aa8c-6600b02d6bb1.jpg)


mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.ecommerce.model

这一步完成后,我们就可以通过注解或 XML 来编写 SQL 映射了。


代码实践:看一个真实的例子

下面是一个简单的商品信息查询示例:

📁 Model 类定义

public class Product {
    private Long id;
    private String name;
    private BigDecimal price;
    private Integer stock;
    // getter/setter...
}

📁 Mapper 接口定义

@Mapper
public interface ProductMapper {
    Product selectById(Long id);
    
    List<Product> selectByCategoryId(Long categoryId);

    int insert(Product product);
}

📁 XML 映射文件(ProductMapper.xml)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.ecommerce.mapper.ProductMapper">

    <select id="selectById" resultType="Product">
        SELECT * FROM product WHERE id = #{id}
    </select>

    <select id="selectByCategoryId" resultType="Product">
        SELECT *
        FROM product
        WHERE category_id = #{categoryId}
        ORDER BY create_time DESC
    </select>

    <insert id="insert">
        INSERT INTO product (name, price, stock)
        VALUES (#{name}, #{price}, #{stock})
    </insert>

</mapper>

📁 Service 层调用示例

@Service
public class ProductService {

    @Autowired
    private ProductMapper productMapper;

    public Product getProductById(Long id) {
        return productMapper.selectById(id);
    }

    public List<Product> getProductsByCategory(Long categoryId) {
        return productMapper.selectByCategoryId(categoryId);
    }

    public void addProduct(Product product) {
        productMapper.insert(product);
    }
}

是不是感觉特别清爽?相比之前一堆 ResultSet 处理代码,现在只需要定义接口和 SQL 就搞定了!


踩坑经验:这些雷我们替你踩过了

❌ 雷区一:XML文件路径配错了,半天没发现

刚开始的时候,总是提示 Mapped Statements collection does not contain value for xxx,后来才发现是 mapper-locations 配置不对。如果你用的是 Maven,默认资源放在 src/main/resources/mapper/ 目录下,确保打包时会被正确复制进去。

解决方案:检查路径是否匹配,必要时加上通配符,比如 classpath*:mapper/**/*.xml

❌ 雷区二:参数绑定不清晰导致空指针

比如有时候写了这样的 SQL:

SELECT * FROM product WHERE category_id = #{categoryID}

但接口里方法参数名却是 Long categoryId,注意大小写问题!

解决方案:要么统一命名风格(建议用驼峰),或者显式指定参数名:

List<Product> selectByCategoryId(@Param("categoryID") Long categoryId);

❌ 雷区三:忘记 <resultMap>,结果映射混乱

一开始我们有个字段叫 productName,但数据库是 product_name,没有做别名也没写 resultMap,结果一直为 null。

解决方案:要么加别名,要么显式写 <resultMap>

<resultMap id="productResultMap" type="Product">
    <id column="id" property="id"/>
    <result column="product_name" property="name"/>
    <result column="product_price" property="price"/>
    ...
</resultMap>

效果总结:换用 MyBatis 后的变化

自从全面采用 MyBatis 后,我们取得了几个明显的成效:

  1. 代码整洁度提升明显:SQL 和业务逻辑分离,便于维护。
  2. 性能优化空间更大:可以针对性优化慢查询,甚至结合缓存机制。
  3. 排查问题更方便:日志中可以直接看到执行的 SQL,定位问题效率高。
  4. 新人上手更快:新同事只需要了解 SQL 语句和基本映射规则就能干活。

特别是针对电商常见的 列表分页查询、商品推荐、库存更新 这些场景,用 MyBatis 写起来非常灵活,也避免了 Hibernate 那种生成一大堆无用 SQL 的尴尬局面。


经验分享:写给正在入门 MyBatis 的你

作为一名过来人,我想给你几点建议:

  1. 不要害怕写 SQL:有些人觉得 ORM 更高级,其实在企业级项目中,掌握 SQL 才是最根本的能力。
  2. 善用动态 SQL 标签:像 <if><choose><set> 这些标签在构建复杂的条件查询时简直是神器。
  3. 适当抽象通用方法:我们团队封装了一个 BaseMapper<T>,包含常用的增删改查,节省了很多样板代码。
  4. 结合 Druid 或 MyBatis-Plus 增强功能
    • 如果你的项目规模不大,可以试试 MyBatis-Plus,它提供了很多便捷的扩展方法;
    • 想监控 SQL 性能?用上 Druid,实时查看慢 SQL。
  5. 关注连接池配置:别以为引入 MyBatis 就万事大吉了,连接池设置不合理一样会拖累系统性能。我们用了 HikariCP,默认最大连接数是 10,后来改成 30,效果立竿见影。

结语:坚持才是硬道理

回想当初在 MyBatis 上摸索的过程,其实是挺艰难的 —— 要学各种标签、要注意 SQL 注入问题、还要时刻提防事务管理的坑。但正是那一段时间的坚持,让我和团队真正掌握了如何写出高性能、易维护的数据访问层代码。

如今再回头看,我很庆幸当初选择了 MyBatis,而不是盲目追求全自动的 ORM。毕竟,在真实的企业开发中,灵活比自动化更重要。希望这篇文章能帮助你少走一点弯路,早点掌握 MyBatis 的精髓。

如果你也在用 MyBatis,欢迎留言交流实战中的问题,我们一起进步!

评论 0

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