MyBatis-Plus 概述

什么是 MyBatis-Plus

MyBatis-Plus简称 MP是一个 MyBatis 的增强工具在 MyBatis 的基础上只做增强不做改变为简化开发提高效率而生

  • 核心特性
    • 无侵入只做增强不做改变
    • 损耗小启动即会自动注入基本 CURD性能基本无损耗
    • 强大的 CRUD 操作内置通用 Mapper通用 Service
    • 支持 Lambda 形式调用通过 Lambda 表达式方便的编写各类查询条件
    • 支持主键自动生成支持多达 4 种主键策略
    • 支持 ActiveRecord 模式支持 ActiveRecord 形式调用
    • 支持自定义全局通用操作支持全局通用方法注入
    • 内置代码生成器采用代码或者 Maven 插件可快速生成 MapperModelServiceController 层代码
    • 内置分页插件基于 MyBatis 物理分页开发者无需关心具体操作
    • 分页插件支持多种数据库支持 MySQLMariaDBOracleDB2H2HSQLSQLitePostgreSQLServer 等多种数据库
    • 内置性能分析插件可输出 SQL 语句以及其执行时间建议开发测试时启用该功能能快速揪出慢查询
    • 内置全局拦截插件提供全表 deleteupdate 操作智能分析阻断也可自定义拦截规则预防误操作

MyBatis-Plus vs MyBatis

特性 MyBatis MyBatis-Plus
CRUD 操作 需要手动编写 内置通用 Mapper
分页功能 需要第三方插件 内置分页插件
代码生成 需要第三方工具 内置代码生成器
条件构造 需要手动拼接 SQL 支持 Wrapper 条件构造器
学习成本 较低 基于 MyBatis
灵活性 完全兼容 MyBatis
1
2
3
4
细节注意
1. MyBatis-Plus 完全兼容 MyBatis可以无缝迁移
2. 可以选择性使用 MP 的功能不必全部使用
3. 对于复杂查询仍然可以手写 SQL

MyBatis-Plus 的核心组件

BaseMapper

  • 提供基础的 CRUD 操作
  • 包含 17 个常用方法
  • 支持泛型类型安全

IService

  • Service 层的通用接口
  • 封装了常用的业务逻辑
  • 支持批量操作

Wrapper

  • 条件构造器
  • 支持 Lambda 表达式
  • 链式调用简洁优雅

AutoGenerator

  • 代码生成器
  • 支持自定义模板
  • 一键生成多层代码

环境搭建

Spring Boot 整合

添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>

<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>

