MyBatis-Plus 全解析(從入門到實戰,覆蓋99%開發場景)

MyBatis-Plus(簡稱 MP)是 MyBatis 的增強工具,核心思想是「簡化開發、提高效率」,在 MyBatis 基礎上只做增強不做改變,支持所有 MyBatis 原生功能,同時提供了 CRUD 接口、條件構造器、代碼生成器等一系列高效特性,徹底告別重複的 XML 配置和 SQL 編寫。

本文將從「環境搭建→核心特性→進階用法→實戰案例→最佳實踐」全方位拆解 MyBatis-Plus,覆蓋從入門到生產的全場景需求。

一、MyBatis-Plus 核心優勢

先明確 MP 相比原生 MyBatis 的核心價值,理解為什麼要使用它:

  1. 零 XML 配置:內置 CRUD 接口,無需編寫 XML 和 Mapper 方法,直接調用即可;
  2. 強大的條件構造器:支持 Lambda 表達式、鏈式調用,動態 SQL 編寫無需拼接字符串;
  3. 代碼生成器:自動生成 Entity、Mapper、Service、Controller 全套代碼,開發效率翻倍;
  4. 豐富的插件支持:分頁插件、樂觀鎖插件、邏輯刪除、自動填充等,開箱即用;
  5. 低侵入性:完全兼容 MyBatis 原生用法,老項目可無縫遷移;
  6. 高性能:底層優化充分,無額外性能損耗,甚至通過緩存、SQL 優化提升效率。

二、前置環境準備(Spring Boot 集成)

MP 支持 Spring、Spring Boot、Spring Cloud 等環境,這裏以最常用的 Spring Boot 3.x + MySQL 8.x 為例,搭建基礎環境。

2.1 依賴配置(Maven)

pom.xml 中引入 MP 核心依賴(無需額外引入 MyBatis,MP 已內置兼容版本):

<!-- Spring Boot 父依賴(已引入可忽略) -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.0</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.5</version> <!-- 推薦使用最新穩定版 -->
    </dependency>

    <!-- MySQL 驅動(Spring Boot 3.x 需用 8.x 驅動) -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!--  lombok(簡化實體類 getter/setter,可選但推薦) -->
    <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)

配置數據庫連接、MyBatis-Plus 核心參數(核心配置必須正確,否則無法啓動):

spring:
  # 數據庫配置
  datasource:
    url: jdbc:mysql://localhost:3306/mp_demo?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
    username: root  # 你的 MySQL 用户名
    password: 123456  # 你的 MySQL 密碼
    driver-class-name: com.mysql.cj.jdbc.Driver  # MySQL 8.x 驅動類名

