MyBatis-Plus从基础CRUD到高级查询与性能优化实战指南
MyBatis-Plus 系统化实战:从基础 CRUD 到高级查询与性能优化
一、引言
在 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 已内置)。
<!-- Spring Boot 父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version>
<relativePath/>
</parent>
<dependencies>
<!-- Spring Boot Web 依赖(非必需,用于演示接口调用) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus 核心依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- lombok 简化实体类 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试依赖 -->
<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 日志打印等),配置后即可自动加载。
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.StdOutImpl2.3 启动类配置
在 Spring Boot 启动类上添加 @MapperScan 注解,指定 Mapper 接口所在的包路径,确保 MyBatis-Plus 能够扫描到 Mapper 接口并生成代理对象。
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,包含逻辑删除字段):
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 的注解指定表名、字段映射、备注等信息。
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 = …) 注解,用于实现“插入时自动填充创建时间、更新时自动填充更新时间”,无需手动设置。需创建填充处理器:
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 方法,无需手动编写任何方法。
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 接口
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 实现类
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 方法,运行测试方法即可验证效果。
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):
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 配置类,注入分页插件,指定数据库类型(避免分页语法兼容问题)。
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 方法即可实现分页查询,返回结果包含分页信息(总条数、总页数、当前页数据等)。
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<User> 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<User> 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<User> 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
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 实体类
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 实体类(添加订单列表属性)
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 文件,适合简单的关联场景。
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 接口,添加关联查询方法
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 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">
<!-- 用户自身字段映射 -->
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="age" property="age"/>
<result column="email" property="email"/>
<!-- 一对多关联:映射订单列表 -->
<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)关联查询调用示例
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)
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
cache-enabled: true # 全局开启二级缓存(默认false)(2)在 Mapper 接口上添加 @CacheNamespace 注解
二级缓存是 Mapper 级别的,需在具体的 Mapper 接口上添加注解,标识该 Mapper 开启二级缓存。
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 批量操作核心注意事项
- 批次大小控制:saveBatch、updateBatchById 方法默认批次大小为 1000(即每 1000 条数据执行一次批量 SQL)。若批量操作的数据量过大(如 10 万条),无需修改默认批次大小(过大的批次会导致 SQL 语句过长,占用数据库连接资源);若数据量较小(如几百条),可手动指定批次大小(如 batchSize = 500)。
// 手动指定批次大小为500 userService.saveBatch(userList, 500); userService.updateBatchById(userList, 500);- 数据库连接配置优化:批量操作需要占用数据库连接较长时间,需在 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 # 空闲连接超时时间(毫秒)- 避免嵌套批量操作:不要在批量操作中嵌套另一个批量操作(如批量新增用户时,嵌套批量新增订单),会导致数据库连接耗尽,建议分步骤执行,或使用事务控制。
- 事务控制:批量操作建议添加事务注解(@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 语句、参数、执行时间等信息,示例如下:
==> Preparing: SELECT id,username,age FROM user WHERE (age > ?) LIMIT ?,? ==> Parameters: 20(Integer), 0(Long), 2(Long) <== Total: 2
5.3.2 慢查询排查与优化建议
- 识别慢查询:通过 SQL 日志中的执行时间,判断是否为慢查询(一般认为执行时间超过 500ms 的 SQL 为慢查询)。例如:若某条查询 SQL 执行时间达到 2000ms,需重点优化。
- 慢查询优化方法:
- 添加索引:对查询条件中频繁使用的字段(如 where、join on 后的字段)添加索引,减少数据库扫描行数;
- 避免全表扫描:避免使用 select * 查询所有字段,只查询需要的字段;避免使用 or、not in 等关键字(可替换为 in、exists);
- 优化关联查询:减少关联表的数量,对关联字段添加索引;避免在关联查询中使用子查询,可替换为 join;
- 分页查询优化:确保分页查询的排序字段有索引,避免分页时出现全表排序;
- 批量操作优化:如前所述,使用 MyBatis-Plus 的批量方法,减少数据库连接次数。
- 日志进阶配置:对于生产环境,不建议将日志打印到控制台(影响性能),可配置将 SQL 日志输出到文件,并只打印慢查询日志(通过 Logback/Log4j2 配置),示例(Logback):
到此这篇关于MyBatis-Plus从基础CRUD到高级查询与性能优化实战指南的文章就介绍到这了,更多相关mybatisplus crud查询内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
解决java.util.HashMap$Values cannot be cast to java.ut的问题
这篇文章主要介绍了解决java.util.HashMap$Values cannot be cast to java.ut的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2023-03-03
@Valid 校验无效,BindingResult未获得错误的解决
这篇文章主要介绍了@Valid 校验无效,BindingResult未获得错误的解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2021-10-10
SpringBoot用ServiceLocatorFactoryBean优雅切换支付渠道
本文主要介绍了SpringBoot用ServiceLocatorFactoryBean优雅切换支付渠道,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧2025-10-10


最新评论