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

采菊东篱下
2025-06-15 21:32
阅读 545

开篇:为什么我选择分享这个话题?

开篇:为什么我选择分享这个话题?

记得刚工作那会儿,我在公司接的第一个项目是一个小型的CRM系统,主要功能是管理客户信息、订单以及售后服务记录。当时我们使用的是JDBC直接操作数据库,代码量大不说,维护起来也特别痛苦。每写一个业务逻辑就得考虑怎么拼SQL语句、处理结果集,而且一旦字段变了,一堆地方得同步修改。

直到后来我们团队决定引入MyBatis作为ORM框架,我才真正体会到什么叫“优雅地和数据库打交道”。那时候刚开始学MyBatis,踩过不少坑,比如XML配置出错、参数绑定问题、还有懒加载不生效这些常见的陷阱。但现在回想起来,正是这些问题让我对MyBatis的理解从表面深入到了底层机制。

今天我想结合自己的实战经验,带大家走进MyBatis的世界,通过一个真实的项目背景,手把手带你从零开始搭建一个基于MyBatis的基础工程,并逐步解决一些常见问题。如果你是刚接触MyBatis的小白,或者在实际开发中总感觉用得不够顺手,这篇文章应该能帮到你。


项目背景:小型CRM系统的重构

项目背景:小型CRM系统的重构

系统概览

我们要重构的是一个企业内部使用的客户管理系统,主要包括以下几个模块:

  • 客户管理(增删改查)
  • 订单管理(订单与客户的关联)
  • 售后服务(工单流转)

数据库使用MySQL,表结构大致如下:

CREATE TABLE customer (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(50),
  phone VARCHAR(20),
  create_time DATETIME
);

CREATE TABLE order_info (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  customer_id BIGINT,
  product_code VARCHAR(50),
  amount DECIMAL(10,2),
  FOREIGN KEY (customer_id) REFERENCES customer(id)
);

CREATE TABLE service_ticket (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  customer_id BIGINT,
  title VARCHAR(100),
  status ENUM('NEW', 'PROCESSING', 'CLOSED'),
  assignee VARCHAR(50),
  create_time DATETIME,
  update_time DATETIME
);

旧版系统使用JDBC直接访问数据库,接口设计耦合严重,扩展性和可维护性极差。于是我们决定重构,目标之一就是引入MyBatis来简化数据访问层。


问题描述:遇到的挑战与痛点

第一次尝试使用MyBatis的时候,我们遇到了几个典型的问题:

  1. 不知道如何组织DAO接口和Mapper文件
  2. SQL写在XML里,怎么传参、怎么映射结果?
  3. 如何实现多表关联查询的自动映射?
  4. 日志调试难,出现问题无法快速定位

尤其是第三个问题——多表联查时对象嵌套映射,困扰了我一段时间。例如,在订单详情页面我们需要显示客户信息和订单信息,这时候就需要JOIN两个表,还要把结果映射成两个对象组合的结构,这在JDBC时代需要手动拼装,但MyBatis有没有更优雅的方式呢?

另外,我们在生产环境还发现了一个性能问题:频繁的数据库连接没有复用,导致数据库压力过大。这其实是MyBatis配置不当造成的。


解决方案:一步步引入MyBatis并优化架构

第一步:引入MyBatis核心依赖

我们的项目是Spring Boot项目,所以只需要在pom.xml里添加以下依赖即可:

<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.28</version>
</dependency>

然后在application.yml中配置数据源和MyBatis相关参数:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/crm_db
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

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

第二步:创建实体类 & Mapper接口

以客户模块为例,创建对应的实体类和Mapper接口:

public class Customer {
    private Long id;
    private String name;
    private String phone;
    private LocalDateTime createTime;
    // getter/setter省略
}
@Mapper
public interface CustomerMapper {
    Customer selectById(Long id);

    List<Customer> selectAll();

    void insert(Customer customer);

    void update(Customer customer);

    void deleteById(Long id);
}

这样我们就定义好了DAO接口。接下来是关键部分:Mapper XML文件的编写

第三步:编写Mapper XML,搞定SQL和映射

这里要重点说明一下XML文件的写法:

