MyBatis-Plus 系统化实战:从基础 CRUD 到高级查询与性能优化

MyBatis-Plus 系统化实战:从基础 CRUD 到高级查询与性能优化


🌸你好呀!我是 lbb小魔仙
🌟 感谢陪伴~ 小白博主在线求友
🌿 跟着小白学Linux/Java/Python
📖 专栏汇总:
《Linux》专栏 | 《Java》专栏 | 《Python》专栏

  • [MyBatis-Plus 系统化实战:从基础 CRUD 到高级查询与性能优化](#MyBatis-Plus 系统化实战:从基础 CRUD 到高级查询与性能优化)
  • 一、引言
  • [二、环境搭建:Spring Boot + MyBatis-Plus 快速集成](#二、环境搭建:Spring Boot + MyBatis-Plus 快速集成)
    • [2.1 引入 Maven 依赖](#2.1 引入 Maven 依赖)
    • [2.2 核心配置(application.yml)](#2.2 核心配置(application.yml))
    • [2.3 启动类配置](#2.3 启动类配置)
  • [三、基础 CRUD 实战:基于 IService 与 BaseMapper](#三、基础 CRUD 实战:基于 IService 与 BaseMapper)
    • [3.1 准备数据库表](#3.1 准备数据库表)
    • [3.2 编写实体类 User](#3.2 编写实体类 User)
    • [3.3 自动填充配置(可选)](#3.3 自动填充配置(可选))
    • [3.4 编写 Mapper 接口](#3.4 编写 Mapper 接口)
    • [3.5 编写 Service 接口与实现类](#3.5 编写 Service 接口与实现类)
      • [3.5.1 Service 接口](#3.5.1 Service 接口)
      • [3.5.2 Service 实现类](#3.5.2 Service 实现类)
    • [3.6 基础 CRUD 调用示例(测试类)](#3.6 基础 CRUD 调用示例(测试类))
  • 四、高级查询技巧:提升查询灵活性与效率
    • [4.1 条件构造器:QueryWrapper / LambdaQueryWrapper](#4.1 条件构造器:QueryWrapper / LambdaQueryWrapper)
    • [4.2 分页插件配置与调用示例](#4.2 分页插件配置与调用示例)
      • [4.2.1 分页插件配置](#4.2.1 分页插件配置)
      • [4.2.2 分页查询调用示例](#4.2.2 分页查询调用示例)
    • [4.3 关联查询:注解与手动 SQL 最佳实践](#4.3 关联查询:注解与手动 SQL 最佳实践)
      • [4.3.1 准备关联表与实体类](#4.3.1 准备关联表与实体类)
        • [(1)订单表 SQL](#(1)订单表 SQL)
        • [(2)Order 实体类](#(2)Order 实体类)
        • [(3)修改 User 实体类(添加订单列表属性)](#(3)修改 User 实体类(添加订单列表属性))
      • [4.3.2 方式一:通过 @One / @Many 注解实现关联查询](#4.3.2 方式一:通过 @One / @Many 注解实现关联查询)
      • [4.3.3 方式二:手动编写 XML SQL 实现关联查询(推荐)](#4.3.3 方式二:手动编写 XML SQL 实现关联查询(推荐))
        • [(1)修改 UserMapper 接口,添加关联查询方法](#(1)修改 UserMapper 接口,添加关联查询方法)
        • [(2)编写 XML 映射文件](#(2)编写 XML 映射文件)
        • (3)关联查询调用示例
  • 五、性能优化策略:提升系统响应速度与稳定性
    • [5.1 二级缓存配置:减少数据库查询压力](#5.1 二级缓存配置:减少数据库查询压力)
      • [5.1.1 二级缓存开启步骤](#5.1.1 二级缓存开启步骤)
      • [5.1.2 二级缓存注意事项](#5.1.2 二级缓存注意事项)
    • [5.2 批量操作:saveBatch、updateBatchById 的注意事项](#5.2 批量操作:saveBatch、updateBatchById 的注意事项)
      • [5.2.1 批量操作核心注意事项](#5.2.1 批量操作核心注意事项)
    • [5.3 SQL 日志分析与慢查询排查建议](#5.3 SQL 日志分析与慢查询排查建议)
      • [5.3.1 SQL 日志配置(application.yml)](#5.3.1 SQL 日志配置(application.yml))
      • [5.3.2 慢查询排查与优化建议](#5.3.2 慢查询排查与优化建议)

一、引言

在 Java 持久层开发领域,MyBatis 凭借其轻量、灵活、SQL 可控性强的特点,成为企业级项目的主流选择。但原生 MyBatis 存在大量重复编码工作------例如基础 CRUD 接口的编写、复杂查询条件的拼接、分页逻辑的重复实现等,这些冗余操作不仅降低开发效率,还可能因人为疏忽引入潜在 Bug。

MyBatis-Plus(简称 MP)作为 MyBatis 的增强工具,遵循"只做增强,不做改变"的核心理念,在完全兼容 MyBatis 原有功能的基础上,内置了大量实用特性,彻底解决了原生 MyBatis 的痛点。其核心优势包括:无侵入式增强、内置基础 CRUD 接口无需手动编写 SQL、强大的条件构造器简化查询逻辑、完善的分页插件与批量操作支持、灵活的关联查询方案等。

在企业开发中,MyBatis-Plus 能够显著提升持久层开发效率(减少 60% 以上的重复编码),同时保证 SQL 的灵活性与性能可控性,广泛应用于中大型 Spring Boot 项目中,是 Java 开发者必备的实战技能之一。本文将从环境搭建到性能优化,系统化讲解 MyBatis-Plus 的实战用法,助力开发者快速上手并灵活运用。

二、环境搭建:Spring Boot + MyBatis-Plus 快速集成

MyBatis-Plus 与 Spring Boot 的集成非常简洁,只需完成 Maven 依赖引入和基础配置,即可快速启用所有核心功能。以下是完整的搭建步骤(基于 Spring Boot 2.7.x,JDK 8+)。

2.1 引入 Maven 依赖

在 pom.xml 中引入 MyBatis-Plus 核心依赖、数据库驱动(以 MySQL 8.0 为例)、 lombok(简化实体类编写),无需额外引入 MyBatis 依赖(MP 已内置)。

xml 复制代码
<!-- Spring Boot 父依赖 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.15</version>
    <relativePath/>
&lt;/parent&gt;

&lt;dependencies&gt;
    <!-- Spring Boot Web 依赖(非必需,用于演示接口调用) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency&gt;

    <!-- MyBatis-Plus 核心依赖 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version&gt;3.5.3.1&lt;/version&gt;
    &lt;/dependency&gt;

    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope&gt;runtime&lt;/scope&gt;
    &lt;/dependency&gt;

    <!-- lombok 简化实体类 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional&gt;true&lt;/optional&gt;
    &lt;/dependency&gt;

    <!-- 测试依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2.2 核心配置(application.yml)

在 resources 目录下创建 application.yml 文件,配置数据库连接信息、MyBatis-Plus 基础参数(如 mapper 映射文件路径、实体类别名包、SQL 日志打印等),配置后即可自动加载。

yaml 复制代码
spring:
  # 数据库配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mp_demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false
    username: root  # 替换为你的数据库用户名
    password: 123456  # 替换为你的数据库密码

# MyBatis-Plus 配置
mybatis-plus:
  # mapper 映射文件路径(若无需自定义 SQL,可省略)
  mapper-locations: classpath:mapper/**/*.xml
  # 实体类别名包(简化 XML 中实体类引用)
  type-aliases-package: com.example.mpdemo.entity
  # 全局配置
  global-config:
    db-config:
      # 主键生成策略(AUTO=自增,NONE=手动输入,ASSIGN_ID=雪花算法等)
      id-type: AUTO
      # 逻辑删除字段配置(可选,用于软删除)
      logic-delete-field: isDeleted
      logic-delete-value: 1  # 已删除
      logic-not-delete-value: 0  # 未删除
  # 日志配置(打印执行的 SQL,便于调试)
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

2.3 启动类配置

在 Spring Boot 启动类上添加 @MapperScan 注解,指定 Mapper 接口所在的包路径,确保 MyBatis-Plus 能够扫描到 Mapper 接口并生成代理对象。

java 复制代码
package com.example.mpdemo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 扫描 Mapper 接口所在包
@MapperScan("com.example.mpdemo.mapper")
@SpringBootApplication
public class MpDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(MpDemoApplication.class, args);
    }

}

三、基础 CRUD 实战:基于 IService 与 BaseMapper

MyBatis-Plus 提供了两个核心接口用于实现基础 CRUD 操作:BaseMapper(面向 Mapper 层)和 IService(面向 Service 层,封装了 BaseMapper 的方法,提供更便捷的调用)。推荐在开发中使用 IService + ServiceImpl 的组合,简化 Service 层代码编写。

以下将以 User 实体类为例,演示完整的基础 CRUD 实战(包含实体类、Mapper 接口、Service 接口/实现类、调用示例),所有代码可直接复制运行。

3.1 准备数据库表

创建 user 表(对应 User 实体类),SQL 语句如下(适配 MySQL 8.0,包含逻辑删除字段):

sql 复制代码
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(100) NOT NULL COMMENT '密码(实际开发中需加密)',
  `age` int(3) DEFAULT NULL COMMENT '年龄',
  `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
  `is_deleted` tinyint(1) DEFAULT 0 COMMENT '逻辑删除(0=未删除,1=已删除)',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

3.2 编写实体类 User

使用 lombok 的 @Data 注解简化 getter/setter 方法,通过 MyBatis-Plus 的注解指定表名、字段映射、备注等信息。

java 复制代码
package com.example.mpdemo.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.util.Date;

@Data
@TableName("user")  // 指定对应数据库表名(若实体类名与表名一致,可省略)
public class User {

    // 主键,自增(对应全局配置的 id-type: AUTO)
    @TableId(type = IdType.AUTO)
    private Integer id;

    // 用户名(非空字段)
    @TableField("username")  // 若实体类字段与表字段一致,可省略
    private String username;

    // 密码
    private String password;

    // 年龄
    private Integer age;

    // 邮箱
    private String email;

    // 逻辑删除字段(与全局配置一致,可省略注解)
    @TableLogic
    private Integer isDeleted;

    // 创建时间(自动填充)
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

    // 更新时间(自动填充)
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}

3.3 自动填充配置(可选)

上述实体类中,createTime 和 updateTime 字段使用了 @TableField(fill = ...) 注解,用于实现"插入时自动填充创建时间、更新时自动填充更新时间",无需手动设置。需创建填充处理器:

java 复制代码
package com.example.mpdemo.config;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    // 插入时填充
    @Override
    public void insertFill(MetaObject metaObject) {
        // 填充 createTime 和 updateTime
        strictInsertFill(metaObject, "createTime", Date.class, new Date());
        strictInsertFill(metaObject, "updateTime", Date.class, new Date());
    }

    // 更新时填充
    @Override
    public void updateFill(MetaObject metaObject) {
        // 只填充 updateTime
        strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
    }
}

3.4 编写 Mapper 接口

UserMapper 继承 BaseMapper,BaseMapper 已内置了 selectById、insert、updateById、deleteById 等基础 CRUD 方法,无需手动编写任何方法。

java 复制代码
package com.example.mpdemo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mpdemo.entity.User;
import org.springframework.stereotype.Repository;

// @Repository 用于标识 Mapper 接口,避免 IDE 报错(可选)
@Repository
public interface UserMapper extends BaseMapper<User> {
    // 无需编写任何方法,BaseMapper 已提供所有基础 CRUD 操作
}

3.5 编写 Service 接口与实现类

IService 是 MyBatis-Plus 提供的 Service 层接口,封装了 BaseMapper 的方法,还提供了批量操作、条件查询等便捷方法。UserService 继承 IService,UserServiceImpl 继承 ServiceImpl(实现 IService)并注入 UserMapper。

3.5.1 Service 接口

java 复制代码
package com.example.mpdemo.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.mpdemo.entity.User;

// 继承 IService,指定实体类类型
public interface UserService extends IService<User> {
    // 可添加自定义 Service 方法(基础 CRUD 无需添加)
}

3.5.2 Service 实现类

java 复制代码
package com.example.mpdemo.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.mpdemo.entity.User;
import com.example.mpdemo.mapper.UserMapper;
import com.example.mpdemo.service.UserService;
import org.springframework.stereotype.Service;

// 继承 ServiceImpl,注入 Mapper 接口,实现 Service 接口
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    // 基础 CRUD 方法无需实现,ServiceImpl 已全部封装
}

3.6 基础 CRUD 调用示例(测试类)

通过 Spring Boot 测试类,演示 IService 中常用的基础 CRUD 方法,运行测试方法即可验证效果。

java 复制代码
package com.example.mpdemo;

import com.example.mpdemo.entity.User;
import com.example.mpdemo.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class UserServiceTest {

    // 注入 UserService
    @Autowired
    private UserService userService;

    // 1. 新增用户(insert)
    @Test
    public void testInsert() {
        User user = new User();
        user.setUsername("zhangsan");
        user.setPassword("123456");
        user.setAge(20);
        user.setEmail("zhangsan@example.com");
        // 调用 IService 的 save 方法(底层调用 BaseMapper 的 insert)
        boolean success = userService.save(user);
        System.out.println("新增结果:" + success + ",新增用户ID:" + user.getId());
    }

    // 2. 根据ID查询用户(selectById)
    @Test
    public void testSelectById() {
        User user = userService.getById(1); // 查询ID为1的用户
        System.out.println("查询到的用户:" + user);
    }

    // 3. 查询所有用户(selectList)
    @Test
    public void testSelectAll() {
        List<User> userList = userService.list(); // 查询所有未删除的用户(自动过滤逻辑删除)
        userList.forEach(System.out::println);
    }

    // 4. 根据ID更新用户(updateById)
    @Test
    public void testUpdateById() {
        User user = new User();
        user.setId(1); // 必须设置主键ID
        user.setAge(22); // 更新年龄为22
        user.setEmail("zhangsan22@example.com"); // 更新邮箱
        boolean success = userService.updateById(user);
        System.out.println("更新结果:" + success);
    }

    // 5. 根据ID删除用户(逻辑删除,deleteById)
    @Test
    public void testDeleteById() {
        boolean success = userService.removeById(1); // 逻辑删除ID为1的用户
        System.out.println("删除结果:" + success);
    }

    // 6. 批量新增(saveBatch)
    @Test
    public void testSaveBatch() {
        List<User> userList = List.of(
                new User(null, "lisi", "123456", 25, "lisi@example.com", 0, null, null),
                new User(null, "wangwu", "123456", 28, "wangwu@example.com", 0, null, null)
        );
        // 批量新增,底层会分批次执行SQL(默认批次大小1000)
        boolean success = userService.saveBatch(userList);
        System.out.println("批量新增结果:" + success);
    }
}

四、高级查询技巧:提升查询灵活性与效率

基础 CRUD 仅能满足简单的业务需求,在实际开发中,往往需要复杂的查询条件(如多字段模糊查询、范围查询)、分页查询、关联查询等。MyBatis-Plus 提供了完善的高级查询特性,以下讲解最常用、最实用的技巧。

4.1 条件构造器:QueryWrapper / LambdaQueryWrapper

条件构造器是 MyBatis-Plus 高级查询的核心,用于动态拼接 SQL 查询条件(无需手动拼接 SQL 字符串,避免 SQL 注入风险)。常用的构造器有两个:

  • QueryWrapper:通过字符串指定字段名,灵活性高,但字段名容易写错(无编译期检查);

  • LambdaQueryWrapper:通过 Lambda 表达式获取字段名,有编译期检查,避免字段名写错,推荐使用。

以下通过示例演示两种构造器的常用用法(基于 UserService):

java 复制代码
package com.example.mpdemo;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.mpdemo.entity.User;
import com.example.mpdemo.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class QueryWrapperTest {

    @Autowired
    private UserService userService;

    // 1. QueryWrapper 示例:多条件查询(年龄大于20,邮箱包含example,用户名不为null)
    @Test
    public void testQueryWrapper() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // 年龄 > 20
        queryWrapper.gt("age", 20);
        // 邮箱 like %example%
        queryWrapper.like("email", "example");
        // 用户名 is not null
        queryWrapper.isNotNull("username");
        // 排序:按年龄降序,按ID升序
        queryWrapper.orderByDesc("age").orderByAsc("id");

        List<User> userList = userService.list(queryWrapper);
        userList.forEach(System.out::println);
    }

    // 2. LambdaQueryWrapper 示例(推荐):同样的条件,避免字段名写错
    @Test
    public void testLambdaQueryWrapper() {
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 年龄 > 20(Lambda表达式获取字段,编译期检查)
        lambdaQueryWrapper.gt(User::getAge, 20);
        // 邮箱 like %example%
        lambdaQueryWrapper.like(User::getEmail, "example");
        // 用户名 is not null
        lambdaQueryWrapper.isNotNull(User::getUsername);
        // 排序:按年龄降序,按ID升序
        lambdaQueryWrapper.orderByDesc(User::getAge).orderByAsc(User::getId);

        List<User> userList = userService.list(lambdaQueryWrapper);
        userList.forEach(System.out::println);
    }

    // 3. 条件构造器常用方法补充
    @Test
    public void testLambdaQueryWrapperAdvanced() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        // 1. 范围查询:年龄在20-30之间(包含20和30)
        wrapper.between(User::getAge, 20, 30);
        // 2. 等值查询:用户名 = zhangsan
        wrapper.eq(User::getUsername, "zhangsan");
        // 3. 非等值查询:密码 != 123456
        wrapper.ne(User::getPassword, "123456");
        // 4. 模糊查询:用户名以zhang开头(like 'zhang%')
        wrapper.likeLeft(User::getUsername, "zhang");
        // 5. 模糊查询:用户名以san结尾(like '%san')
        wrapper.likeRight(User::getUsername, "san");
        // 6. 空值查询:邮箱 is null
        wrapper.isNull(User::getEmail);
        // 7. 非空查询:邮箱 is not null
        wrapper.isNotNull(User::getEmail);
        // 8. 逻辑或:年龄 < 20 或 年龄 > 30
        wrapper.or().lt(User::getAge, 20).or().gt(User::getAge, 30);
        // 9. 逻辑与(默认,可省略):年龄 > 20 且 邮箱不为空
        wrapper.and(i -> i.gt(User::getAge, 20).isNotNull(User::getEmail));
        // 10. 只查询指定字段(避免查询无用字段,提升性能)
        wrapper.select(User::getId, User::getUsername, User::getAge);

        List<User> userList = userService.list(wrapper);
        userList.forEach(System.out::println);
    }
}

4.2 分页插件配置与调用示例

MyBatis-Plus 内置了分页插件(PaginationInnerInterceptor),支持 MySQL、Oracle、SQL Server 等多种数据库,配置简单,调用便捷,无需手动编写分页 SQL(如 limit 语句)。

4.2.1 分页插件配置

创建 MyBatis-Plus 配置类,注入分页插件,指定数据库类型(避免分页语法兼容问题)。

java 复制代码
package com.example.mpdemo.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisPlusConfig {

    // 配置分页插件
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加分页插件,指定数据库类型为 MySQL
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

4.2.2 分页查询调用示例

使用 Page 类封装分页参数(当前页码、每页条数),结合条件构造器,调用 IService 的 page 方法即可实现分页查询,返回结果包含分页信息(总条数、总页数、当前页数据等)。

java 复制代码
package com.example.mpdemo;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.mpdemo.entity.User;
import com.example.mpdemo.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class PageTest {

    @Autowired
    private UserService userService;

    // 1. 基础分页查询(无条件)
    @Test
    public void testPageBasic() {
        // 分页参数:当前页(从1开始)、每页条数
        Page&lt;User&gt; page = new Page<>(1, 2);
        // 调用 page 方法,返回分页结果
        IPage<User> userIPage = userService.page(page);

        // 分页信息解析
        System.out.println("总条数:" + userIPage.getTotal());
        System.out.println("总页数:" + userIPage.getPages());
        System.out.println("当前页码:" + userIPage.getCurrent());
        System.out.println("每页条数:" + userIPage.getSize());
        System.out.println("当前页数据:");
        userIPage.getRecords().forEach(System.out::println);
    }

    // 2. 带条件的分页查询(结合 LambdaQueryWrapper)
    @Test
    public void testPageWithCondition() {
        // 分页参数:第2页,每页3条
        Page&lt;User&gt; page = new Page<>(2, 3);
        // 条件:年龄 > 20,按年龄降序
        LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class)
                .gt(User::getAge, 20)
                .orderByDesc(User::getAge);

        // 带条件分页查询
        IPage<User> userIPage = userService.page(page, wrapper);

        // 输出分页结果
        System.out.println("总条数:" + userIPage.getTotal());
        System.out.println("总页数:" + userIPage.getPages());
        userIPage.getRecords().forEach(System.out::println);
    }

    // 3. 分页查询指定字段(避免查询无用字段)
    @Test
    public void testPageSelectField() {
        Page&lt;User&gt; page = new Page<>(1, 2);
        LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class)
                .gt(User::getAge, 20)
                .select(User::getId, User::getUsername, User::getAge); // 只查询3个字段

        IPage<User> userIPage = userService.page(page, wrapper);
        userIPage.getRecords().forEach(System.out::println);
    }
}

4.3 关联查询:注解与手动 SQL 最佳实践

MyBatis-Plus 本身不直接提供像 MyBatis-Plus Join 那样的强关联查询封装,但可以通过 MyBatis 的原生注解(@One、@Many)或手动编写 XML SQL 实现关联查询(一对一、一对多),以下是企业开发中最常用的两种方式。

示例场景:新增 Order 实体类(订单表),User 与 Order 是一对多关系(一个用户可以有多个订单),演示一对多关联查询。

4.3.1 准备关联表与实体类

(1)订单表 SQL
sql 复制代码
CREATE TABLE `order` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '订单ID',
  `order_no` varchar(50) NOT NULL COMMENT '订单编号',
  `user_id` int(11) NOT NULL COMMENT '关联用户ID(外键)',
  `total_amount` decimal(10,2) NOT NULL COMMENT '订单总金额',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`) COMMENT '用户ID索引,提升关联查询效率'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
(2)Order 实体类
java 复制代码
package com.example.mpdemo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;
import java.util.Date;

@Data
@TableName("`order`")  // 订单表名是关键字order,需用反引号包裹
public class Order {

    @TableId(type = IdType.AUTO)
    private Integer id;

    private String orderNo;

    // 关联用户ID(外键)
    private Integer userId;

    private BigDecimal totalAmount;

    private Date createTime;
}
(3)修改 User 实体类(添加订单列表属性)
java 复制代码
package com.example.mpdemo.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.util.Date;
import java.util.List;

@Data
@TableName("user")
public class User {

    // 原有字段不变,新增以下属性
    // 一对多关联:一个用户有多个订单(非数据库字段,需用@TableField(exist = false)标识)
    @TableField(exist = false)
    private List<Order> orderList;
}

4.3.2 方式一:通过 @One / @Many 注解实现关联查询

通过 MyBatis 的 @Many 注解(一对多),在 UserMapper 中编写关联查询方法,无需编写 XML 文件,适合简单的关联场景。

java 复制代码
package com.example.mpdemo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mpdemo.entity.Order;
import com.example.mpdemo.entity.User;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface UserMapper extends BaseMapper<User> {

    // 一对多关联查询:查询用户及其所有订单
    @Select("SELECT * FROM user WHERE id = #{userId}")
    @Results({
            // 映射用户自身字段(id、username等)
            @Result(column = "id", property = "id"),
            @Result(column = "username", property = "username"),
            @Result(column = "age", property = "age"),
            @Result(column = "email", property = "email"),
            // 关联查询订单:通过user的id关联order的user_id
            @Result(
                    column = "id",  // 关联字段(user的id)
                    property = "orderList",  // 映射到user的orderList属性
                    // 一对多关联,使用@Many注解
                    many = @Many(select = "com.example.mpdemo.mapper.OrderMapper.selectByUserId")
            )
    })
    User selectUserWithOrders(Integer userId);

    // 批量查询用户及其订单(可选)
    @Select("SELECT * FROM user WHERE id IN (#{ids})")
    @Results({
            @Result(column = "id", property = "id"),
            @Result(column = "username", property = "username"),
            @Result(
                    column = "id",
                    property = "orderList",
                    many = @Many(select = "com.example.mpdemo.mapper.OrderMapper.selectByUserId")
            )
    })
    List<User> selectUsersWithOrders(List<Integer> ids);
}

// 新增 OrderMapper 接口
package com.example.mpdemo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mpdemo.entity.Order;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface OrderMapper extends BaseMapper<Order> {

    // 根据用户ID查询订单(供UserMapper的@Many注解调用)
    @Select("SELECT * FROM `order` WHERE user_id = #{userId}")
    List<Order> selectByUserId(Integer userId);
}

4.3.3 方式二:手动编写 XML SQL 实现关联查询(推荐)

对于复杂的关联查询(如多表关联、多条件过滤),推荐使用 XML 编写 SQL,灵活性更高,便于维护。以下演示通过 XML 实现用户与订单的一对多关联查询。

(1)修改 UserMapper 接口,添加关联查询方法
java 复制代码
package com.example.mpdemo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mpdemo.entity.User;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface UserMapper extends BaseMapper<User> {

    // 新增:通过XML实现用户与订单的关联查询
    List<User> selectUserWithOrdersByXml(Integer userId);
}
(2)编写 XML 映射文件

在 resources/mapper 目录下创建 UserMapper.xml 文件(与 application.yml 中 mapper-locations 配置一致),编写关联查询 SQL。

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.mpdemo.mapper.UserMapper"><!-- 一对多关联查询:查询用户及其所有订单 -->
    <select id="selectUserWithOrdersByXml" resultMap="UserWithOrdersMap">
        SELECT
            u.id AS user_id,
            u.username,
            u.age,
            u.email,
            o.id AS order_id,
            o.order_no,
            o.total_amount,
            o.create_time AS order_create_time
        FROM
            user u
        LEFT JOIN
            `order` o ON u.id = o.user_id
        WHERE
            u.id = #{userId}
            AND u.is_deleted = 0
    </select><!-- 结果集映射:关联用户和订单 -->
    <resultMap id="UserWithOrdersMap" type="com.example.mpdemo.entity.User"&gt;
        <!-- 用户自身字段映射 -->
        <id column="user_id" property="id"/>
        <result column="username" property="username"/>
        <result column="age" property="age"/>
        &lt;result column="email" property="email"/&gt;

        <!-- 一对多关联:映射订单列表 -->
        <collection property="orderList" ofType="com.example.mpdemo.entity.Order">
            <id column="order_id" property="id"/>
            <result column="order_no" property="orderNo"/>
            <result column="user_id" property="userId"/>
            <result column="total_amount" property="totalAmount"/>
            <result column="order_create_time" property="createTime"/>
        </collection>
    </resultMap>
</mapper>
(3)关联查询调用示例
java 复制代码
package com.example.mpdemo;

import com.example.mpdemo.entity.User;
import com.example.mpdemo.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class RelationQueryTest {

    @Autowired
    private UserMapper userMapper;

    // 测试注解方式关联查询
    @Test
    public void testSelectUserWithOrders() {
        User user = userMapper.selectUserWithOrders(1);
        System.out.println("用户信息:" + user.getUsername() + "," + user.getEmail());
        System.out.println("用户订单:");
        user.getOrderList().forEach(order -> System.out.println(order.getOrderNo() + "," + order.getTotalAmount()));
    }

    // 测试XML方式关联查询
    @Test
    public void testSelectUserWithOrdersByXml() {
        User user = userMapper.selectUserWithOrdersByXml(1);
        System.out.println("用户信息:" + user.getUsername() + "," + user.getEmail());
        System.out.println("用户订单:");
        user.getOrderList().forEach(order -> System.out.println(order.getOrderNo() + "," + order.getTotalAmount()));
    }
}

五、性能优化策略:提升系统响应速度与稳定性

MyBatis-Plus 的高效性不仅体现在开发效率上,通过合理的配置与优化,还能显著提升系统的运行性能。以下讲解企业开发中最常用的 3 种性能优化策略,涵盖缓存、批量操作、SQL 日志分析。

5.1 二级缓存配置:减少数据库查询压力

MyBatis 提供了两级缓存:一级缓存(SqlSession 级缓存,默认开启)和二级缓存(Mapper 级缓存,默认关闭)。MyBatis-Plus 完全兼容 MyBatis 的缓存机制,开启二级缓存后,多个 SqlSession 可以共享 Mapper 层的缓存数据,减少重复查询数据库的次数,提升查询性能。

5.1.1 二级缓存开启步骤

(1)全局开启二级缓存(application.yml)
yaml 复制代码
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    cache-enabled: true  # 全局开启二级缓存(默认false)
(2)在 Mapper 接口上添加 @CacheNamespace 注解

二级缓存是 Mapper 级别的,需在具体的 Mapper 接口上添加注解,标识该 Mapper 开启二级缓存。

java 复制代码
package com.example.mpdemo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mpdemo.entity.User;
import org.apache.ibatis.annotations.CacheNamespace;
import org.springframework.stereotype.Repository;

// 开启二级缓存,eviction=缓存回收策略,flushInterval=缓存刷新间隔(毫秒)
@CacheNamespace(eviction = org.apache.ibatis.cache.decorators.LruCache.class, flushInterval = 60000)
@Repository
public interface UserMapper extends BaseMapper<User> {
    // 接口方法不变
}

5.1.2 二级缓存注意事项

  • 缓存的数据必须是可序列化的(实体类需实现 Serializable 接口),否则会报错;

  • 二级缓存适用于查询频繁、修改较少的数据(如字典表、配置表),避免缓存雪崩;

  • 当 Mapper 中的增删改方法被调用时,该 Mapper 的二级缓存会自动清空,保证数据一致性;

  • 对于关联查询的缓存,需在关联的 Mapper 接口上也开启二级缓存,避免缓存失效。

5.2 批量操作:saveBatch、updateBatchById 的注意事项

MyBatis-Plus 提供了批量新增(saveBatch)、批量更新(updateBatchById)等批量操作方法,相比循环调用单条操作,批量操作能减少数据库连接次数,提升操作效率。但在使用时需注意以下细节,避免出现性能问题或数据异常。

5.2.1 批量操作核心注意事项

  1. 批次大小控制 :saveBatch、updateBatchById 方法默认批次大小为 1000(即每 1000 条数据执行一次批量 SQL)。若批量操作的数据量过大(如 10 万条),无需修改默认批次大小(过大的批次会导致 SQL 语句过长,占用数据库连接资源);若数据量较小(如几百条),可手动指定批次大小(如 batchSize = 500)。
    // 手动指定批次大小为500 userService.saveBatch(userList, 500); userService.updateBatchById(userList, 500);

  2. 数据库连接配置优化 :批量操作需要占用数据库连接较长时间,需在 application.yml 中优化数据库连接池配置(以 HikariCP 为例,Spring Boot 默认连接池):
    spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mp_demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false username: root password: 123456 hikari: maximum-pool-size: 20 # 最大连接数,根据服务器性能调整 minimum-idle: 5 # 最小空闲连接 connection-timeout: 30000 # 连接超时时间(毫秒) idle-timeout: 600000 # 空闲连接超时时间(毫秒)

  3. 避免嵌套批量操作:不要在批量操作中嵌套另一个批量操作(如批量新增用户时,嵌套批量新增订单),会导致数据库连接耗尽,建议分步骤执行,或使用事务控制。

  4. 事务控制 :批量操作建议添加事务注解(@Transactional),确保批量操作要么全部成功,要么全部失败,避免数据不一致。

    `

    @Service

    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Override

    @Transactional(rollbackFor = Exception.class)

    public boolean batchSaveUser(List userList) {

    // 批量新增,添加事务控制

    return saveBatch(userList, 500);

    }

    }

    `

5.3 SQL 日志分析与慢查询排查建议

在实际开发中,SQL 性能是影响系统响应速度的关键因素。MyBatis-Plus 支持打印执行的 SQL 日志,通过分析日志可以排查慢查询、冗余查询等问题,优化 SQL 性能。

5.3.1 SQL 日志配置(application.yml)

已在环境搭建部分配置了日志打印(log-impl: org.apache.ibatis.logging.stdout.StdOutImpl),会在控制台打印执行的 SQL 语句、参数、执行时间等信息,示例如下:

text 复制代码
==>  Preparing: SELECT id,username,age FROM user WHERE (age > ?) LIMIT ?,?
==> Parameters: 20(Integer), 0(Long), 2(Long)
<==      Total: 2

5.3.2 慢查询排查与优化建议

  1. 识别慢查询:通过 SQL 日志中的执行时间,判断是否为慢查询(一般认为执行时间超过 500ms 的 SQL 为慢查询)。例如:若某条查询 SQL 执行时间达到 2000ms,需重点优化。

  2. 慢查询优化方法

    • 添加索引:对查询条件中频繁使用的字段(如 where、join on 后的字段)添加索引,减少数据库扫描行数;

    • 避免全表扫描:避免使用 select * 查询所有字段,只查询需要的字段;避免使用 or、not in 等关键字(可替换为 in、exists);

    • 优化关联查询:减少关联表的数量,对关联字段添加索引;避免在关联查询中使用子查询,可替换为 join;

    • 分页查询优化:确保分页查询的排序字段有索引,避免分页时出现全表排序;

    • 批量操作优化:如前所述,使用 MyBatis-Plus 的批量方法,减少数据库连接次数。

  3. 日志进阶配置 :对于生产环境,不建议将日志打印到控制台(影响性能),可配置将 SQL 日志输出到文件,并只打印慢查询日志(通过 Logback/Log4j2 配置),示例(Logback):

    `

` >📕**个人领域** :Linux/C++/java/AI >🚀 **个人主页** :[有点流鼻涕 · CSDN](https://blog.csdn.net/lbbxmx111) >💬 **座右铭** : "向光而行,沐光而生。" > ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/5271d18c85414d2bb23d348c908c47aa.gif#pic_center#pic_center#pic_center#pic_center)

相关推荐
BLUcoding2 小时前
Docker 离线安装和镜像源配置
java·docker·eureka
tsyjjOvO2 小时前
Maven从入门到精通
java·maven
JMchen1232 小时前
跨平台相机方案深度对比:CameraX vs. Flutter Camera vs. React Native
java·经验分享·数码相机·flutter·react native·kotlin·dart
day day day ...2 小时前
easyExcel和poi分别处理不同标准的excel
java·服务器·excel
hgz07102 小时前
堆内存分区
java·开发语言·jvm
索荣荣2 小时前
SpringBoot Starter终极指南:从入门到精通
java·开发语言·springboot
独断万古他化2 小时前
【Spring 事务】事务隔离级别与事务传播机制:从理论到业务落地实操
java·后端·spring·事务隔离·事务传播
苏涵.2 小时前
三种工厂设计模式
java
嗯嗯**2 小时前
Neo4j学习3:Java连接图库并执行CQL
java·学习·spring·neo4j·图数据库·驱动·cql