MyBatis-Plus從基礎(chǔ)CRUD到高級(jí)查詢與性能優(yōu)化實(shí)戰(zhàn)指南
MyBatis-Plus 系統(tǒng)化實(shí)戰(zhàn):從基礎(chǔ) CRUD 到高級(jí)查詢與性能優(yōu)化
一、引言
在 Java 持久層開發(fā)領(lǐng)域,MyBatis 憑借其輕量、靈活、SQL 可控性強(qiáng)的特點(diǎn),成為企業(yè)級(jí)項(xiàng)目的主流選擇。但原生 MyBatis 存在大量重復(fù)編碼工作——例如基礎(chǔ) CRUD 接口的編寫、復(fù)雜查詢條件的拼接、分頁邏輯的重復(fù)實(shí)現(xiàn)等,這些冗余操作不僅降低開發(fā)效率,還可能因人為疏忽引入潛在 Bug。
MyBatis-Plus(簡(jiǎn)稱 MP)作為 MyBatis 的增強(qiáng)工具,遵循“只做增強(qiáng),不做改變”的核心理念,在完全兼容 MyBatis 原有功能的基礎(chǔ)上,內(nèi)置了大量實(shí)用特性,徹底解決了原生 MyBatis 的痛點(diǎn)。其核心優(yōu)勢(shì)包括:無侵入式增強(qiáng)、內(nèi)置基礎(chǔ) CRUD 接口無需手動(dòng)編寫 SQL、強(qiáng)大的條件構(gòu)造器簡(jiǎn)化查詢邏輯、完善的分頁插件與批量操作支持、靈活的關(guān)聯(lián)查詢方案等。
在企業(yè)開發(fā)中,MyBatis-Plus 能夠顯著提升持久層開發(fā)效率(減少 60% 以上的重復(fù)編碼),同時(shí)保證 SQL 的靈活性與性能可控性,廣泛應(yīng)用于中大型 Spring Boot 項(xiàng)目中,是 Java 開發(fā)者必備的實(shí)戰(zhàn)技能之一。本文將從環(huán)境搭建到性能優(yōu)化,系統(tǒng)化講解 MyBatis-Plus 的實(shí)戰(zhàn)用法,助力開發(fā)者快速上手并靈活運(yùn)用。
二、環(huán)境搭建:Spring Boot + MyBatis-Plus 快速集成
MyBatis-Plus 與 Spring Boot 的集成非常簡(jiǎn)潔,只需完成 Maven 依賴引入和基礎(chǔ)配置,即可快速啟用所有核心功能。以下是完整的搭建步驟(基于 Spring Boot 2.7.x,JDK 8+)。
2.1 引入 Maven 依賴
在 pom.xml 中引入 MyBatis-Plus 核心依賴、數(shù)據(jù)庫(kù)驅(qū)動(dòng)(以 MySQL 8.0 為例)、 lombok(簡(jiǎn)化實(shí)體類編寫),無需額外引入 MyBatis 依賴(MP 已內(nèi)置)。
<!-- 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 依賴(非必需,用于演示接口調(diào)用) -->
<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 驅(qū)動(dòng) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- lombok 簡(jiǎn)化實(shí)體類 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 測(cè)試依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>2.2 核心配置(application.yml)
在 resources 目錄下創(chuàng)建 application.yml 文件,配置數(shù)據(jù)庫(kù)連接信息、MyBatis-Plus 基礎(chǔ)參數(shù)(如 mapper 映射文件路徑、實(shí)體類別名包、SQL 日志打印等),配置后即可自動(dòng)加載。
spring:
# 數(shù)據(jù)庫(kù)配置
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 # 替換為你的數(shù)據(jù)庫(kù)用戶名
password: 123456 # 替換為你的數(shù)據(jù)庫(kù)密碼
# MyBatis-Plus 配置
mybatis-plus:
# mapper 映射文件路徑(若無需自定義 SQL,可省略)
mapper-locations: classpath:mapper/**/*.xml
# 實(shí)體類別名包(簡(jiǎn)化 XML 中實(shí)體類引用)
type-aliases-package: com.example.mpdemo.entity
# 全局配置
global-config:
db-config:
# 主鍵生成策略(AUTO=自增,NONE=手動(dòng)輸入,ASSIGN_ID=雪花算法等)
id-type: AUTO
# 邏輯刪除字段配置(可選,用于軟刪除)
logic-delete-field: isDeleted
logic-delete-value: 1 # 已刪除
logic-not-delete-value: 0 # 未刪除
# 日志配置(打印執(zhí)行的 SQL,便于調(diào)試)
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl2.3 啟動(dòng)類配置
在 Spring Boot 啟動(dòng)類上添加 @MapperScan 注解,指定 Mapper 接口所在的包路徑,確保 MyBatis-Plus 能夠掃描到 Mapper 接口并生成代理對(duì)象。
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);
}
}三、基礎(chǔ) CRUD 實(shí)戰(zhàn):基于 IService 與 BaseMapper
MyBatis-Plus 提供了兩個(gè)核心接口用于實(shí)現(xiàn)基礎(chǔ) CRUD 操作:BaseMapper(面向 Mapper 層)和 IService(面向 Service 層,封裝了 BaseMapper 的方法,提供更便捷的調(diào)用)。推薦在開發(fā)中使用 IService + ServiceImpl 的組合,簡(jiǎn)化 Service 層代碼編寫。
以下將以 User 實(shí)體類為例,演示完整的基礎(chǔ) CRUD 實(shí)戰(zhàn)(包含實(shí)體類、Mapper 接口、Service 接口/實(shí)現(xiàn)類、調(diào)用示例),所有代碼可直接復(fù)制運(yùn)行。
3.1 準(zhǔn)備數(shù)據(jù)庫(kù)表
創(chuàng)建 user 表(對(duì)應(yīng) User 實(shí)體類),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 '密碼(實(shí)際開發(fā)中需加密)', `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 '創(chuàng)建時(shí)間', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時(shí)間', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用戶表';
3.2 編寫實(shí)體類 User
使用 lombok 的 @Data 注解簡(jiǎn)化 getter/setter 方法,通過 MyBatis-Plus 的注解指定表名、字段映射、備注等信息。
package com.example.mpdemo.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.util.Date;
@Data
@TableName("user") // 指定對(duì)應(yīng)數(shù)據(jù)庫(kù)表名(若實(shí)體類名與表名一致,可省略)
public class User {
// 主鍵,自增(對(duì)應(yīng)全局配置的 id-type: AUTO)
@TableId(type = IdType.AUTO)
private Integer id;
// 用戶名(非空字段)
@TableField("username") // 若實(shí)體類字段與表字段一致,可省略
private String username;
// 密碼
private String password;
// 年齡
private Integer age;
// 郵箱
private String email;
// 邏輯刪除字段(與全局配置一致,可省略注解)
@TableLogic
private Integer isDeleted;
// 創(chuàng)建時(shí)間(自動(dòng)填充)
@TableField(fill = FieldFill.INSERT)
private Date createTime;
// 更新時(shí)間(自動(dòng)填充)
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}3.3 自動(dòng)填充配置(可選)
上述實(shí)體類中,createTime 和 updateTime 字段使用了 @TableField(fill = …) 注解,用于實(shí)現(xiàn)“插入時(shí)自動(dòng)填充創(chuàng)建時(shí)間、更新時(shí)自動(dòng)填充更新時(shí)間”,無需手動(dòng)設(shè)置。需創(chuàng)建填充處理器:
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 {
// 插入時(shí)填充
@Override
public void insertFill(MetaObject metaObject) {
// 填充 createTime 和 updateTime
strictInsertFill(metaObject, "createTime", Date.class, new Date());
strictInsertFill(metaObject, "updateTime", Date.class, new Date());
}
// 更新時(shí)填充
@Override
public void updateFill(MetaObject metaObject) {
// 只填充 updateTime
strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
}
}3.4 編寫 Mapper 接口
UserMapper 繼承 BaseMapper,BaseMapper 已內(nèi)置了 selectById、insert、updateById、deleteById 等基礎(chǔ) CRUD 方法,無需手動(dòng)編寫任何方法。
package com.example.mpdemo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mpdemo.entity.User;
import org.springframework.stereotype.Repository;
// @Repository 用于標(biāo)識(shí) Mapper 接口,避免 IDE 報(bào)錯(cuò)(可選)
@Repository
public interface UserMapper extends BaseMapper<User> {
// 無需編寫任何方法,BaseMapper 已提供所有基礎(chǔ) CRUD 操作
}3.5 編寫 Service 接口與實(shí)現(xiàn)類
IService 是 MyBatis-Plus 提供的 Service 層接口,封裝了 BaseMapper 的方法,還提供了批量操作、條件查詢等便捷方法。UserService 繼承 IService,UserServiceImpl 繼承 ServiceImpl(實(shí)現(xiàn) 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,指定實(shí)體類類型
public interface UserService extends IService<User> {
// 可添加自定義 Service 方法(基礎(chǔ) CRUD 無需添加)
}3.5.2 Service 實(shí)現(xiàn)類
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 接口,實(shí)現(xiàn) Service 接口
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
// 基礎(chǔ) CRUD 方法無需實(shí)現(xiàn),ServiceImpl 已全部封裝
}3.6 基礎(chǔ) CRUD 調(diào)用示例(測(cè)試類)
通過 Spring Boot 測(cè)試類,演示 IService 中常用的基礎(chǔ) CRUD 方法,運(yùn)行測(cè)試方法即可驗(yàn)證效果。
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");
// 調(diào)用 IService 的 save 方法(底層調(diào)用 BaseMapper 的 insert)
boolean success = userService.save(user);
System.out.println("新增結(jié)果:" + success + ",新增用戶ID:" + user.getId());
}
// 2. 根據(jù)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(); // 查詢所有未刪除的用戶(自動(dòng)過濾邏輯刪除)
userList.forEach(System.out::println);
}
// 4. 根據(jù)ID更新用戶(updateById)
@Test
public void testUpdateById() {
User user = new User();
user.setId(1); // 必須設(shè)置主鍵ID
user.setAge(22); // 更新年齡為22
user.setEmail("zhangsan22@example.com"); // 更新郵箱
boolean success = userService.updateById(user);
System.out.println("更新結(jié)果:" + success);
}
// 5. 根據(jù)ID刪除用戶(邏輯刪除,deleteById)
@Test
public void testDeleteById() {
boolean success = userService.removeById(1); // 邏輯刪除ID為1的用戶
System.out.println("刪除結(jié)果:" + 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)
);
// 批量新增,底層會(huì)分批次執(zhí)行SQL(默認(rèn)批次大小1000)
boolean success = userService.saveBatch(userList);
System.out.println("批量新增結(jié)果:" + success);
}
}四、高級(jí)查詢技巧:提升查詢靈活性與效率
基礎(chǔ) CRUD 僅能滿足簡(jiǎn)單的業(yè)務(wù)需求,在實(shí)際開發(fā)中,往往需要復(fù)雜的查詢條件(如多字段模糊查詢、范圍查詢)、分頁查詢、關(guān)聯(lián)查詢等。MyBatis-Plus 提供了完善的高級(jí)查詢特性,以下講解最常用、最實(shí)用的技巧。
4.1 條件構(gòu)造器:QueryWrapper / LambdaQueryWrapper
條件構(gòu)造器是 MyBatis-Plus 高級(jí)查詢的核心,用于動(dòng)態(tài)拼接 SQL 查詢條件(無需手動(dòng)拼接 SQL 字符串,避免 SQL 注入風(fēng)險(xiǎn))。常用的構(gòu)造器有兩個(gè):
- QueryWrapper:通過字符串指定字段名,靈活性高,但字段名容易寫錯(cuò)(無編譯期檢查);
- LambdaQueryWrapper:通過 Lambda 表達(dá)式獲取字段名,有編譯期檢查,避免字段名寫錯(cuò),推薦使用。
以下通過示例演示兩種構(gòu)造器的常用用法(基于 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 示例(推薦):同樣的條件,避免字段名寫錯(cuò)
@Test
public void testLambdaQueryWrapper() {
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
// 年齡 > 20(Lambda表達(dá)式獲取字段,編譯期檢查)
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. 條件構(gòu)造器常用方法補(bǔ)充
@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結(jié)尾(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. 邏輯與(默認(rèn),可省略):年齡 > 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 分頁插件配置與調(diào)用示例
MyBatis-Plus 內(nèi)置了分頁插件(PaginationInnerInterceptor),支持 MySQL、Oracle、SQL Server 等多種數(shù)據(jù)庫(kù),配置簡(jiǎn)單,調(diào)用便捷,無需手動(dòng)編寫分頁 SQL(如 limit 語句)。
4.2.1 分頁插件配置
創(chuàng)建 MyBatis-Plus 配置類,注入分頁插件,指定數(shù)據(jù)庫(kù)類型(避免分頁語法兼容問題)。
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();
// 添加分頁插件,指定數(shù)據(jù)庫(kù)類型為 MySQL
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}4.2.2 分頁查詢調(diào)用示例
使用 Page 類封裝分頁參數(shù)(當(dāng)前頁碼、每頁條數(shù)),結(jié)合條件構(gòu)造器,調(diào)用 IService 的 page 方法即可實(shí)現(xiàn)分頁查詢,返回結(jié)果包含分頁信息(總條數(shù)、總頁數(shù)、當(dāng)前頁數(shù)據(jù)等)。
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. 基礎(chǔ)分頁查詢(無條件)
@Test
public void testPageBasic() {
// 分頁參數(shù):當(dāng)前頁(從1開始)、每頁條數(shù)
Page<User> page = new Page<>(1, 2);
// 調(diào)用 page 方法,返回分頁結(jié)果
IPage<User> userIPage = userService.page(page);
// 分頁信息解析
System.out.println("總條數(shù):" + userIPage.getTotal());
System.out.println("總頁數(shù):" + userIPage.getPages());
System.out.println("當(dāng)前頁碼:" + userIPage.getCurrent());
System.out.println("每頁條數(shù):" + userIPage.getSize());
System.out.println("當(dāng)前頁數(shù)據(jù):");
userIPage.getRecords().forEach(System.out::println);
}
// 2. 帶條件的分頁查詢(結(jié)合 LambdaQueryWrapper)
@Test
public void testPageWithCondition() {
// 分頁參數(shù):第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);
// 輸出分頁結(jié)果
System.out.println("總條數(shù):" + userIPage.getTotal());
System.out.println("總頁數(shù):" + 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個(gè)字段
IPage<User> userIPage = userService.page(page, wrapper);
userIPage.getRecords().forEach(System.out::println);
}
}4.3 關(guān)聯(lián)查詢:注解與手動(dòng) SQL 最佳實(shí)踐
MyBatis-Plus 本身不直接提供像 MyBatis-Plus Join 那樣的強(qiáng)關(guān)聯(lián)查詢封裝,但可以通過 MyBatis 的原生注解(@One、@Many)或手動(dòng)編寫 XML SQL 實(shí)現(xiàn)關(guān)聯(lián)查詢(一對(duì)一、一對(duì)多),以下是企業(yè)開發(fā)中最常用的兩種方式。
示例場(chǎng)景:新增 Order 實(shí)體類(訂單表),User 與 Order 是一對(duì)多關(guān)系(一個(gè)用戶可以有多個(gè)訂單),演示一對(duì)多關(guān)聯(lián)查詢。
4.3.1 準(zhǔn)備關(guān)聯(lián)表與實(shí)體類
(1)訂單表 SQL
CREATE TABLE `order` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '訂單ID', `order_no` varchar(50) NOT NULL COMMENT '訂單編號(hào)', `user_id` int(11) NOT NULL COMMENT '關(guān)聯(lián)用戶ID(外鍵)', `total_amount` decimal(10,2) NOT NULL COMMENT '訂單總金額', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間', PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`) COMMENT '用戶ID索引,提升關(guān)聯(lián)查詢效率' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='訂單表';
(2)Order 實(shí)體類
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`") // 訂單表名是關(guān)鍵字order,需用反引號(hào)包裹
public class Order {
@TableId(type = IdType.AUTO)
private Integer id;
private String orderNo;
// 關(guān)聯(lián)用戶ID(外鍵)
private Integer userId;
private BigDecimal totalAmount;
private Date createTime;
}(3)修改 User 實(shí)體類(添加訂單列表屬性)
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 {
// 原有字段不變,新增以下屬性
// 一對(duì)多關(guān)聯(lián):一個(gè)用戶有多個(gè)訂單(非數(shù)據(jù)庫(kù)字段,需用@TableField(exist = false)標(biāo)識(shí))
@TableField(exist = false)
private List<Order> orderList;
}4.3.2 方式一:通過 @One / @Many 注解實(shí)現(xiàn)關(guān)聯(lián)查詢
通過 MyBatis 的 @Many 注解(一對(duì)多),在 UserMapper 中編寫關(guān)聯(lián)查詢方法,無需編寫 XML 文件,適合簡(jiǎn)單的關(guān)聯(lián)場(chǎng)景。
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> {
// 一對(duì)多關(guān)聯(lián)查詢:查詢用戶及其所有訂單
@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"),
// 關(guān)聯(lián)查詢訂單:通過user的id關(guān)聯(lián)order的user_id
@Result(
column = "id", // 關(guān)聯(lián)字段(user的id)
property = "orderList", // 映射到user的orderList屬性
// 一對(duì)多關(guān)聯(lián),使用@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> {
// 根據(jù)用戶ID查詢訂單(供UserMapper的@Many注解調(diào)用)
@Select("SELECT * FROM `order` WHERE user_id = #{userId}")
List<Order> selectByUserId(Integer userId);
}4.3.3 方式二:手動(dòng)編寫 XML SQL 實(shí)現(xiàn)關(guān)聯(lián)查詢(推薦)
對(duì)于復(fù)雜的關(guān)聯(lián)查詢(如多表關(guān)聯(lián)、多條件過濾),推薦使用 XML 編寫 SQL,靈活性更高,便于維護(hù)。以下演示通過 XML 實(shí)現(xiàn)用戶與訂單的一對(duì)多關(guān)聯(lián)查詢。
(1)修改 UserMapper 接口,添加關(guān)聯(lián)查詢方法
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實(shí)現(xiàn)用戶與訂單的關(guān)聯(lián)查詢
List<User> selectUserWithOrdersByXml(Integer userId);
}(2)編寫 XML 映射文件
在 resources/mapper 目錄下創(chuàng)建 UserMapper.xml 文件(與 application.yml 中 mapper-locations 配置一致),編寫關(guān)聯(lián)查詢 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"><!-- 一對(duì)多關(guān)聯(lián)查詢:查詢用戶及其所有訂單 -->
<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><!-- 結(jié)果集映射:關(guān)聯(lián)用戶和訂單 -->
<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"/>
<!-- 一對(duì)多關(guān)聯(lián):映射訂單列表 -->
<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)關(guān)聯(lián)查詢調(diào)用示例
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;
// 測(cè)試注解方式關(guān)聯(lián)查詢
@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()));
}
// 測(cè)試XML方式關(guān)聯(lián)查詢
@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()));
}
}五、性能優(yōu)化策略:提升系統(tǒng)響應(yīng)速度與穩(wěn)定性
MyBatis-Plus 的高效性不僅體現(xiàn)在開發(fā)效率上,通過合理的配置與優(yōu)化,還能顯著提升系統(tǒng)的運(yùn)行性能。以下講解企業(yè)開發(fā)中最常用的 3 種性能優(yōu)化策略,涵蓋緩存、批量操作、SQL 日志分析。
5.1 二級(jí)緩存配置:減少數(shù)據(jù)庫(kù)查詢壓力
MyBatis 提供了兩級(jí)緩存:一級(jí)緩存(SqlSession 級(jí)緩存,默認(rèn)開啟)和二級(jí)緩存(Mapper 級(jí)緩存,默認(rèn)關(guān)閉)。MyBatis-Plus 完全兼容 MyBatis 的緩存機(jī)制,開啟二級(jí)緩存后,多個(gè) SqlSession 可以共享 Mapper 層的緩存數(shù)據(jù),減少重復(fù)查詢數(shù)據(jù)庫(kù)的次數(shù),提升查詢性能。
5.1.1 二級(jí)緩存開啟步驟
(1)全局開啟二級(jí)緩存(application.yml)
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
cache-enabled: true # 全局開啟二級(jí)緩存(默認(rèn)false)(2)在 Mapper 接口上添加 @CacheNamespace 注解
二級(jí)緩存是 Mapper 級(jí)別的,需在具體的 Mapper 接口上添加注解,標(biāo)識(shí)該 Mapper 開啟二級(jí)緩存。
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;
// 開啟二級(jí)緩存,eviction=緩存回收策略,flushInterval=緩存刷新間隔(毫秒)
@CacheNamespace(eviction = org.apache.ibatis.cache.decorators.LruCache.class, flushInterval = 60000)
@Repository
public interface UserMapper extends BaseMapper<User> {
// 接口方法不變
}5.1.2 二級(jí)緩存注意事項(xiàng)
- 緩存的數(shù)據(jù)必須是可序列化的(實(shí)體類需實(shí)現(xiàn) Serializable 接口),否則會(huì)報(bào)錯(cuò);
- 二級(jí)緩存適用于查詢頻繁、修改較少的數(shù)據(jù)(如字典表、配置表),避免緩存雪崩;
- 當(dāng) Mapper 中的增刪改方法被調(diào)用時(shí),該 Mapper 的二級(jí)緩存會(huì)自動(dòng)清空,保證數(shù)據(jù)一致性;
- 對(duì)于關(guān)聯(lián)查詢的緩存,需在關(guān)聯(lián)的 Mapper 接口上也開啟二級(jí)緩存,避免緩存失效。
5.2 批量操作:saveBatch、updateBatchById 的注意事項(xiàng)
MyBatis-Plus 提供了批量新增(saveBatch)、批量更新(updateBatchById)等批量操作方法,相比循環(huán)調(diào)用單條操作,批量操作能減少數(shù)據(jù)庫(kù)連接次數(shù),提升操作效率。但在使用時(shí)需注意以下細(xì)節(jié),避免出現(xiàn)性能問題或數(shù)據(jù)異常。
5.2.1 批量操作核心注意事項(xiàng)
- 批次大小控制:saveBatch、updateBatchById 方法默認(rèn)批次大小為 1000(即每 1000 條數(shù)據(jù)執(zhí)行一次批量 SQL)。若批量操作的數(shù)據(jù)量過大(如 10 萬條),無需修改默認(rèn)批次大?。ㄟ^大的批次會(huì)導(dǎo)致 SQL 語句過長(zhǎng),占用數(shù)據(jù)庫(kù)連接資源);若數(shù)據(jù)量較小(如幾百條),可手動(dòng)指定批次大?。ㄈ?batchSize = 500)。
// 手動(dòng)指定批次大小為500 userService.saveBatch(userList, 500); userService.updateBatchById(userList, 500);- 數(shù)據(jù)庫(kù)連接配置優(yōu)化:批量操作需要占用數(shù)據(jù)庫(kù)連接較長(zhǎng)時(shí)間,需在 application.yml 中優(yōu)化數(shù)據(jù)庫(kù)連接池配置(以 HikariCP 為例,Spring Boot 默認(rèn)連接池):
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 # 最大連接數(shù),根據(jù)服務(wù)器性能調(diào)整 minimum-idle: 5 # 最小空閑連接 connection-timeout: 30000 # 連接超時(shí)時(shí)間(毫秒) idle-timeout: 600000 # 空閑連接超時(shí)時(shí)間(毫秒)- 避免嵌套批量操作:不要在批量操作中嵌套另一個(gè)批量操作(如批量新增用戶時(shí),嵌套批量新增訂單),會(huì)導(dǎo)致數(shù)據(jù)庫(kù)連接耗盡,建議分步驟執(zhí)行,或使用事務(wù)控制。
- 事務(wù)控制:批量操作建議添加事務(wù)注解(@Transactional),確保批量操作要么全部成功,要么全部失敗,避免數(shù)據(jù)不一致。
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
@Transactional(rollbackFor = Exception.class)
public boolean batchSaveUser(List userList) {
// 批量新增,添加事務(wù)控制
return saveBatch(userList, 500);
}
}5.3 SQL 日志分析與慢查詢排查建議
在實(shí)際開發(fā)中,SQL 性能是影響系統(tǒng)響應(yīng)速度的關(guān)鍵因素。MyBatis-Plus 支持打印執(zhí)行的 SQL 日志,通過分析日志可以排查慢查詢、冗余查詢等問題,優(yōu)化 SQL 性能。
5.3.1 SQL 日志配置(application.yml)
已在環(huán)境搭建部分配置了日志打?。╨og-impl: org.apache.ibatis.logging.stdout.StdOutImpl),會(huì)在控制臺(tái)打印執(zhí)行的 SQL 語句、參數(shù)、執(zhí)行時(shí)間等信息,示例如下:
==> Preparing: SELECT id,username,age FROM user WHERE (age > ?) LIMIT ?,? ==> Parameters: 20(Integer), 0(Long), 2(Long) <== Total: 2
5.3.2 慢查詢排查與優(yōu)化建議
- 識(shí)別慢查詢:通過 SQL 日志中的執(zhí)行時(shí)間,判斷是否為慢查詢(一般認(rèn)為執(zhí)行時(shí)間超過 500ms 的 SQL 為慢查詢)。例如:若某條查詢 SQL 執(zhí)行時(shí)間達(dá)到 2000ms,需重點(diǎn)優(yōu)化。
- 慢查詢優(yōu)化方法:
- 添加索引:對(duì)查詢條件中頻繁使用的字段(如 where、join on 后的字段)添加索引,減少數(shù)據(jù)庫(kù)掃描行數(shù);
- 避免全表掃描:避免使用 select * 查詢所有字段,只查詢需要的字段;避免使用 or、not in 等關(guān)鍵字(可替換為 in、exists);
- 優(yōu)化關(guān)聯(lián)查詢:減少關(guān)聯(lián)表的數(shù)量,對(duì)關(guān)聯(lián)字段添加索引;避免在關(guān)聯(lián)查詢中使用子查詢,可替換為 join;
- 分頁查詢優(yōu)化:確保分頁查詢的排序字段有索引,避免分頁時(shí)出現(xiàn)全表排序;
- 批量操作優(yōu)化:如前所述,使用 MyBatis-Plus 的批量方法,減少數(shù)據(jù)庫(kù)連接次數(shù)。
- 日志進(jìn)階配置:對(duì)于生產(chǎn)環(huán)境,不建議將日志打印到控制臺(tái)(影響性能),可配置將 SQL 日志輸出到文件,并只打印慢查詢?nèi)罩荆ㄍㄟ^ Logback/Log4j2 配置),示例(Logback):
到此這篇關(guān)于MyBatis-Plus從基礎(chǔ)CRUD到高級(jí)查詢與性能優(yōu)化實(shí)戰(zhàn)指南的文章就介紹到這了,更多相關(guān)mybatisplus crud查詢內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- MyBatisPlus如何優(yōu)化千萬級(jí)數(shù)據(jù)的CRUD
- MyBatisPlus中CRUD使用方法詳解
- MyBatisPlus標(biāo)準(zhǔn)數(shù)據(jù)層CRUD的使用詳解
- mybatisplus?復(fù)合主鍵(多主鍵)?CRUD示例詳解
- MybatisPlus,無XML分分鐘實(shí)現(xiàn)CRUD操作
- Mybatisplus多表關(guān)聯(lián)分頁查詢多種實(shí)現(xiàn)方式
- MybatisPlus多表查詢及分頁查詢完整代碼
- 深入解析MybatisPlus多表連接查詢
- MybatisPlus將自定義的sql列表查詢返回改為分頁查詢
相關(guān)文章
解決java.util.HashMap$Values?cannot?be?cast?to?java.ut的問題
這篇文章主要介紹了解決java.util.HashMap$Values?cannot?be?cast?to?java.ut的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
@Valid 校驗(yàn)無效,BindingResult未獲得錯(cuò)誤的解決
這篇文章主要介紹了@Valid 校驗(yàn)無效,BindingResult未獲得錯(cuò)誤的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
Java多線程并發(fā)之線程池任務(wù)請(qǐng)求攔截測(cè)試實(shí)例
這篇文章主要介紹了Java多線程并發(fā)之線程池任務(wù)請(qǐng)求攔截測(cè)試實(shí)例,隊(duì)列中永遠(yuǎn)沒有線程被加入,即使線程池已滿,也不會(huì)導(dǎo)致被加入排隊(duì)隊(duì)列,實(shí)現(xiàn)了只有線程池存在空閑線程的時(shí)候才會(huì)接受新任務(wù)的需求,需要的朋友可以參考下2023-12-12
SpringBoot用ServiceLocatorFactoryBean優(yōu)雅切換支付渠道
本文主要介紹了SpringBoot用ServiceLocatorFactoryBean優(yōu)雅切換支付渠道,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-10-10
Java的接口調(diào)用時(shí)的權(quán)限驗(yàn)證功能的實(shí)現(xiàn)
這篇文章主要介紹了Java的接口調(diào)用時(shí)的權(quán)限驗(yàn)證功能的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
如何用java做一個(gè)word轉(zhuǎn)圖片的功能詳解
這篇文章主要給大家介紹了關(guān)于如何用java做一個(gè)word轉(zhuǎn)圖片的功能,通過實(shí)現(xiàn)Java Word轉(zhuǎn)圖片功能,避免PDF中間轉(zhuǎn)換損耗,涵蓋分頁處理、字體設(shè)置、性能優(yōu)化及替代方案對(duì)比,需要的朋友可以參考下2025-05-05
SpringBoot實(shí)現(xiàn)Md5對(duì)數(shù)據(jù)庫(kù)數(shù)據(jù)加密的示例
本文主要介紹了SpringBoot實(shí)現(xiàn)Md5對(duì)數(shù)據(jù)庫(kù)數(shù)據(jù)加密的示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04