<!-- Lombok可选简化实体类 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/mp_demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
# Mapper XML 文件位置
mapper-locations: classpath:mapper/*.xml
# 类型别名包
type-aliases-package: com.example.entity
configuration:
# 开启驼峰命名
map-underscore-to-camel-case: true
# 日志实现
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 主键策略
id-type: auto
# 逻辑删除字段
logic-delete-field: deleted
# 逻辑删除值
logic-delete-value: 1
# 逻辑未删除值
logic-not-delete-value: 0

创建数据库表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- 创建用户表
CREATE TABLE `user` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
`name` VARCHAR(30) COMMENT '姓名',
`age` INT COMMENT '年龄',
`email` VARCHAR(50) COMMENT '邮箱',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` TINYINT DEFAULT 0 COMMENT '逻辑删除(0:未删除 1:已删除)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

-- 插入测试数据
INSERT INTO `user` (`name`, `age`, `email`) VALUES
('张三', 18, 'zhangsan@example.com'),
('李四', 20, 'lisi@example.com'),
('王五', 28, 'wangwu@example.com'),
('赵六', 21, 'zhaoliu@example.com'),
('孙七', 24, 'sunqi@example.com');

创建实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.example.entity;

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

@Data
@TableName("user") // 指定表名
public class User {

@TableId(type = IdType.AUTO) // 主键策略
private Long id;

private String name;

private Integer age;

private String email;

@TableField(fill = FieldFill.INSERT) // 插入时自动填充
private LocalDateTime createTime;

@TableField(fill = FieldFill.INSERT_UPDATE) // 插入和更新时自动填充
private LocalDateTime updateTime;

@TableLogic // 逻辑删除
private Integer deleted;
}

常用注解说明

注解 说明 示例
@TableName 指定表名 @TableName("sys_user")
@TableId 主键注解 @TableId(type = IdType.AUTO)
@TableField 字段注解 @TableField("user_name")
@TableLogic 逻辑删除 @TableLogic
@Version 乐观锁 @Version

创建 Mapper 接口

1
2
3
4
5
6
7
8
9
10
11
package com.example.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
// 继承 BaseMapper 后自动拥有 CRUD 方法
// 可以在此添加自定义方法
}

配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.example.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();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

CRUD 操作

插入操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Autowired
private UserMapper userMapper;

// 插入一条记录
@Test
public void testInsert() {
User user = new User();
user.setName("张三");
user.setAge(18);
user.setEmail("zhangsan@example.com");

int result = userMapper.insert(user);
System.out.println("影响行数" + result);
System.out.println("插入的ID" + user.getId()); // 自动回填ID
}

// 批量插入
@Test
public void testBatchInsert() {
List<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
User user = new User();
user.setName("用户" + i);
user.setAge(20 + i);
user.setEmail("user" + i + "@example.com");
users.add(user);
}

// 方式一循环插入
users.forEach(userMapper::insert);

// 方式二使用 IService推荐
userService.saveBatch(users);
}

删除操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 根据 ID 删除
@Test
public void testDeleteById() {
int result = userMapper.deleteById(1L);
System.out.println("影响行数" + result);
}

// 根据条件删除
@Test
public void testDeleteByMap() {
Map<String, Object> columnMap = new HashMap<>();
columnMap.put("name", "张三");
columnMap.put("age", 18);

int result = userMapper.deleteByMap(columnMap);
System.out.println("影响行数" + result);
}

// 根据 Wrapper 条件删除
@Test
public void testDeleteByWrapper() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getAge, 18);

int result = userMapper.delete(wrapper);
System.out.println("影响行数" + result);
}

// 批量删除
@Test
public void testDeleteBatchIds() {
List<Long> ids = Arrays.asList(1L, 2L, 3L);
int result = userMapper.deleteBatchIds(ids);
System.out.println("影响行数" + result);
}

更新操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 根据 ID 更新
@Test
public void testUpdateById() {
User user = new User();
user.setId(1L);
user.setName("张三丰");
user.setAge(100);

int result = userMapper.updateById(user);
System.out.println("影响行数" + result);
}

// 根据条件更新
@Test
public void testUpdateByWrapper() {
User user = new User();
user.setName("李四丰");
user.setAge(25);

LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getId, 2L);

int result = userMapper.update(user, wrapper);
System.out.println("影响行数" + result);
}

// 选择性更新只更新非 null 字段
@Test
public void testUpdateSelective() {
User user = new User();
user.setId(1L);
user.setAge(20); // 只更新 age 字段

int result = userMapper.updateById(user);
System.out.println("影响行数" + result);
}

查询操作

基本查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 根据 ID 查询
@Test
public void testSelectById() {
User user = userMapper.selectById(1L);
System.out.println(user);
}

// 根据多个 ID 查询
@Test
public void testSelectBatchIds() {
List<Long> ids = Arrays.asList(1L, 2L, 3L);
List<User> users = userMapper.selectBatchIds(ids);
users.forEach(System.out::println);
}

// 根据条件查询
@Test
public void testSelectByMap() {
Map<String, Object> columnMap = new HashMap<>();
columnMap.put("name", "张三");

List<User> users = userMapper.selectByMap(columnMap);
users.forEach(System.out::println);
}

// 查询所有
@Test
public void testSelectList() {
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}

条件查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// QueryWrapper 条件构造器
@Test
public void testQueryWrapper() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("name", "张") // name LIKE '%张%'
.ge("age", 18) // age >= 18
.orderByDesc("age"); // ORDER BY age DESC

List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}

// LambdaQueryWrapper推荐类型安全
@Test
public void testLambdaQueryWrapper() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getName, "张")
.ge(User::getAge, 18)
.orderByDesc(User::getAge);

List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}

// 复杂条件
@Test
public void testComplexQuery() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.and(w -> w.like(User::getName, "张")
.or()
.like(User::getName, "李"))
.between(User::getAge, 18, 30)
.isNotNull(User::getEmail)
.orderByDesc(User::getAge)
.last("LIMIT 10"); // 原生 SQL

List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}

分页查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Autowired
private UserService userService;

// 简单分页
@Test
public void testPage() {
Page<User> page = new Page<>(1, 10); // 第1页每页10条
IPage<User> userPage = userService.page(page);

System.out.println("总记录数" + userPage.getTotal());
System.out.println("总页数" + userPage.getPages());
System.out.println("当前页" + userPage.getCurrent());
System.out.println("每页大小" + userPage.getSize());
System.out.println("数据列表" + userPage.getRecords());
}

// 带条件的分页查询
@Test
public void testPageWithCondition() {
Page<User> page = new Page<>(1, 10);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getName, "张");

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

// 自定义分页查询
@Test
public void testCustomPage() {
Page<User> page = new Page<>(1, 10);
IPage<User> userPage = userMapper.selectUserPage(page);
userPage.getRecords().forEach(System.out::println);
}
1
2
3
4
<!-- Mapper XML 中的分页查询 -->
<select id="selectUserPage" resultType="User">
SELECT * FROM user WHERE age > #{age}
</select>

条件构造器

QueryWrapper

用于查询条件构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 基本用法
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "张三") // =
.ne("age", 18) // !=
.gt("age", 18) // >
.ge("age", 18) // >=
.lt("age", 30) // <
.le("age", 30) // <=
.like("name", "张") // LIKE '%张%'
.notLike("name", "李") // NOT LIKE '%李%'
.likeLeft("name", "三") // LIKE '%三'
.likeRight("name", "张") // LIKE '张%'
.isNull("email") // IS NULL
.isNotNull("email") // IS NOT NULL
.in("age", 18, 20, 25) // IN (18, 20, 25)
.notIn("age", 18, 20) // NOT IN (18, 20)
.between("age", 18, 30) // BETWEEN 18 AND 30
.notBetween("age", 20, 25) // NOT BETWEEN 20 AND 25
.orderByAsc("age") // ORDER BY age ASC
.orderByDesc("age") // ORDER BY age DESC
.groupBy("age") // GROUP BY age
.having("COUNT(*) > 1"); // HAVING COUNT(*) > 1

LambdaQueryWrapper

类型安全的查询条件构造推荐

1
2
3
4
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getName, "张三")
.ge(User::getAge, 18)
.orderByDesc(User::getAge);

UpdateWrapper

用于更新条件构造

1
2
3
4
5
6
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.eq("name", "张三")
.set("age", 20)
.set("email", "new@example.com");

userMapper.update(null, wrapper);

LambdaUpdateWrapper

类型安全的更新条件构造

1
2
3
4
5
6
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getName, "张三")
.set(User::getAge, 20)
.set(User::getEmail, "new@example.com");

userMapper.update(null, wrapper);

常用条件方法

方法 说明 SQL 等价
eq 等于 =
ne 不等于 <>
gt 大于 >
ge 大于等于 >=
lt 小于 <
le 小于等于 <=
like 模糊查询 LIKE '%value%'
in IN 查询 IN (v1, v2, ...)
between 范围查询 BETWEEN v1 AND v2
orderByDesc 降序排序 ORDER BY ... DESC

Service 层封装

IService 接口

MyBatis-Plus 提供了 IService 接口封装了常用的业务逻辑

创建 Service 接口

1
2
3
4
5
6
7
8
package com.example.service;

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

public interface UserService extends IService<User> {
// 可以在此添加自定义方法
}

创建 Service 实现类

1
2
3
4
5
6
7
8
9
10
11
12
package com.example.service.impl;

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

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
// 继承 ServiceImpl 后自动拥有 IService 的所有方法
}

常用方法

保存操作

1
2
3
4
5
6
7
8
9
// 插入一条记录
boolean save(User entity);

// 批量插入
boolean saveBatch(Collection<User> entityList);
boolean saveBatch(Collection<User> entityList, int batchSize);

// 插入或更新
boolean saveOrUpdate(User entity);

删除操作

1
2
3
4
5
6
7
8
// 根据 ID 删除
boolean removeById(Serializable id);

// 根据条件删除
boolean remove(Wrapper<User> queryWrapper);

// 批量删除
boolean removeByIds(Collection<? extends Serializable> idList);

更新操作

1
2
3
4
5
6
7
8
9
// 根据 ID 更新
boolean updateById(User entity);

// 根据条件更新
boolean update(User entity, Wrapper<User> updateWrapper);

// 批量更新
boolean updateBatchById(Collection<User> entityList);
boolean updateBatchById(Collection<User> entityList, int batchSize);

查询操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 根据 ID 查询
User getById(Serializable id);

// 根据条件查询列表
List<User> list(Wrapper<User> queryWrapper);

// 查询所有
List<User> list();

// 根据条件查询一个
User getOne(Wrapper<User> queryWrapper);

// 统计总数
long count();
long count(Wrapper<User> queryWrapper);

// 分页查询
IPage<User> page(IPage<User> page, Wrapper<User> queryWrapper);

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@Autowired
private UserService userService;

// 保存
@Test
public void testSave() {
User user = new User();
user.setName("张三");
user.setAge(18);
user.setEmail("zhangsan@example.com");

boolean result = userService.save(user);
System.out.println("保存结果" + result);
}

// 批量保存
@Test
public void testSaveBatch() {
List<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
User user = new User();
user.setName("用户" + i);
user.setAge(20 + i);
users.add(user);
}

boolean result = userService.saveBatch(users);
System.out.println("批量保存结果" + result);
}

// 条件查询
@Test
public void testList() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getName, "张")
.ge(User::getAge, 18);

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

// 分页查询
@Test
public void testPage() {
Page<User> page = new Page<>(1, 10);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.ge(User::getAge, 18);

IPage<User> userPage = userService.page(page, wrapper);
System.out.println("总记录数" + userPage.getTotal());
userPage.getRecords().forEach(System.out::println);
}

代码生成器

添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.5</version>
</dependency>

<!-- 模板引擎 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>

生成代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.example.generator;

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) {
FastAutoGenerator.create(
"jdbc:mysql://localhost:3306/mp_demo?useSSL=false&serverTimezone=Asia/Shanghai",
"root",
"123456"
)
// 全局配置
.globalConfig(builder -> {
builder.author("Your Name") // 作者
.outputDir("D:/project/src/main/java") // 输出目录
.commentDate("yyyy-MM-dd") // 注释日期格式
.disableOpenDir(); // 禁止打开输出目录
})
// 包配置
.packageConfig(builder -> {
builder.parent("com.example") // 父包名
.moduleName("system") // 模块名
.entity("entity") // Entity 包名
.mapper("mapper") // Mapper 包名
.service("service") // Service 包名
.serviceImpl("service.impl") // Service Impl 包名
.controller("controller") // Controller 包名
.pathInfo(Collections.singletonMap(
OutputFile.xml,
"D:/project/src/main/resources/mapper"
)); // XML 输出目录
})
// 策略配置
.strategyConfig(builder -> {
builder.addInclude("user", "role", "permission") // 需要生成的表
.addTablePrefix("t_", "sys_") // 表前缀
.entityBuilder()
.enableLombok() // 启用 Lombok
.enableTableFieldAnnotation() // 启用字段注解
.logicDeleteColumnName("deleted") // 逻辑删除字段
.controllerBuilder()
.enableRestStyle() // 启用 REST 风格
.serviceBuilder()
.formatServiceFileName("%sService") // Service 文件名格式
.formatServiceImplFileName("%sServiceImpl"); // Service Impl 文件名格式
})
// 模板引擎
.templateEngine(new VelocityTemplateEngine())
// 执行
.execute();
}
}

生成的代码结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
src/main/java/com/example/system/
├── controller/
│ └── UserController.java
├── entity/
│ └── User.java
├── mapper/
│ └── UserMapper.java
└── service/
├── UserService.java
└── impl/
└── UserServiceImpl.java

src/main/resources/mapper/
└── UserMapper.xml

高级功能

逻辑删除

配置

1
2
3
4
5
6
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0

实体类

1
2
@TableLogic
private Integer deleted;

使用

1
2
3
4
5
6
// 删除操作会转换为更新操作
// DELETE FROM user WHERE id = 1
// 实际执行UPDATE user SET deleted = 1 WHERE id = 1

// 查询操作会自动添加 deleted = 0 条件
// SELECT * FROM user WHERE deleted = 0

乐观锁

数据库添加版本字段

1
ALTER TABLE `user` ADD COLUMN `version` INT DEFAULT 1 COMMENT '版本号';

实体类

1
2
@Version
private Integer version;

配置插件

1
2
3
4
5
6
7
8
9
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}

使用

1
2
3
4
5
6
7
8
9
10
// 1. 查询数据
User user = userMapper.selectById(1L);

// 2. 修改数据
user.setName("新名字");

// 3. 更新时会带上 version 条件
// UPDATE user SET name = '新名字', version = version + 1
// WHERE id = 1 AND version = 1
userMapper.updateById(user);

自动填充

创建元数据处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.example.handler;

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

import java.time.LocalDateTime;

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

@Override
public void insertFill(MetaObject metaObject) {
log.info("开始插入填充...");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}

@Override
public void updateFill(MetaObject metaObject) {
log.info("开始更新填充...");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}

实体类

1
2
3
4
5
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;

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

枚举处理

创建枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.example.enums;

import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;

@Getter
public enum GenderEnum {

MALE(1, "男"),
FEMALE(2, "女"),
UNKNOWN(0, "未知");

@EnumValue // 标记数据库存的值
private final int code;
private final String desc;

GenderEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
}

实体类

1
private GenderEnum gender;

配置

1
2
mybatis-plus:
type-enums-package: com.example.enums

SQL 注入器

自定义全局操作方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 自定义方法
public class FindAll extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
String sql = "SELECT * FROM " + tableInfo.getTableName();
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
return this.addSelectMappedStatementForTable(mapperClass, "findAll", sqlSource, tableInfo);
}
}

// 注册到容器
@Bean
public ISqlInjector sqlInjector() {
return new DefaultSqlInjector() {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
methodList.add(new FindAll());
return methodList;
}
};
}

性能优化

分页优化

1
2
3
4
// 使用 CountOptimizer 优化 COUNT 查询
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL) {{
setOptimizeJoin(true); // 优化 JOIN 查询
}});

批量操作优化

1
2
3
4
5
// 使用 saveBatch 代替循环插入
userService.saveBatch(users, 1000); // 每批1000条

// 使用 updateBatchById 代替循环更新
userService.updateBatchById(users, 1000);

缓存优化

1
2
3
4
5
6
7
8
9
<!-- 开启二级缓存 -->
<cache/>

<!-- 或者使用 Redis 缓存 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.5.5</version>
</dependency>

SQL 性能分析

1
2
3
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印 SQL
1
2
3
4
5
6
7
8
9
// 开发环境启用性能分析插件
@Bean
@Profile({"dev", "test"}) // 只在开发和测试环境启用
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor interceptor = new PerformanceInterceptor();
interceptor.setMaxTime(100); // SQL 最大执行时间毫秒
interceptor.setFormat(true); // 格式化 SQL
return interceptor;
}

最佳实践

命名规范

1
2
3
4
5
6
7
8
9
10
11
Mapper 接口XxxMapper
Service 接口XxxService
Service 实现XxxServiceImpl
ControllerXxxController
EntityXxx

方法命名
- 查询getXxxlistXxxpageXxx
- 插入saveXxxaddXxx
- 更新updateXxxmodifyXxx
- 删除removeXxxdeleteXxx

代码规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 1. 优先使用 LambdaQueryWrapper类型安全
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getName, "张三");

// 2. 避免使用 select *
wrapper.select(User::getId, User::getName, User::getAge);

// 3. 合理使用分页
Page<User> page = new Page<>(1, 10);
IPage<User> userPage = userService.page(page, wrapper);

// 4. 事务管理
@Transactional
public void updateUser(User user) {
userService.updateById(user);
}

// 5. 异常处理
try {
userService.save(user);
} catch (Exception e) {
log.error("保存用户失败", e);
throw new BusinessException("保存用户失败");
}

性能建议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1. 避免 N+1 查询问题
// 错误做法
List<User> users = userService.list();
users.forEach(user -> {
Order order = orderService.getByUserId(user.getId()); // N 次查询
});

// 正确做法
List<User> users = userService.list();
List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());
List<Order> orders = orderService.listByUserIds(userIds); // 1 次查询

// 2. 合理使用索引
// 确保查询字段有索引

// 3. 批量操作
userService.saveBatch(users, 1000);

// 4. 避免大事务
@Transactional
public void processOrder(Order order) {
// 只包含必要的数据库操作
orderService.save(order);
// 耗时操作放在事务外
}

常见问题

主键策略

策略 说明 适用场景
AUTO 数据库自增 MySQLSQL Server
INPUT 用户输入 需要手动设置 ID
ASSIGN_ID 雪花算法 分布式系统默认
ASSIGN_UUID UUID 需要唯一标识

字段映射问题

1
2
3
4
5
6
7
8
9
// 问题数据库字段与 Java 属性名不一致
// 解决方案一使用 @TableField 注解
@TableField("user_name")
private String userName;

// 解决方案二开启驼峰命名
mybatis-plus:
configuration:
map-underscore-to-camel-case: true

分页不生效

1
2
3
4
5
6
7
// 确保配置了分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}

报错处理

💗💗 MyBatis-Plus 报错Invalid bound statement

1
2
3
4
5
6
7
8
9
10
11
错误信息
Invalid bound statement (not found): com.example.mapper.UserMapper.selectList

错误原因
1. Mapper XML 文件未找到
2. namespace 配置错误

解决方案
1. 检查 Mapper XML 文件是否在 classpath 下
2. 确认 mybatis-plus.mapper-locations 配置正确
3. 如果使用 BaseMapper不需要 XML 文件

💗💗 MyBatis-Plus 报错Table ‘xxx’ doesn’t exist

1
2
3
4
5
6
7
8
9
10
11
错误信息
Table 'mp_demo.user' doesn't exist

错误原因
1. 表名不正确
2. 数据库连接错误

解决方案
1. 检查 @TableName 注解是否正确
2. 确认数据库连接配置
3. 检查表是否存在

💗💗 MyBatis-Plus 报错Failed to process

1
2
3
4
5
6
7
8
9
10
11
错误信息
Failed to process, please exclude the tableName or statementId

错误原因
1. SQL 语法错误
2. 动态 SQL 解析失败

解决方案
1. 检查 Wrapper 条件是否正确
2. 查看完整 SQL 语句
3. 启用日志查看详细错误

学习资源

  • 视频
    • MyBatisPlus 教程https://www.bilibili.com/video/BV12R4y157Be
  • 官方文档
    • MyBatis-Plus 官方文档https://baomidou.com/
    • MyBatis-Plus GitHubhttps://github.com/baomidou/mybatis-plus
  • 书籍
    • MyBatis-Plus 从入门到精通社区编著
  • 教程
    • MyBatis-Plus 入门教程https://baomidou.com/pages/24112f/
    • Baeldung MyBatis-Plus 教程https://www.baeldung.com/mybatis-plus
  • 工具
    • MyBatis-Plus Generator代码生成器
    • MyBatisXIDEA 插件
  • 社区
    • MyBatis-Plus Giteehttps://gitee.com/baomidou/mybatis-plus
    • Stack Overflow MyBatis-Plus 标签https://stackoverflow.com/questions/tagged/mybatis-plus