Appearance
MyBatis-Plus
导航目录
- 1. MyBatis-Plus 简介
- 2. 快速开始
- 3. 核心功能
- 4. CRUD 接口
- 5. 条件构造器 (Wrapper)
- 6. 分页查询
- 7. 高级特性
- 8. 代码生成器 (Generator)
- 9. 多数据源配置
- 10. 性能优化
- 11. 常见问题
- 12. 最佳实践
- 13. 扩展功能
- 14. Db 静态工具类
1. MyBatis-Plus 简介
1.1 什么是 MyBatis-Plus
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
愿景:我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。
1.2 核心特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅通过少量配置即可实现单表大部分 CRUD 操作
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需担心字段写错
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器)
- 支持 ActiveRecord 模式:实体类只需继承 Model 类即可进行强大的 CRUD 操作
- 内置代码生成器:采用代码或者 Maven 插件可快速生成各层代码
- 内置分页插件:基于 MyBatis 物理分页,支持多种数据库(MySQL, Oracle, PostgreSQL等)
- 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用
- 内置全局拦截插件:提供全表 delete、update 操作智能分析阻断,预防误操作
2. 快速开始
2.1 环境要求
- JDK 8+
- Maven 3.0+
- MyBatis
- Spring Boot 2.x/3.x(可选)
2.2 引入依赖
Maven:
xml
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.6</version>
</dependency>
<!-- 代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.6</version>
</dependency>
<!-- 模板引擎 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
</dependencies>Gradle:
gradle
dependencies {
implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.6'
implementation 'com.baomidou:mybatis-plus-generator:3.5.6'
implementation 'org.apache.velocity:velocity-engine-core:2.3'
}2.3 配置
application.yml:
yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: root
mybatis-plus:
configuration:
# 日志实现
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 驼峰下划线转换
map-underscore-to-camel-case: true
global-config:
db-config:
# 主键类型
id-type: auto
# 逻辑删除字段名
logic-delete-field: deleted
# 逻辑删除值
logic-delete-value: 1
# 逻辑未删除值
logic-not-delete-value: 02.4 启动类配置
java
@SpringBootApplication
@MapperScan("com.example.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}3. 核心功能
3.1 核心注解
MyBatis-Plus 的注解是实体类与数据库表之间的“映射桥梁”,核心解决“实体属性 ↔ 数据库字段”的匹配问题。
3.1.1 @TableName
用于标识实体类对应的数据库表名。
| 属性 | 类型 | 说明 |
|---|---|---|
| value | String | 数据库表名 |
| schema | String | 数据库 schema |
| keepGlobalPrefix | boolean | 是否保持全局表前缀(默认 false) |
3.1.2 @TableId
用于标记主键属性,指定主键生成策略。
| 属性 | 类型 | 说明 |
|---|---|---|
| value | String | 主键字段名 |
| type | IdType | 主键生成策略 |
主键策略 IdType:
| 值 | 描述 | 适用场景 |
|---|---|---|
| AUTO | 数据库 ID 自增 | 依赖数据库自增策略 |
| NONE | 无状态 | 未设置主键类型 |
| INPUT | 手动输入 | 插入前自行 set 主键值 |
| ASSIGN_ID | 雪花算法 | 分布式系统,Long 或 String 类型 |
| ASSIGN_UUID | UUID | 32位无中划线字符串 |
3.1.3 @TableField
用于标记非主键属性,解决属性名与字段名不一致等问题。
| 属性 | 类型 | 说明 |
|---|---|---|
| value | String | 数据库字段名 |
| exist | boolean | 是否为数据库表字段(默认 true) |
| fill | FieldFill | 字段自动填充策略(如插入、更新时自动赋值) |
| select | boolean | 是否参与查询(默认 true) |
3.1.4 实体类示例
java
@Data
@TableName("user") // 指定表名
public class User {
@TableId(type = IdType.AUTO) // 主键策略
private Long id;
@TableField("user_name") // 字段映射
private String name;
private Integer age;
@TableField(fill = FieldFill.INSERT) // 自动填充
private LocalDateTime createTime;
@Version // 乐观锁
private Integer version;
@TableLogic // 逻辑删除
private Integer deleted;
@TableField(exist = false) // 排除非表字段
private String remark;
}3.2 Mapper 接口
java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface UserMapper extends BaseMapper<User> {
// 自定义方法
List<User> selectAllByName(String name);
// 注解方式
@Select("SELECT * FROM user WHERE age > #{age}")
List<User> selectByAge(@Param("age") Integer age);
}3.3 Service 接口
java
import com.baomidou.mybatisplus.extension.service.IService;
public interface UserService extends IService<User> {
// 自定义业务方法
List<User> customMethod(String name);
}java
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public List<User> customMethod(String name) {
// 自定义实现
return baseMapper.selectAllByName(name);
}
}4. CRUD 接口
MyBatis-Plus 提供了通用 Mapper 和通用 Service,极大简化了 CRUD 操作。
4.1 BaseMapper vs IService 对比
| 维度 | BaseMapper | IService |
|---|---|---|
| 定位 | 数据访问层(DAO)接口 | 业务逻辑层(Service)接口 |
| 底层依赖 | 直接对接 MyBatis 生成 SQL | 基于 BaseMapper 封装,提供高级能力 |
| 方法命名 | 贴近 SQL(insert/delete/select) | 贴近业务(save/remove/get) |
| 功能侧重 | 基础单表 CRUD | 补充批量操作、分页增强等 |
4.2 Mapper CRUD
继承 BaseMapper<T> 即可拥有单表 CRUD 能力。
java
@Autowired
private UserMapper userMapper;
// 插入(自动回填ID)
User user = new User();
user.setName("张三");
int result = userMapper.insert(user);
// 根据 ID 删除
userMapper.deleteById(1L);
// 根据 ID 更新(仅更新非 null 字段)
User updateUser = new User();
updateUser.setId(1L);
updateUser.setName("李四");
userMapper.updateById(updateUser);
// 根据 ID 查询
User user = userMapper.selectById(1L);
// 查询列表
List<User> users = userMapper.selectList(new QueryWrapper<User>().gt("age", 18));4.3 Service CRUD
继承 ServiceImpl<M, T> 并实现自定义 Service 接口(继承 IService<T>)。
java
@Autowired
private UserService userService;
// 批量插入(默认批次 1000)
userService.saveBatch(userList);
// 保存或更新(根据 ID 判断)
userService.saveOrUpdate(user);
// 链式查询
List<User> list = userService.lambdaQuery()
.like(User::getName, "张")
.gt(User::getAge, 18)
.list();
// 链式更新
userService.lambdaUpdate()
.set(User::getAge, 20)
.eq(User::getId, 1L)
.update();5. 条件构造器 (Wrapper)
条件构造器是 MyBatis-Plus 的灵魂,用于在 Java 层动态生成 SQL 的 WHERE 条件。
5.1 Wrapper 家族体系
| 类名 | 说明 |
|---|---|
| QueryWrapper | 支持查询(SELECT)和删除(DELETE)条件 |
| UpdateWrapper | 支持更新(UPDATE)条件,可手动设置 set 字段 |
| LambdaQueryWrapper | 推荐。基于 Lambda 表达式,具有类型检查,防止字段名写错 |
| LambdaUpdateWrapper | 推荐。基于 Lambda 的更新构造器 |
5.2 常用方法
| 方法 | 说明 | 示例 |
|---|---|---|
eq | 等于 (=) | eq("name", "张三") |
ne | 不等于 (<>) | ne("age", 20) |
gt / ge | 大于 (>) / 大于等于 (>=) | gt("age", 18) |
lt / le | 小于 (<) / 小于等于 (<=) | lt("age", 30) |
like | 模糊查询 (%值%) | like("name", "张") |
between | 范围查询 (BETWEEN) | between("age", 20, 30) |
isNull | 为空 (IS NULL) | isNull("email") |
in | 集合查询 (IN) | in("id", 1, 2, 3) |
orderByDesc | 倒序排序 | orderByDesc("create_time") |
5.3 动态条件控制 (Condition)
所有 Wrapper 方法都支持第一个参数 boolean condition,用于控制该条件是否拼接。
java
String name = "张三";
Integer age = null;
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.hasText(name), User::getName, name)
.gt(age != null, User::getAge, age);5.4 复杂条件拼接
java
// 嵌套查询:WHERE (name = '张三' OR age > 20) AND status = 1
wrapper.and(i -> i.eq(User::getName, "张三").or().gt(User::getAge, 20))
.eq(User::getStatus, 1);5.5 代码示例
5.5.1 QueryWrapper 示例
java
// 基本查询
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 等于条件
queryWrapper.eq("name", "张三");
// 不等于条件
queryWrapper.ne("age", 20);
// 大于条件
queryWrapper.gt("age", 18);
// 小于等于条件
queryWrapper.le("salary", 5000);
// 模糊查询
queryWrapper.like("name", "张");
// 左模糊
queryWrapper.likeLeft("name", "三");
// 右模糊
queryWrapper.likeRight("name", "张");
// 范围查询
queryWrapper.between("age", 18, 30);
// 空值查询
queryWrapper.isNull("email");
// 非空查询
queryWrapper.isNotNull("phone");
// 集合查询
queryWrapper.in("id", 1, 2, 3, 4, 5);
// 排序
queryWrapper.orderByDesc("create_time").orderByAsc("id");
// 分组
queryWrapper.groupBy("department_id");
// 聚合
queryWrapper.select("department_id", "COUNT(*) as user_count");
List<User> users = userMapper.selectList(queryWrapper);5.5.2 LambdaQueryWrapper 示例(推荐)
java
// 基本查询
LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
// 等于条件
lambdaWrapper.eq(User::getName, "张三");
// 不等于条件
lambdaWrapper.ne(User::getAge, 20);
// 大于条件
lambdaWrapper.gt(User::getAge, 18);
// 小于等于条件
lambdaWrapper.le(User::getSalary, 5000);
// 模糊查询
lambdaWrapper.like(User::getName, "张");
// 范围查询
lambdaWrapper.between(User::getAge, 18, 30);
// 空值查询
lambdaWrapper.isNull(User::getEmail);
// 集合查询
lambdaWrapper.in(User::getId, 1, 2, 3, 4, 5);
// 排序
lambdaWrapper.orderByDesc(User::getCreateTime).orderByAsc(User::getId);
List<User> users = userMapper.selectList(lambdaWrapper);5.5.3 UpdateWrapper 示例
java
// 更新操作
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
// 设置更新字段
updateWrapper.set("name", "李四")
.set("age", 25)
.set("salary", 6000);
// 条件
updateWrapper.eq("id", 1L);
int result = userMapper.update(null, updateWrapper);5.5.4 LambdaUpdateWrapper 示例(推荐)
java
// 更新操作
LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
// 设置更新字段
lambdaUpdateWrapper.set(User::getName, "李四")
.set(User::getAge, 25)
.set(User::getSalary, 6000);
// 条件
lambdaUpdateWrapper.eq(User::getId, 1L);
int result = userMapper.update(null, lambdaUpdateWrapper);5.5.5 动态条件构建
java
// 动态条件构建
String name = "张三";
Integer age = 20;
String email = "";
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 动态添加条件
wrapper.eq(StringUtils.hasText(name), User::getName, name)
.gt(age != null, User::getAge, age)
.isNotNull(StringUtils.hasText(email), User::getEmail);
List<User> users = userMapper.selectList(wrapper);5.5.6 复杂逻辑组合
java
// 复杂逻辑组合:(name = '张三' OR name = '李四') AND (age > 18 AND status = 1)
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.and(i -> i.eq(User::getName, "张三").or().eq(User::getName, "李四"))
.and(i -> i.gt(User::getAge, 18).eq(User::getStatus, 1));
List<User> users = userMapper.selectList(wrapper);5.5.7 实际业务场景示例
场景1:根据条件查询用户列表
java
public List<User> queryUsers(UserQueryDTO queryDTO) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 用户名模糊查询
if (StringUtils.hasText(queryDTO.getUserName())) {
wrapper.like(User::getName, queryDTO.getUserName());
}
// 年龄范围查询
if (queryDTO.getMinAge() != null) {
wrapper.ge(User::getAge, queryDTO.getMinAge());
}
if (queryDTO.getMaxAge() != null) {
wrapper.le(User::getAge, queryDTO.getMaxAge());
}
// 状态查询
if (queryDTO.getStatus() != null) {
wrapper.eq(User::getStatus, queryDTO.getStatus());
}
// 部门查询
if (queryDTO.getDepartmentId() != null) {
wrapper.eq(User::getDepartmentId, queryDTO.getDepartmentId());
}
// 排序
wrapper.orderByDesc(User::getCreateTime);
return userMapper.selectList(wrapper);
}场景2:批量更新用户状态
java
public boolean updateUserStatus(List<Long> userIds, Integer status) {
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.set(User::getStatus, status)
.in(User::getId, userIds);
return userMapper.update(null, wrapper) > 0;
}场景3:根据条件删除用户
java
public boolean deleteUsers(DeleteUserDTO deleteDTO) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 按状态删除
if (deleteDTO.getStatus() != null) {
wrapper.eq(User::getStatus, deleteDTO.getStatus());
}
// 按创建时间删除
if (deleteDTO.getCreateTimeBefore() != null) {
wrapper.lt(User::getCreateTime, deleteDTO.getCreateTimeBefore());
}
return userMapper.delete(wrapper) > 0;
}6. 分页查询
6.1 配置分页插件
java
@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 分页查询使用
java
// 方式一:使用Page对象
Page<User> page = new Page<>(1, 10); // 当前页,每页大小
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.gt("age", 18);
Page<User> result = userMapper.selectPage(page, wrapper);
// 获取分页信息
System.out.println("总记录数:" + result.getTotal());
System.out.println("总页数:" + result.getPages());
System.out.println("当前页:" + result.getCurrent());
System.out.println("每页大小:" + result.getSize());
List<User> records = result.getRecords();
// 方式二:使用Service
Page<User> page = new Page<>(1, 10);
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.gt("age", 18);
Page<User> result = userService.page(page, wrapper);
// 不查询总记录数(性能优化)
Page<User> page = new Page<>(1, 10, false);7. 高级功能
7.1 自动填充
java
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}7.2 乐观锁
java
// 实体类中添加版本字段
public class User {
@Version
private Integer version;
}
// 使用乐观锁
User user = userMapper.selectById(1L);
user.setName("新名字");
user.setEmail("new@example.com");
// 此时version会自动+1
userMapper.updateById(user);7.3 逻辑删除
概念对比:
- 物理删除:直接从数据库执行
DELETE操作,数据彻底丢失,难以恢复。 - 逻辑删除:执行
UPDATE操作将状态位设为“已删除”,数据依然存在,但在查询时会自动过滤。
实现步骤:
- 数据库字段:增加一个标识字段(如
deleted,默认值0)。 - 实体类属性:在对应属性上添加
@TableLogic注解。 - 配置文件(可选):自定义全局逻辑删除的值。
yaml
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除字段名
logic-delete-value: 1 # 删除状态
logic-not-delete-value: 0 # 未删除状态效果:
- 执行
deleteById时,底层变为UPDATE user SET deleted=1 WHERE id=? AND deleted=0。 - 执行
selectList时,底层自动加上WHERE deleted=0。
7.4 枚举处理
java
// 定义枚举
public enum GenderEnum {
MALE(1, "男"),
FEMALE(2, "女");
private final Integer code;
private final String desc;
GenderEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
// getter...
}
// 实体类中使用枚举
public class User {
@EnumValue // 标记数据库存储的值
private GenderEnum gender;
}
// 配置枚举处理器
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// ... 其他配置
return interceptor;
}
@Bean
public MybatisConfiguration mybatisConfiguration() {
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setDefaultEnumTypeHandler(MybatisEnumTypeHandler.class);
return configuration;
}
}8. 代码生成器
8.1 快速代码生成
java
public class CodeGenerator {
public static void main(String[] args) {
// 代码生成器
AutoGenerator generator = new AutoGenerator();
// 全局配置
GlobalConfig globalConfig = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
globalConfig.setOutputDir(projectPath + "/src/main/java");
globalConfig.setAuthor("Your Name");
globalConfig.setOpen(false);
globalConfig.setSwagger2(true); // 实体属性 Swagger2 注解
generator.setGlobalConfig(globalConfig);
// 数据源配置
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/mybatis_plus");
dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("root");
generator.setDataSource(dataSourceConfig);
// 包配置
PackageConfig packageConfig = new PackageConfig();
packageConfig.setModuleName("system");
packageConfig.setParent("com.example");
packageConfig.setEntity("entity");
packageConfig.setMapper("mapper");
packageConfig.setService("service");
packageConfig.setServiceImpl("service.impl");
packageConfig.setController("controller");
generator.setPackageInfo(packageConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setInclude("user", "role"); // 表名
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix("t_"); // 表前缀
generator.setStrategy(strategy);
// 执行生成
generator.execute();
}
}8.2 自定义模板
java
// 自定义模板配置
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setEntity("templates/entity.java");
templateConfig.setMapper("templates/mapper.java");
templateConfig.setService("templates/service.java");
templateConfig.setServiceImpl("templates/serviceImpl.java");
templateConfig.setController("templates/controller.java");
generator.setTemplate(templateConfig);9. 多数据源配置
9.1 依赖引入
xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.6.1</version>
</dependency>9.2 配置多数据源
yaml
spring:
datasource:
dynamic:
primary: master # 设置默认的数据源或者数据源组
strict: false
datasource:
master:
url: jdbc:mysql://localhost:3306/master_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://localhost:3306/slave_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver9.3 使用注解切换数据源
java
@Service
public class UserService {
@DS("master") // 使用主数据源
public void addUser(User user) {
userMapper.insert(user);
}
@DS("slave") // 使用从数据源
public User getUserById(Long id) {
return userMapper.selectById(id);
}
}10. 性能优化
10.1 SQL 性能分析插件
java
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// SQL分析插件
interceptor.addInnerInterceptor(new IllegalSQLInnerInterceptor());
return interceptor;
}10.2 分页优化
java
// 使用不查询总记录数的分页(大数据量时)
Page<User> page = new Page<>(1, 10, false);
// 自定义分页SQL(复杂查询时)
@Select("SELECT * FROM user ${ew.customSqlSegment}")
List<User> selectPageCustom(@Param(Constants.WRAPPER) Wrapper<User> wrapper, Page<User> page);10.3 查询优化
java
// 只查询需要的字段
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("id", "name", "email");
// 使用Lambda避免字段名硬编码
LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
lambdaWrapper.select(User::getId, User::getName, User::getEmail);11. 常见问题
11.1 字段映射问题
java
// 解决数据库字段名与实体类属性名不一致
public class User {
@TableField("user_name") // 数据库字段名
private String name;
// 忽略非表字段
@TableField(exist = false)
private String tempField;
}11.2 主键策略问题
java
public class User {
// 雪花算法ID
@TableId(type = IdType.ASSIGN_ID)
private Long id;
// 数据库自增
@TableId(type = IdType.AUTO)
private Long id;
}11.3 批量操作性能
java
// 批量插入
List<User> userList = new ArrayList<>();
// ... 添加数据
boolean success = userService.saveBatch(userList, 1000); // 每批1000条
// 批量更新
boolean success = userService.updateBatchById(userList, 1000);12. 最佳实践
12.1 项目结构
src/main/java/com/example/
├── entity/ # 实体类
├── mapper/ # Mapper接口
├── service/ # Service接口
│ └── impl/ # Service实现
├── controller/ # 控制器
└── config/ # 配置类12.2 统一返回结果
java
@Data
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
return result;
}
}12.3 全局异常处理
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result<String> handleException(Exception e) {
return Result.error(500, e.getMessage());
}
}13. 扩展功能
13.1 自定义注入方法
java
public interface MyBaseMapper<T> extends BaseMapper<T> {
int alwaysDeleteSome();
int deleteAll();
}
public class MySqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
List<AbstractMethod> methodList = super.getMethodList(mapperClass);
methodList.add(new DeleteAll());
methodList.add(new AlwaysDeleteSome());
return methodList;
}
}13.2 多租户支持
java
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 多租户插件
TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor();
tenantInterceptor.setTenantLineHandler(new TenantLineHandler() {
@Override
public Expression getTenantId() {
// 从上下文中获取租户ID
return new LongValue(1L);
}
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public boolean ignoreTable(String tableName) {
// 忽略不需要租户隔离的表
return "tenant".equals(tableName);
}
});
interceptor.addInnerInterceptor(tenantInterceptor);
return interceptor;
}14. Db 静态工具类 (MyBatis-Plus 3.5.3+)
Db 类是 MyBatis-Plus 提供的一个静态方法工具类,旨在简化 Service 间的循环依赖问题,让开发者在非 Service 环境(如拦截器、异步任务)中也能方便地调用 CRUD。
14.1 核心优势
- 无循环依赖:无需在 Service 中注入其他 Service,直接通过
Db调用,有效解决“你依赖我,我依赖你”的死锁问题。 - 静态调用:直接
Db.getById(id, User.class),代码更加整洁。 - 性能一致:底层依然调用
ServiceImpl,性能与普通注入无异。
14.2 常用方法
java
// 根据 ID 查询
User user = Db.getById(1L, User.class);
// 根据条件查询列表
List<User> users = Db.lambdaQuery(User.class)
.like(User::getUsername, "o")
.ge(User::getBalance, 1000)
.list();
// 批量插入
List<User> userList = Arrays.asList(new User(), new User());
Db.saveBatch(userList);
// 更新数据
Db.lambdaUpdate(User.class)
.set(User::getBalance, 2000)
.eq(User::getUsername, "Rose")
.update();3. 实战案例:查询用户及收货地址
3.1 需求描述
改造根据 id 查询用户的接口,在查询用户的同时返回用户的收货地址列表。
3.2 实现步骤
步骤 1:导入 AddressVO
将 AddressVO.java 复制到 com.itheima.mp.domain.vo 包下:
java
package com.itheima.mp.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "收货地址VO")
public class AddressVO {
@ApiModelProperty("地址id")
private Long id;
@ApiModelProperty("用户id")
private Long userId;
@ApiModelProperty("省")
private String province;
@ApiModelProperty("市")
private String city;
@ApiModelProperty("区/县")
private String town;
@ApiModelProperty("详细地址")
private String street;
@ApiModelProperty("联系人")
private String contact;
@ApiModelProperty("手机号")
private String mobile;
@ApiModelProperty("是否是默认地址")
private Boolean isDefault;
}步骤 2:改造 UserVO
在 UserVO 中添加地址列表属性:
java
package com.itheima.mp.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel(description = "用户VO")
public class UserVO {
@ApiModelProperty("用户id")
private Long id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("详细信息")
private String info;
@ApiModelProperty("使用状态(1正常 2冻结)")
private Integer status;
@ApiModelProperty("账户余额")
private Integer balance;
@ApiModelProperty("收货地址列表")
private List<AddressVO> addresses;
}步骤 3:修改 UserController
java
@RestController
@RequestMapping("/users")
@Api(tags = "用户管理接口")
@RequiredArgsConstructor
public class UserController {
private final IUserService userService;
/**
* 根据用户id查询用户
* @param id 用户id
*/
@ApiOperation("根据用户id查询用户")
@GetMapping("/{id}")
public UserVO queryById(@PathVariable("id") Long id) {
return userService.queryUserAndAddressById(id);
}
}步骤 4:新增 IUserService 接口方法
java
public interface IUserService extends IService<User> {
/**
* 根据用户id查询用户和地址
* @param id 用户id
* @return userVO
*/
UserVO queryUserAndAddressById(Long id);
}步骤 5:实现 UserServiceImpl
使用 Db 工具类查询收货地址,避免循环依赖:
java
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public UserVO queryUserAndAddressById(Long id) {
// 查询用户
User user = getById(id);
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
// 使用Db工具类查询用户的收货地址列表,避免循环依赖
List<Address> addressList = Db.lambdaQuery(Address.class)
.eq(Address::getUserId, id)
.list();
userVO.setAddresses(BeanUtil.copyToList(addressList, AddressVO.class));
return userVO;
}
}4. 扩展练习:批量查询用户及地址
需求:改造根据用户 id 批量查询用户并返回用户的收货地址列表
java
@Override
public List<UserVO> queryUserAndAddressByIds(List<Long> ids) {
// 1. 查询用户列表
List<User> userList = this.listByIds(ids);
List<UserVO> userVOS = BeanUtil.copyToList(userList, UserVO.class);
// 2. 使用Db工具类查询地址列表,并按用户id分组
List<Address> addressList = Db.lambdaQuery(Address.class)
.in(Address::getUserId, ids)
.list();
Map<Long, List<AddressVO>> addressVoMap = BeanUtil.copyToList(addressList, AddressVO.class)
.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
// 3. 将地址列表设置到对应的用户VO中
for (UserVO userVO : userVOS) {
userVO.setAddresses(addressVoMap.get(userVO.getId()));
}
return userVOS;
}5. 注意事项
- Mapper 要求:使用
Db工具类时,必须有对应实体类的 Mapper - 事务管理:
Db操作默认在事务中执行 - 性能考虑:对于复杂查询,建议使用自定义 Mapper 方法
- 代码简洁性:简单的单表操作使用
Db,复杂业务逻辑使用 Service