<!-- src/main/resources/mapper/CustomerMapper.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.crm.mapper.CustomerMapper">

    <resultMap id="BaseResultMap" type="Customer">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="phone" property="phone"/>
        <result column="create_time" property="createTime"/>
    </resultMap>

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

    <select id="selectAll" resultMap="BaseResultMap">
        SELECT * FROM customer
    </select>

    <insert id="insert">
        INSERT INTO customer (name, phone, create_time)
        VALUES (#{name}, #{phone}, #{createTime})
    </insert>

    <!-- 其他update/delete方法省略 -->

</mapper>

第四步:多表关联映射技巧(真正的难点来了!)

还记得之前说的订单详情页面需要展示客户信息吗?我们希望一次SQL就能查出订单+客户对象的嵌套关系。

假设有一个DTO类:

public class OrderDetailDTO {
    private OrderInfo order;
    private Customer customer;

    // setter/getter
}

那我们可以这样写XML:

<resultMap id="OrderDetailResultMap" type="OrderDetailDTO">
    <association property="order" javaType="OrderInfo">
        <id column="order_id" property="id"/>
        <result column="product_code" property="productCode"/>
        <result column="amount" property="amount"/>
    </association>

    <association property="customer" javaType="Customer">
        <id column="customer_id" property="id"/>
        <result column="name" property="name"/>
        <result column="phone" property="phone"/>
    </association>
</resultMap>

<select id="getOrderDetailById" resultMap="OrderDetailResultMap">
    SELECT
      o.id AS order_id,
      o.product_code,
      o.amount,
      c.id AS customer_id,
      c.name,
      c.phone
    FROM order_info o
    JOIN customer c ON o.customer_id = c.id
    WHERE o.id = #{orderId}
</select>

这就是MyBatis最强大的地方之一:支持复杂对象图的自动映射。只需正确配置resultMap,就可以轻松实现嵌套对象的填充。

第五步:解决性能问题 —— 配置连接池

之前提到的生产环境数据库连接压力大的问题,其实是因为默认情况下Spring Boot使用的是SimpleDriverDataSource,它没有连接池,每次请求都新建连接!

我们后来换成了HikariCP:

spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      idle-timeout: 30000
      max-lifetime: 1800000
      auto-commit: true

效果非常显著,QPS提升了一倍,而且数据库连接资源得到了有效利用。


效果总结:重构后的收益

完成整个重构之后,我们得到了以下几个方面的提升:

  1. 代码量大幅减少:原本每个模块几十行JDBC代码现在都被浓缩成Mapper接口和XML文件。
  2. 维护成本降低:SQL集中管理,便于排查问题和优化执行效率。
  3. 系统性能提升:引入连接池后,响应速度变快,数据库负载下降。
  4. 可扩展性强:如果将来需要对接新的数据库类型,改动成本大大降低。

此外,还有一个附加的好处是——开发效率提升了。我们可以在本地开启MyBatis的日志打印,方便调试:

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

这样就会输出所有执行的SQL语句和参数值,非常实用。


经验分享:我的建议与注意事项

1. 别怕写SQL,学会控制自由度

很多人觉得MyBatis不如Hibernate自动化,但实际上,手动写SQL才是掌控性能的关键。尤其是在面对大数据量场景时,一句SQL写得好坏直接影响系统表现。

比如我曾遇到一个慢查询,用MyBatis动态生成分页SQL比直接用PageHelper更高效。

2. 规范命名,避免混淆

建议遵循如下命名习惯:

类型 规范示例
实体类 Customer, OrderInfo
Mapper接口 CustomerMapper, OrderMapper
XML文件 CustomerMapper.xml
表名 customer, order_info

3. 善用注解 vs XML?看需求定

对于简单CRUD,可以考虑用注解方式:

@Mapper
public interface CustomerMapper {
    @Select("SELECT * FROM customer WHERE id=#{id}")
    Customer selectById(Long id);

    @Insert("INSERT INTO customer(name, phone, create_time) VALUES(#{name}, #{phone}, #{createTime})")
    void insert(Customer customer);
}

但对于复杂的查询、特别是涉及多表联合、条件拼接等场景,还是建议使用XML,便于管理和阅读。

4. 动态SQL是个好东西

比如根据条件搜索客户,就可以这么写:

<select id="searchCustomers" resultType="Customer">
    SELECT *
    FROM customer
    <where>
        <if test="name != null and name.trim() != ''">
            AND name LIKE CONCAT('%', #{name}, '%')
        </if>
        <if test="phone != null and phone.trim() != ''">
            AND phone = #{phone}
        </if>
    </where>
</select>

这比在Java代码里拼接字符串清爽太多了,而且不容易出错。

5. 不要忽视缓存机制

MyBatis本身支持一级缓存(Session级别)和二级缓存(Mapper级别),适当使用可以进一步提升性能。

虽然我们这次项目没用上,但在某些读多写少的场景下(比如字典表、静态数据),开启二级缓存非常有用。

6. 日志监控不能少

生产环境中一定要配合日志系统(如ELK)记录慢查询、错误SQL等,及时预警。


写在最后:一点感悟

回想自己从JDBC一路“打怪升级”到MyBatis的过程,真的是痛并快乐着。每一次踩坑都是成长的机会,而MyBatis正是那个既灵活又强大的工具,它不会替你决定怎么做,而是给你足够的自由去按自己的想法操作数据库。

如果你正在学习MyBatis或者准备用在新项目中,希望这篇实战文章对你有所帮助。记住一句话:

框架只是工具,理解原理才能游刃有余。

祝你在MyBatis的学习路上越走越远,写出高性能、易维护的好代码!


如果你喜欢这类实战型技术分享,欢迎点赞、转发或留言交流~

评论 0

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