mybatis-plus:
  # 1.  mapper.xml 文件存放路徑(若使用 XML 自定義 SQL,需配置)
  mapper-locations: classpath:mapper/*.xml
  # 2. 實體類包掃描(自動識別 @TableName 註解的實體)
  type-aliases-package: com.example.mpdemo.entity
  # 3. 全局配置
  global-config:
    db-config:
      # 主鍵生成策略(默認雪花算法,後續詳細講)
      id-type: ASSIGN_ID
      # 邏輯刪除字段配置(全局統一配置,後續詳細講)
      logic-delete-field: isDeleted
      logic-delete-value: 1  # 已刪除
      logic-not-delete-value: 0  # 未刪除
      # 表名前綴(全局統一添加表前綴,如實體 User 對應表 mp_user)
      table-prefix: mp_
  # 4. 原生 MyBatis 配置(可選)
  configuration:
    # 打印 SQL(開發環境開啓,生產環境關閉)
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 駝峯命名自動轉換(數據庫字段 user_name → 實體屬性 userName)
    map-underscore-to-camel-case: true
    # 開啓二級緩存(默認關閉,根據需求開啓)
    cache-enabled: false

2.3 啓動類註解

在 Spring Boot 啓動類上添加 @MapperScan 註解,掃描 Mapper 接口所在包:

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

// 掃描 Mapper 接口(替換為你的 Mapper 包路徑)
@MapperScan("com.example.mpdemo.mapper")
@SpringBootApplication
public class MpDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(MpDemoApplication.class, args);
    }
}

2.4 環境驗證

創建測試表 mp_user(對應實體 User),用於後續功能測試:

CREATE TABLE `mp_user` (
  `id` bigint NOT NULL COMMENT '主鍵ID',
  `user_name` varchar(50) NOT NULL COMMENT '用户名',
  `age` int DEFAULT NULL COMMENT '年齡',
  `email` varchar(100) DEFAULT NULL COMMENT '郵箱',
  `is_deleted` tinyint 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='用户表';

至此,MyBatis-Plus 基礎環境搭建完成,接下來進入核心特性講解。

三、核心特性:Entity 實體類註解(映射基礎)

Entity 是數據庫表的映射類,MP 通過註解關聯實體與表、字段與列,核心註解如下(必須掌握):

註解

作用

常用屬性

示例

@TableName

關聯實體與數據庫表

value(表名)、schema(數據庫)

@TableName("mp_user")

@TableId

標記主鍵字段

type(主鍵生成策略)

@TableId(type = IdType.ASSIGN_ID)

@TableField

標記普通字段(非主鍵)

value(列名)、exist(是否數據庫字段)、fill(自動填充策略)

@TableField(value = "user_name")

@TableLogic

標記邏輯刪除字段(優先級高於全局配置)

value(未刪除值)、delval(已刪除值)

@TableLogic(value = "0", delval = "1")

@Version

標記樂觀鎖字段

-

@Version private Integer version;

@EnumValue

枚舉字段映射(數據庫存儲枚舉值)

-

枚舉類中 @EnumValue private Integer code;

@TableNamePrefix

單獨給實體添加表前綴(覆蓋全局配置)

value(前綴)

@TableNamePrefix("mp_")

3.1 實體類示例(完整規範)

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

// Lombok 註解,自動生成 getter/setter/toString 等方法
@Data
// 關聯數據庫表(若全局配置了 table-prefix,可省略 value,自動拼接前綴)
@TableName("mp_user")
public class User {
    // 主鍵(雪花算法生成,適合分佈式場景)
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    // 普通字段(數據庫列名 user_name,駝峯自動轉換可省略 value)
    @TableField(value = "user_name")
    private String userName;

    // 普通字段(允許為 null)
    private Integer age;

    // 普通字段(非數據庫字段,查詢時忽略)
    @TableField(exist = false)
    private String tempField;

    // 邏輯刪除字段(覆蓋全局配置)
    @TableLogic(value = "0", delval = "1")
    private Integer isDeleted;

    // 自動填充:創建時間(插入時填充)
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    // 自動填充:更新時間(插入和更新時填充)
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

3.2 主鍵生成策略(IdType

MP 提供 7 種主鍵生成策略,根據業務場景選擇:

策略類型

説明

適用場景

AUTO

數據庫自增(需數據庫表主鍵設為 AUTO_INCREMENT)

單庫單表,無分佈式需求

ASSIGN_ID(默認)

雪花算法(Snowflake)生成 19 位 Long 型 ID

分佈式系統,需要全局唯一 ID

ASSIGN_UUID

生成 UUID(去除橫線,32 位字符串)

無需有序 ID,需要字符串主鍵

INPUT

手動輸入主鍵

主鍵由業務邏輯生成(如訂單號)

NONE

未設置策略(默認跟隨全局配置)

-

ID_WORKER

舊版雪花算法(已過時,推薦用 ASSIGN_ID)

兼容舊項目

ID_WORKER_STR

舊版雪花算法(字符串形式,已過時)

兼容舊項目,字符串主鍵

注意:使用 AUTO 策略時,需確保數據庫表主鍵字段設置了 AUTO_INCREMENT,否則會報錯。

四、核心特性:CRUD 接口(無 SQL 開發)

MP 提供了 BaseMapper(Mapper 層)和 IService(Service 層)兩套 CRUD 接口,繼承後直接調用,無需編寫任何 SQL 和方法。

4.1 Mapper 層:BaseMapper 接口

4.1.1 用法

創建 Mapper 接口,繼承 BaseMapper<Entity>,即可獲得全套 CRUD 方法:

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

// @Repository 註解(Spring 掃描為 DAO 組件)
@Repository
public interface UserMapper extends BaseMapper<User> {
    // 無需編寫任何方法,BaseMapper 已提供全套 CRUD
}
4.1.2 BaseMapper 核心方法(常用 15+)

方法名

功能描述

參數説明

返回值

insert(T entity)

新增一條記錄

實體對象(非 null 字段會插入)

影響行數(int)

deleteById(Serializable id)

根據主鍵刪除

主鍵 ID(Long/String 等)

影響行數(int)

deleteByMap(Map<String, Object> map)

根據條件刪除(Map 鍵為列名,值為條件)

條件 Map

影響行數(int)

delete(Wrapper<T> queryWrapper)

根據條件構造器刪除

QueryWrapper/LambdaQueryWrapper

影響行數(int)

updateById(T entity)

根據主鍵更新(非 null 字段才更新)

實體對象

影響行數(int)

update(T entity, Wrapper<T> updateWrapper)

條件更新(entity 存更新字段,wrapper 存條件)

實體對象 + 條件構造器

影響行數(int)

selectById(Serializable id)

根據主鍵查詢

主鍵 ID

實體對象(T)

selectBatchIds(Collection<? extends Serializable> idList)

批量查詢(根據主鍵集合)

主鍵集合(如 List)

List

selectByMap(Map<String, Object> map)

根據條件查詢(Map 條件)

條件 Map

List

selectList(Wrapper<T> queryWrapper)

根據條件構造器查詢列表

條件構造器(可無,查全部)

List

selectOne(Wrapper<T> queryWrapper)

根據條件查詢單條記錄(需確保結果唯一)

條件構造器

T(無結果返回 null)

selectCount(Wrapper<T> queryWrapper)

統計符合條件的記錄數

條件構造器

總數(long)

selectPage(Page<T> page, Wrapper<T> queryWrapper)

分頁查詢

分頁對象 + 條件構造器

Page(含總條數、當前頁數據)

4.1.3 Mapper 層調用示例(Service 層中注入使用)
import com.example.mpdemo.entity.User;
import com.example.mpdemo.mapper.UserMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;

@Service
public class UserService {

    // 注入 Mapper(也可使用 @Autowired)
    @Resource
    private UserMapper userMapper;

    // 1. 新增用户
    public boolean addUser(User user) {
        return userMapper.insert(user) > 0;
    }

    // 2. 根據 ID 查詢用户
    public User getUserById(Long id) {
        return userMapper.selectById(id);
    }

    // 3. 查詢所有用户
    public List<User> getAllUsers() {
        return userMapper.selectList(null); // queryWrapper 為 null 表示無條件
    }

    // 4. 根據 ID 更新用户(只更新非 null 字段)
    public boolean updateUser(User user) {
        return userMapper.updateById(user) > 0;
    }

    // 5. 根據 ID 刪除用户
    public boolean deleteUser(Long id) {
        return userMapper.deleteById(id) > 0;
    }
}

4.2 Service 層:IService 接口(推薦)

IService 是對 BaseMapper 的進一步封裝,提供了更豐富的批量操作、鏈式調用等功能,同時規範了 Service 層的代碼結構(IService 接口 + ServiceImpl 實現類)。

4.2.1 用法步驟
  1. 創建 Service 接口,繼承 IService<Entity>
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.mpdemo.entity.User;

public interface UserService extends IService<User> {
    // 可添加自定義業務方法(如複雜查詢、多表關聯)
    List<User> getUserByAgeRange(Integer minAge, Integer maxAge);
}
  1. 創建 Service 實現類,繼承 ServiceImpl<Mapper, Entity>,並實現自定義接口:
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;
import java.util.List;

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

    // 實現自定義方法(可直接使用 baseMapper 調用 Mapper 層方法)
    @Override
    public List<User> getUserByAgeRange(Integer minAge, Integer maxAge) {
        // 這裏後續結合條件構造器實現,先佔位
        return null;
    }
}
4.2.2 IService 核心方法(比 BaseMapper 更強大)

方法名

功能描述

優勢

save(T entity)

新增(同 BaseMapper.insert)

返回 boolean,更直觀

saveBatch(Collection<T> entityList)

批量新增(默認批次 1000)

優化批量插入性能,無需手動循環

saveOrUpdate(T entity)

新增或更新(有主鍵則更新,無則新增)

簡化“upsert”場景

saveOrUpdateBatch(Collection<T> entityList)

批量新增或更新

高效處理批量 upsert

removeById(Serializable id)

根據主鍵刪除(同 BaseMapper.deleteById)

返回 boolean

removeBatchByIds(Collection<? extends Serializable> idList)

批量刪除(根據主鍵集合)

無需手動循環刪除

updateById(T entity)

根據主鍵更新(同 BaseMapper.updateById)

返回 boolean

updateBatchById(Collection<T> entityList)

批量更新(根據主鍵)

優化批量更新性能

getById(Serializable id)

根據主鍵查詢(同 BaseMapper.selectById)

返回 T,命名更直觀

list()

查詢所有(同 BaseMapper.selectList(null))

簡化無條件查詢

listByIds(Collection<? extends Serializable> idList)

批量查詢(同 BaseMapper.selectBatchIds)

命名更直觀

count()

統計總數(同 BaseMapper.selectCount(null))

簡化無條件統計

page(Page<T> page)

分頁查詢(無條件)

簡化分頁操作

page(Page<T> page, Wrapper<T> queryWrapper)

條件分頁查詢

分頁 + 條件構造器無縫結合

4.2.3 Service 層調用示例(Controller 中使用)
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.mpdemo.entity.User;
import com.example.mpdemo.service.UserService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;

@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private UserService userService;

    // 新增用户
    @PostMapping
    public boolean addUser(@RequestBody User user) {
        return userService.save(user);
    }

    // 批量新增用户
    @PostMapping("/batch")
    public boolean addUserBatch(@RequestBody List<User> userList) {
        return userService.saveBatch(userList);
    }

    // 根據 ID 查詢用户
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.getById(id);
    }

    // 分頁查詢所有用户(page:頁碼,size:每頁條數)
    @GetMapping("/page")
    public IPage<User> getUserPage(@RequestParam Integer page, @RequestParam Integer size) {
        return userService.page(new Page<>(page, size));
    }

    // 根據 ID 更新用户
    @PutMapping
    public boolean updateUser(@RequestBody User user) {
        return userService.updateById(user);
    }

    // 根據 ID 刪除用户
    @DeleteMapping("/{id}")
    public boolean deleteUser(@PathVariable Long id) {
        return userService.removeById(id);
    }
}

五、核心特性:條件構造器(動態 SQL 神器)

條件構造器是 MP 最強大的功能之一,支持 鏈式調用、Lambda 表達式、複雜條件組合,無需手動拼接 SQL,徹底解決動態 SQL 編寫繁瑣、易出錯的問題。

MP 提供 4 種核心條件構造器,重點掌握前兩種:

構造器類型

説明

優點

缺點

QueryWrapper<T>

非 Lambda 形式,通過字符串指定字段名

兼容所有場景,無需實體類字段對應

字段名硬編碼,易寫錯(無編譯檢查)

LambdaQueryWrapper<T>

Lambda 形式,通過實體類方法引用字段

字段名編譯檢查,無硬編碼錯誤

實體類字段與數據庫列必須映射正確

UpdateWrapper<T>

非 Lambda 形式,用於更新操作的條件構造

支持更新字段設置(如 set("age", 20))

字段名硬編碼

LambdaUpdateWrapper<T>

Lambda 形式,用於更新操作的條件構造

字段名編譯檢查,更新字段無需硬編碼

實體類字段與數據庫列必須映射正確

5.1 QueryWrapper 用法(非 Lambda)

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.mpdemo.entity.User;
import java.util.List;

// 1. 條件查詢:查詢年齡 > 18 且用户名包含 "張" 的用户
public List<User> queryUser() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    // 鏈式調用條件:age > 18
    queryWrapper.gt("age", 18)
                // 用户名 like %張%
                .like("user_name", "張")
                // 按創建時間降序排序
                .orderByDesc("create_time");
    return userService.list(queryWrapper);
}

// 2. 條件統計:統計郵箱不為 null 且未刪除的用户數
public long countUser() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.isNotNull("email")
                .eq("is_deleted", 0); // 邏輯刪除字段
    return userService.count(queryWrapper);
}

5.2 LambdaQueryWrapper 用法(推薦,無硬編碼)

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.mpdemo.entity.User;
import java.util.List;

// 1. 條件查詢:年齡在 18-30 之間,且郵箱以 "@qq.com" 結尾
public List<User> queryUserByLambda() {
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    // 字段名通過 User::getter 方法引用,編譯時檢查
    lambdaQueryWrapper.between(User::getAge, 18, 30) // age between 18 and 30
                      .likeRight(User::getEmail, "@qq.com") // email like '@qq.com%'
                      .orderByAsc(User::getCreateTime); // 按創建時間升序
    return userService.list(lambdaQueryWrapper);
}

// 2. 動態條件:根據傳入參數動態拼接條件(如用户名和年齡可選)
public List<User> queryUserDynamic(String userName, Integer age) {
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    // 若 userName 不為 null 且不為空,添加模糊查詢條件
    if (userName != null && !userName.isEmpty()) {
        lambdaQueryWrapper.like(User::getUserName, userName);
    }
    // 若 age 不為 null,添加年齡 >= 條件
    if (age != null) {
        lambdaQueryWrapper.ge(User::getAge, age);
    }
    // 強制查詢未刪除的用户(邏輯刪除)
    lambdaQueryWrapper.eq(User::getIsDeleted, 0);
    return userService.list(lambdaQueryWrapper);
}

5.3 UpdateWrapper 用法(更新條件構造)

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.example.mpdemo.entity.User;

// 1. 條件更新:將用户名 "張三" 的年齡改為 25,郵箱改為 zhangsan@qq.com
public boolean updateUserByWrapper() {
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    // 設置更新字段
    updateWrapper.set("age", 25)
                  .set("email", "zhangsan@qq.com")
                  // 設置更新條件
                  .eq("user_name", "張三")
                  .eq("is_deleted", 0);
    return userService.update(updateWrapper);
}

5.4 LambdaUpdateWrapper 用法(推薦)

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.example.mpdemo.entity.User;

// 1. 條件更新:年齡 > 30 的用户,年齡統一加 1
public boolean updateUserByLambdaWrapper() {
    LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
    // 更新字段:age = age + 1
    lambdaUpdateWrapper.setSql("age = age + 1")
                      // 條件:age > 30 且未刪除
                      .gt(User::getAge, 30)
                      .eq(User::getIsDeleted, 0);
    return userService.update(lambdaUpdateWrapper);
}

// 2. 動態更新:只更新非 null 的字段
public boolean updateUserDynamic(User user) {
    LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
    // 條件:根據主鍵 ID 更新
    lambdaUpdateWrapper.eq(User::getId, user.getId());
    // 若 userName 不為 null,更新用户名
    if (user.getUserName() != null) {
        lambdaUpdateWrapper.set(User::getUserName, user.getUserName());
    }
    // 若 age 不為 null,更新年齡
    if (user.getAge() != null) {
        lambdaUpdateWrapper.set(User::getAge, user.getAge());
    }
    return userService.update(lambdaUpdateWrapper);
}

5.5 常用條件構造方法(全量彙總)

方法名

功能描述

示例(Lambda 形式)

eq

等於(=)

eq(User::getAge, 20)age = 20

ne

不等於(!=)

ne(User::getAge, 20)age != 20

gt

大於(>)

gt(User::getAge, 20)age > 20

ge

大於等於(>=)

ge(User::getAge, 20)age >= 20

lt

小於(<)

lt(User::getAge, 20)age < 20

le

小於等於(<=)

le(User::getAge, 20)age <= 20

between

介於兩者之間(between)

between(User::getAge, 18, 30)age between 18 and 30

notBetween

不介於兩者之間(not between)

notBetween(User::getAge, 18, 30)age not between 18 and 30

like

模糊查詢(like %val%)

like(User::getUserName, "張")user_name like '%張%'

likeLeft

左模糊(like %val)

likeLeft(User::getEmail, "@qq.com")email like '%@qq.com'

likeRight

右模糊(like val%)

likeRight(User::getEmail, "zhang")email like 'zhang%'

notLike

不匹配模糊查詢(not like)

notLike(User::getUserName, "張")user_name not like '%張%'

isNull

字段為 null

isNull(User::getEmail)email is null

isNotNull

字段不為 null

isNotNull(User::getEmail)email is not null

in

包含在集合中(in)

in(User::getAge, 18,20,22)age in (18,20,22)

notIn

不包含在集合中(not in)

notIn(User::getAge, 18,20)age not in (18,20)

inSql

子查詢 in(in (sql))

inSql(User::getId, "select id from mp_user where age > 30")

groupBy

分組(group by)

groupBy(User::getAge, User::getUserName)group by age, user_name

orderByAsc

升序排序(order by asc)

orderByAsc(User::getCreateTime)order by create_time asc

orderByDesc

降序排序(order by desc)

orderByDesc(User::getCreateTime)order by create_time desc

having

分組後條件(having)

groupBy(User::getAge).having("count(*) > 10")group by age having count(*) > 10

and

並且(and)

eq(User::getAge,20).and(i -> i.like(User::getUserName,"張"))age=20 and user_name like '%張%'

or

或者(or)

eq(User::getAge,20).or(i -> i.like(User::getUserName,"李"))age=20 or user_name like '%李%'

exists

存在子查詢(exists (sql))

exists("select 1 from mp_order where user_id = mp_user.id")

notExists

不存在子查詢(not exists)

notExists("select 1 from mp_order where user_id = mp_user.id")

set

更新字段(update 時用)

set(User::getAge, 25)age = 25

setSql

自定義更新 SQL(update 時用)

setSql("age = age + 1")age = age + 1

六、核心特性:插件機制(分頁、樂觀鎖等)

MP 提供了一系列開箱即用的插件,通過簡單配置即可啓用,覆蓋分頁、樂觀鎖、邏輯刪除、性能分析等常用場景。

6.1 分頁插件(最常用)

MP 的分頁插件支持物理分頁(而非內存分頁),自動攔截查詢語句添加 limit 條件,支持 MySQL、Oracle、PostgreSQL 等主流數據庫。

6.1.1 配置分頁插件(Spring Boot 3.x)

創建 MP 配置類,註冊分頁插件:

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;
    }
}
6.1.2 分頁查詢用法
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.mpdemo.entity.User;
import java.util.List;

// 1. 基礎分頁(無條件)
public IPage<User> getUserPage(Integer pageNum, Integer pageSize) {
    // Page 構造器:pageNum(頁碼,從 1 開始),pageSize(每頁條數)
    Page<User> page = new Page<>(pageNum, pageSize);
    // 調用 Service 層 page 方法
    return userService.page(page);
}

// 2. 條件分頁(帶 Lambda 條件構造器)
public IPage<User> getUserPageByCondition(Integer pageNum, Integer pageSize, String userName) {
    Page<User> page = new Page<>(pageNum, pageSize);
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    if (userName != null) {
        lambdaQueryWrapper.like(User::getUserName, userName);
    }
    // 分頁 + 條件查詢
    IPage<User> userPage = userService.page(page, lambdaQueryWrapper);
    
    // IPage 核心屬性(返回給前端)
    long total = userPage.getTotal(); // 總記錄數
    long pages = userPage.getPages(); // 總頁數
    List<User> records = userPage.getRecords(); // 當前頁數據
    boolean hasNext = userPage.hasNext(); // 是否有下一頁
    boolean hasPrevious = userPage.hasPrevious(); // 是否有上一頁
    
    return userPage;
}

// 3. 自定義 XML SQL 分頁(若需自定義 SQL,分頁插件同樣生效)
// Mapper 接口方法:
// IPage<User> selectUserByAgePage(Page<User> page, @Param("minAge") Integer minAge);
// XML 中無需寫 limit,分頁插件自動添加:
// <select id="selectUserByAgePage" resultType="com.example.mpdemo.entity.User">
//     select * from mp_user where age >= #{minAge}
// </select>

6.2 樂觀鎖插件(解決併發更新衝突)

樂觀鎖基於「版本號機制」,適用於併發場景下避免數據覆蓋,核心邏輯:

  1. 實體類添加版本號字段(如 version),標記 @Version 註解;
  2. 更新時,SQL 自動拼接 where version = 原版本號
  3. 若版本號不匹配(已被其他線程更新),更新失敗(返回影響行數 0)。
6.2.1 配置樂觀鎖插件

MyBatisPlusConfig 中添加樂觀鎖插件:

import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;

@Configuration
public class MyBatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分頁插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 樂觀鎖插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}
6.2.2 實體類添加版本號字段
import com.baomidou.mybatisplus.annotation.Version;

@Data
@TableName("mp_user")
public class User {
    // ... 其他字段省略 ...
    
    // 樂觀鎖版本號字段(數據庫表需添加 version 字段,默認值 0)
    @Version
    private Integer version;
}
6.2.3 樂觀鎖使用示例
import com.example.mpdemo.entity.User;
import org.springframework.transaction.annotation.Transactional;

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

    // 併發更新用户年齡(樂觀鎖示例)
    @Transactional
    public boolean updateUserAge(Long userId, Integer newAge) {
        // 1. 查詢用户(獲取當前版本號)
        User user = getById(userId);
        if (user == null) {
            return false;
        }
        
        // 2. 修改年齡
        user.setAge(newAge);
        
        // 3. 更新(SQL 自動拼接 where version = user.getVersion())
        boolean success = updateById(user);
        if (!success) {
            // 更新失敗(版本號不匹配),可重試或拋異常
            throw new RuntimeException("併發更新衝突,請重試");
        }
        return true;
    }
}

6.3 邏輯刪除插件(假刪除)

邏輯刪除是「假刪除」,通過字段標記數據是否刪除(如 is_deleted),查詢時自動過濾已刪除數據,避免數據物理刪除後無法恢復。

6.3.1 配置(已在 application.yml 中全局配置,無需額外插件)
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: isDeleted  # 邏輯刪除字段名(實體類字段名)
      logic-delete-value: 1          # 已刪除值
      logic-not-delete-value: 0      # 未刪除值
6.3.2 實體類配置(可選,覆蓋全局)
import com.baomidou.mybatisplus.annotation.TableLogic;

@Data
@TableName("mp_user")
public class User {
    // ... 其他字段省略 ...
    
    // 邏輯刪除字段(覆蓋全局配置)
    @TableLogic(value = "0", delval = "1")
    private Integer isDeleted;
}
6.3.3 邏輯刪除使用示例
// 1. 刪除操作(自動改為更新 is_deleted = 1)
userService.removeById(1L); 
// 執行的 SQL:update mp_user set is_deleted = 1 where id = 1 and is_deleted = 0

// 2. 查詢操作(自動過濾 is_deleted = 1 的數據)
userService.getById(1L); 
// 執行的 SQL:select * from mp_user where id = 1 and is_deleted = 0

userService.list();
// 執行的 SQL:select * from mp_user where is_deleted = 0

// 3. 如需查詢已刪除數據,需手動添加條件
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getIsDeleted, 1);
userService.list(queryWrapper);

6.4 性能分析插件(開發環境調試)

性能分析插件可打印 SQL 執行時間、參數、結果,方便開發環境調試慢查詢,生產環境需關閉。

6.4.1 配置性能分析插件
import com.baomidou.mybatisplus.extension.plugins.inner.SqlExplainInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PerformanceInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
public class MyBatisPlusConfig {

    // 僅在 dev 環境啓用(通過 @Profile 控制)
    @Profile({"dev"})
    @Bean
    public PerformanceInterceptor performanceInterceptor() {
        PerformanceInterceptor interceptor = new PerformanceInterceptor();
        // 設置 SQL 執行超時時間(單位:ms),超時則拋異常
        interceptor.setMaxTime(500);
        // 格式化 SQL 輸出(美觀易讀)
        interceptor.setFormat(true);
        return interceptor;
    }
}

七、進階用法:自定義 SQL(兼容 MyBatis 原生)

MP 完全兼容 MyBatis 原生用法,當 CRUD 接口和條件構造器無法滿足複雜需求(如多表關聯查詢、複雜子查詢)時,可通過 XML 或註解自定義 SQL。

7.1 註解方式自定義 SQL(簡單 SQL 推薦)

在 Mapper 接口中直接用 @Select@Insert 等註解編寫 SQL:

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.example.mpdemo.entity.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;

public interface UserMapper extends BaseMapper<User> {

    // 1. 簡單查詢:根據用户名和年齡查詢用户(註解 SQL)
    @Select("select * from mp_user where user_name = #{userName} and age = #{age} and is_deleted = 0")
    User selectByUserNameAndAge(@Param("userName") String userName, @Param("age") Integer age);

    // 2. 結合條件構造器:自定義 SQL + 動態條件(${ew.customSqlSegment} 是固定語法)
    @Select("select u.id, u.user_name, u.age from mp_user u ${ew.customSqlSegment}")
    List<User> selectCustomByWrapper(@Param(Constants.WRAPPER) Wrapper<User> wrapper);

    // 3. 分頁查詢:自定義 SQL + 分頁插件
    @Select("select * from mp_user where age >= #{minAge} and is_deleted = 0")
    IPage<User> selectByAgePage(IPage<User> page, @Param("minAge") Integer minAge);
}

7.2 XML 方式自定義 SQL(複雜 SQL 推薦)

7.2.1 配置 XML 路徑(已在 application.yml 中配置)
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml  # XML 文件放在 resources/mapper 目錄下
7.2.2 創建 XML 文件(resources/mapper/UserMapper.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 接口全路徑一致 -->
<mapper namespace="com.example.mpdemo.mapper.UserMapper">

    <!-- 1. 多表關聯查詢(用户表 + 訂單表,查詢用户及關聯的訂單數) -->
    <select id="selectUserWithOrderCount" resultType="java.util.Map">
        select 
            u.id, u.user_name, u.age, count(o.id) as order_count
        from 
            mp_user u
        left join 
            mp_order o on u.id = o.user_id
        where 
            u.is_deleted = 0
        group by 
            u.id
    </select>

    <!-- 2. 動態 SQL(結合 MP 條件構造器,${ew.customSqlSegment} 拼接條件) -->
    <select id="selectUserByXmlWrapper" resultType="com.example.mpdemo.entity.User">
        select * from mp_user
        <where>
            ${ew.customSqlSegment}
        </where>
    </select>

    <!-- 3. 批量插入(優化性能,比循環 insert 高效) -->
    <insert id="batchInsertUser">
        insert into mp_user (id, user_name, age, email, is_deleted, create_time, update_time)
        values
        <foreach collection="list" item="user" separator=",">
            (#{user.id}, #{user.userName}, #{user.age}, #{user.email}, #{user.isDeleted}, #{user.createTime}, #{user.updateTime})
        </foreach>
    </insert>

</mapper>
7.2.3 Mapper 接口關聯 XML 方法
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.mpdemo.entity.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;

public interface UserMapper extends BaseMapper<User> {

    // 關聯 XML 中的 selectUserWithOrderCount 方法
    List<Map<String, Object>> selectUserWithOrderCount();

    // 關聯 XML 中的 selectUserByXmlWrapper 方法(結合條件構造器)
    List<User> selectUserByXmlWrapper(@Param("ew") Wrapper<User> wrapper);

    // 關聯 XML 中的 batchInsertUser 方法
    int batchInsertUser(@Param("list") List<User> userList);
}

7.3 多表關聯查詢(MP 增強方案)

除了原生 XML 多表查詢,MP 還提供了 @TableName 關聯、LambdaQueryChainWrapper 鏈式查詢等增強方式,這裏推薦 XML 方式(最靈活)和 註解關聯方式(簡單多表場景)。

註解關聯示例(一對一)
// 訂單實體(關聯用户表)
@Data
@TableName("mp_order")
public class Order {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    private Long userId; // 關聯用户表主鍵
    private String orderNo; // 訂單號
    private BigDecimal amount; // 金額
    private Integer isDeleted;

    // 一對一關聯用户信息(非數據庫字段,查詢時通過註解關聯)
    @TableField(exist = false)
    private User user;
}

// Mapper 接口註解關聯查詢
public interface OrderMapper extends BaseMapper<Order> {
    @Select("select o.*, u.user_name, u.age from mp_order o " +
            "left join mp_user u on o.user_id = u.id " +
            "where o.id = #{id} and o.is_deleted = 0")
    // @ResultMap 映射關聯字段(也可使用 @Result 逐個映射)
    @ResultMap("orderUserMap")
    Order selectOrderWithUser(@Param("id") Long id);
}

八、進階用法:代碼生成器(AutoGenerator)

MP 代碼生成器能根據數據庫表結構,自動生成 Entity、Mapper、Service、Controller、XML 全套代碼,支持自定義模板,開發效率直接翻倍。

8.1 引入代碼生成器依賴

<!-- MyBatis-Plus 代碼生成器核心依賴 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.5</version>
</dependency>

<!-- 模板引擎(MP 支持 Velocity、Freemarker、Beetl,這裏用 Velocity) -->
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.3</version>
</dependency>

8.2 編寫代碼生成器配置類

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;
import java.util.Collections;

public class CodeGenerator {
    public static void main(String[] args) {
        // 數據庫連接配置
        String url = "jdbc:mysql://localhost:3306/mp_demo?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
        String username = "root";
        String password = "123456";

        // 代碼生成核心配置
        FastAutoGenerator.create(url, username, password)
                // 1. 全局配置
                .globalConfig(builder -> {
                    builder.author("your-name") // 作者名
                            .outputDir(System.getProperty("user.dir") + "/src/main/java") // 代碼輸出目錄(項目 java 目錄)
                            .commentDate("yyyy-MM-dd") // 註釋日期格式
                            .disableOpenDir() // 生成後不自動打開文件夾
                            .enableSwagger() // 啓用 Swagger 註解(需引入 Swagger 依賴)
                            .fileOverride(); // 覆蓋已存在的文件
                })
                // 2. 包配置(代碼存放的包路徑)
                .packageConfig(builder -> {
                    builder.parent("com.example.mpdemo") // 父包名
                            .entity("entity") // 實體類包名
                            .mapper("mapper") // Mapper 接口包名
                            .service("service") // Service 接口包名
                            .serviceImpl("service.impl") // Service 實現類包名
                            .controller("controller") // Controller 包名
                            .xml("mapper.xml") // XML 文件包名(resources 目錄下)
                            .pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "/src/main/resources/mapper")); // XML 輸出目錄
                })
                // 3. 策略配置(表、字段、代碼生成規則)
                .strategyConfig(builder -> {
                    builder.addInclude("mp_user", "mp_order") // 要生成代碼的表名(多個表用逗號分隔)
                            .addTablePrefix("mp_") // 表前綴(生成實體類時自動去除前綴,如 mp_user → User)
                            // 實體類策略
                            .entityBuilder()
                            .enableLombok() // 啓用 Lombok 註解(生成 @Data 等)
                            .enableTableFieldAnnotation() // 為字段添加 @TableField 註解
                            .idType(com.baomidou.mybatisplus.generator.config.rules.IdType.ASSIGN_ID) // 主鍵生成策略
                            .logicDeleteFieldName("isDeleted") // 邏輯刪除字段名
                            .enableChainModel() // 啓用鏈式調用(生成 set 方法返回 this)
                            // Mapper 策略
                            .mapperBuilder()
                            .enableBaseResultMap() // 啓用 BaseResultMap(XML 中生成結果映射)
                            .enableBaseColumnList() // 啓用 BaseColumnList(XML 中生成字段列表)
                            .enableMapperAnnotation() // 為 Mapper 接口添加 @Mapper 註解
                            // Service 策略
                            .serviceBuilder()
                            .formatServiceFileName("%sService") // Service 接口命名格式(如 UserService)
                            .formatServiceImplFileName("%sServiceImpl") // Service 實現類命名格式(如 UserServiceImpl)
                            // Controller 策略
                            .controllerBuilder()
                            .enableRestStyle() // 啓用 RestController 註解
                            .enableHyphenStyle(); // URL 中駝峯轉連字符(如 userInfo → user-info)
                })
                // 4. 模板引擎配置(使用 Velocity)
                .templateEngine(new VelocityTemplateEngine())
                // 執行生成
                .execute();
    }
}

8.3 生成代碼後效果

運行 CodeGenerator.main() 方法,會自動生成以下文件:

src/main/java/com/example/mpdemo/
├── entity/
│   ├── User.java
│   └── Order.java
├── mapper/
│   ├── UserMapper.java
│   └── OrderMapper.java
├── service/
│   ├── UserService.java
│   ├── OrderService.java
│   └── impl/
│       ├── UserServiceImpl.java
│       └── OrderServiceImpl.java
├── controller/
│   ├── UserController.java
│   └── OrderController.java
src/main/resources/mapper/
├── UserMapper.xml
└── OrderMapper.xml

生成的代碼已包含 CRUD 接口、註解配置、分頁支持等,可直接用於開發。

九、實戰案例:完整業務流程(用户管理)

結合以上所有特性,實現一個完整的「用户管理」業務流程,包含新增、查詢、更新、刪除、分頁、條件查詢等功能。

9.1 實體類(User.java)

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("mp_user")
public class User {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    @TableField(value = "user_name")
    private String userName;

    private Integer age;

    private String email;

    @TableLogic(value = "0", delval = "1")
    private Integer isDeleted;

    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

9.2 自動填充處理器(處理 createTime 和 updateTime)

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

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    // 插入時填充
    @Override
    public void insertFill(MetaObject metaObject) {
        // 填充 createTime 和 updateTime 為當前時間
        strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }

    // 更新時填充
    @Override
    public void updateFill(MetaObject metaObject) {
        // 填充 updateTime 為當前時間
        strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}

9.3 Mapper 接口(UserMapper.java)

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

@Repository
public interface UserMapper extends BaseMapper<User> {
    // 無需自定義方法,BaseMapper 已滿足需求
}

9.4 Service 接口與實現類

// UserService.java
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.mpdemo.entity.User;
import java.util.List;

public interface UserService extends IService<User> {
    // 條件分頁查詢
    IPage<User> getUserPage(Page<User> page, String userName, Integer minAge, Integer maxAge);
}

// UserServiceImpl.java
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
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;
import java.util.List;

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

    @Override
    public IPage<User> getUserPage(Page<User> page, String userName, Integer minAge, Integer maxAge) {
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 動態條件
        if (userName != null && !userName.isEmpty()) {
            lambdaQueryWrapper.like(User::getUserName, userName);
        }
        if (minAge != null) {
            lambdaQueryWrapper.ge(User::getAge, minAge);
        }
        if (maxAge != null) {
            lambdaQueryWrapper.le(User::getAge, maxAge);
        }
        // 過濾已刪除數據
        lambdaQueryWrapper.eq(User::getIsDeleted, 0);
        // 分頁查詢
        return page(page, lambdaQueryWrapper);
    }
}

9.5 Controller 接口(UserController.java)

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.mpdemo.entity.User;
import com.example.mpdemo.service.UserService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;

@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private UserService userService;

    // 新增用户
    @PostMapping
    public boolean addUser(@RequestBody User user) {
        return userService.save(user);
    }

    // 批量新增用户
    @PostMapping("/batch")
    public boolean addUserBatch(@RequestBody List<User> userList) {
        return userService.saveBatch(userList);
    }

    // 根據 ID 查詢用户
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.getById(id);
    }

    // 條件分頁查詢
    @GetMapping("/page")
    public IPage<User> getUserPage(
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize,
            @RequestParam(required = false) String userName,
            @RequestParam(required = false) Integer minAge,
            @RequestParam(required = false) Integer maxAge) {
        Page<User> page = new Page<>(pageNum, pageSize);
        return userService.getUserPage(page, userName, minAge, maxAge);
    }

    // 根據 ID 更新用户
    @PutMapping
    public boolean updateUser(@RequestBody User user) {
        return userService.updateById(user);
    }

    // 根據 ID 刪除用户(邏輯刪除)
    @DeleteMapping("/{id}")
    public boolean deleteUser(@PathVariable Long id) {
        return userService.removeById(id);
    }
}

9.6 接口測試(Postman 示例)

  1. 新增用户:POST /user,請求體:
{
  "userName": "張三",
  "age": 25,
  "email": "zhangsan@qq.com"
}
  1. 分頁查詢:GET /user/page?pageNum=1&pageSize=10&userName=張&minAge=18
  2. 更新用户:PUT /user,請求體:
{
  "id": 1425678901234567890,
  "userName": "張三三",
  "age": 26
}
  1. 刪除用户:DELETE /user/1425678901234567890

十、最佳實踐與避坑指南

10.1 最佳實踐

  1. 命名規範
  • 數據庫表名、列名使用下劃線命名(如 mp_useruser_name);
  • 實體類名使用 Pascal 命名(如 User),屬性使用駝峯命名(如 userName);
  • Mapper 接口名以 Mapper 結尾(如 UserMapper),Service 接口以 Service 結尾(如 UserService)。
  1. 性能優化
  • 批量操作優先使用 saveBatchupdateBatchById(MP 已優化,比循環單條操作高效);
  • 複雜查詢使用 XML 方式,便於優化 SQL;
  • 分頁查詢避免查詢全表,合理設置 pageSize
  • 啓用 MyBatis 二級緩存(需實體類實現 Serializable),減少重複查詢。
  1. 安全規範
  • 避免直接使用用户輸入作為 SQL 條件(MP 條件構造器已防 SQL 注入,無需手動處理);
  • 生產環境關閉 SQL 打印和性能分析插件;
  • 數據庫密碼使用配置中心存儲(如 Nacos、Apollo),避免硬編碼。
  1. 代碼結構
  • 複雜業務邏輯放在 Service 層,Mapper 層僅負責數據訪問;
  • 自定義 SQL 優先使用 XML 方式,便於維護;
  • 統一異常處理(如樂觀鎖衝突、分頁參數錯誤)。

10.2 常見坑與解決方案

  1. 字段名與列名不匹配
  • 原因:未開啓駝峯命名轉換,或實體字段與數據庫列名差異過大;
  • 解決方案:開啓 map-underscore-to-camel-case: true,或使用 @TableField(value = "列名") 顯式映射。
  1. 分頁插件不生效
  • 原因:未配置分頁插件,或數據庫類型錯誤;
  • 解決方案:在 MyBatisPlusConfig 中註冊 PaginationInnerInterceptor,並指定正確的 DbType
  1. 樂觀鎖更新失敗
  • 原因:版本號字段未加 @Version 註解,或更新時未攜帶版本號;
  • 解決方案:實體類添加 @Version 註解,查詢用户時獲取版本號,更新時傳入。
  1. 邏輯刪除未生效
  • 原因:未配置 logic-delete-field,或字段類型不匹配;
  • 解決方案:在 application.yml 中配置全局邏輯刪除字段,確保實體字段與數據庫列類型一致(如 tinyint)。
  1. 代碼生成器無法生成 XML 文件
  • 原因:未配置 pathInfo 指定 XML 輸出目錄,或目錄不存在;
  • 解決方案:在 packageConfig 中設置 pathInfo(Collections.singletonMap(OutputFile.xml, 目錄路徑)),確保目錄已創建。

十一、總結

MyBatis-Plus 的核心價值是「簡化開發、提高效率」,通過「CRUD 接口+條件構造器+插件機制+代碼生成器」四大核心能力,覆蓋了從簡單查詢到複雜業務的絕大多數場景,同時保持了對 MyBatis 原生的完全兼容。

學習 MP 的關鍵是:

  1. 掌握 Entity 註解與數據庫表的映射關係;
  2. 熟練使用 BaseMapper/IService 接口實現無 SQL 開發;
  3. 靈活運用條件構造器編寫動態 SQL;
  4. 合理配置插件(分頁、樂觀鎖等)解決通用問題;
  5. 用代碼生成器提升開發效率。