【RuoYi-SpringBoot3-Pro】:MyBatis-Plus 集成
本文详细介绍 RuoYi-SpringBoot3-Pro 框架中 MyBatis-Plus 的集成方案,包括核心插件配置、多租户支持、Lambda 查询、代码生成等实战技巧。
GitHub:
https://github.com/undsky/RuoYi-SpringBoot3-Pro
一、概述
RuoYi-SpringBoot3-Pro 使用 MyBatis-Plus 3.5.12 替换原有的 MyBatis,提供更强大、更便捷的 ORM 能力。MyBatis-Plus 是 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
1.1 核心优势
| 特性 | 说明 |
|---|---|
| 无侵入 | 只做增强不做改变,引入不会对现有工程产生影响 |
| 损耗小 | 启动即会自动注入基本 CRUD,性能基本无损耗 |
| 强大的 CRUD | 内置通用 Mapper、Service,少量配置即可实现单表大部分 CRUD |
| Lambda 表达式 | 通过 Lambda 表达式,方便编写各类查询条件 |
| 主键自动生成 | 支持多种主键策略,可自由配置 |
| 内置分页插件 | 基于 MyBatis 物理分页,自动识别数据库类型 |
| 内置性能分析插件 | 可输出 SQL 语句及执行时间 |
| 内置全局拦截插件 | 提供全表 delete、update 操作智能分析阻断 |
1.2 项目依赖
xml
<!-- MyBatis-Plus Spring Boot 3 Starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.12</version>
</dependency>
<!-- MyBatis-Plus JSqlParser(SQL 解析器) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
<version>3.5.12</version>
</dependency>
二、核心配置
2.1 application.yml 配置
yaml
# MyBatis-Plus 配置
mybatis-plus:
# 搜索指定包别名
typeAliasesPackage: com.ruoyi.**.domain
# 配置 mapper 的扫描,找到所有的 mapper.xml 映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml
# 加载全局的配置文件
configLocation: classpath:mybatis/mybatis-config.xml
2.2 插件配置类
项目在 MybatisPlusConfig 中配置了四大核心插件:
java
@EnableTransactionManagement(proxyTargetClass = true)
@Configuration
@EnableConfigurationProperties(TenantProperties.class)
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(TenantProperties tenantProperties) {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 1. 多租户插件(可选)
if (Boolean.TRUE.equals(tenantProperties.getEnable())) {
interceptor.addInnerInterceptor(
new TenantLineInnerInterceptor(new MultiTenantHandler(tenantProperties))
);
}
// 2. 分页插件
interceptor.addInnerInterceptor(paginationInnerInterceptor());
// 3. 乐观锁插件
interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
// 4. 防全表更新删除插件
interceptor.addInnerInterceptor(blockAttackInnerInterceptor());
return interceptor;
}
}
三、核心插件详解
3.1 分页插件
自动识别数据库类型,无需手动配置,支持 MySQL、PostgreSQL、Oracle、达梦、瀚高等多种数据库。
java
/**
* 分页插件配置
*/
public PaginationInnerInterceptor paginationInnerInterceptor() {
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
// 可选:设置数据库类型(不设置则自动识别)
// paginationInnerInterceptor.setDbType(DbType.MYSQL);
// 可选:设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInnerInterceptor.setMaxLimit(-1L);
return paginationInnerInterceptor;
}
使用示例:
java
// Controller 层
@GetMapping("/page")
public TableDataInfo page(Region region) {
// 获取分页对象
Page<Region> page = getPage();
// 获取查询条件
QueryWrapper<Region> queryWrapper = getQueryWrapper(Region.class);
// 执行分页查询
IPage<Region> result = regionService.pageRegion(page, queryWrapper);
return getDataTableByPage(result);
}
// Service 层
@Override
public IPage<Region> pageRegion(Page<Region> page, QueryWrapper<Region> queryWrapper) {
return regionMapper.selectPage(page, queryWrapper);
}
3.2 乐观锁插件
防止并发修改导致数据丢失,通过版本号机制实现。
java
/**
* 乐观锁插件配置
*/
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
return new OptimisticLockerInnerInterceptor();
}
实体类配置:
java
@Data
@TableName("biz_order")
public class Order {
@TableId(type = IdType.AUTO)
private Long id;
private String orderNo;
// 乐观锁版本号字段
@Version
private Integer version;
}
使用示例:
java
// 更新时自动检查版本号
Order order = orderService.getById(1L);
order.setOrderNo("NEW_ORDER_NO");
// 如果版本号不匹配,更新失败返回 0
int rows = orderMapper.updateById(order);
3.3 防全表更新删除插件
避免误操作造成数据丢失,当执行不带 WHERE 条件的 UPDATE 或 DELETE 时,会抛出异常。
java
/**
* 防全表更新删除插件
*/
public BlockAttackInnerInterceptor blockAttackInnerInterceptor() {
return new BlockAttackInnerInterceptor();
}
拦截示例:
java
// ❌ 以下操作会被拦截并抛出异常
orderMapper.delete(null); // 全表删除
orderMapper.update(order, null); // 全表更新
// ✅ 正确的操作方式
QueryWrapper<Order> wrapper = new QueryWrapper<>();
wrapper.eq("status", "CANCELLED");
orderMapper.delete(wrapper); // 带条件删除
3.4 多租户插件
企业级 SaaS 应用必备能力,自动为 SQL 添加租户条件。
配置文件:
yaml
# 多租户配置
tenant:
# 是否启用多租户
enable: true
# 租户 ID 字段名
column: tenant_id
# 需要过滤的表(可选)
filterTables:
# 忽略多租户的表
ignoreTables:
- sys_config
- sys_dict_data
- sys_dict_type
- sys_menu
# ... 其他系统表
# 忽略多租户的用户(如超级管理员)
ignoreLoginNames:
- admin
多租户处理器:
java
public class MultiTenantHandler implements TenantLineHandler {
private final TenantProperties properties;
@Override
public Expression getTenantId() {
// 从当前登录用户获取租户 ID
return new LongValue(SecurityUtils.getLoginUser().getUser().getTenantId());
}
@Override
public String getTenantIdColumn() {
return properties.getColumn(); // 返回 "tenant_id"
}
@Override
public boolean ignoreTable(String tableName) {
// 判断是否忽略该表
List<String> ignoreTables = properties.getIgnoreTables();
return ignoreTables != null && ignoreTables.contains(tableName);
}
}
效果示例:
sql
-- 原始 SQL
SELECT * FROM biz_order WHERE status = 'PAID'
-- 自动添加租户条件后
SELECT * FROM biz_order WHERE status = 'PAID' AND tenant_id = 1001
四、实体类注解
4.1 常用注解说明
java
@Data
@TableName("biz_region") // 指定表名
public class Region implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(type = IdType.AUTO) // 主键策略:自增
@OrderBy(asc = true, sort = 1) // 默认排序
private String id;
/**
* 上级 ID
*/
@TableField("parent_id") // 指定字段名
private String parentId;
/**
* 名称
*/
@TableField("name")
private String name;
/**
* 层级
*/
@TableField("level")
private Integer level;
/**
* 非数据库字段
*/
@TableField(exist = false) // 标记为非数据库字段
private List<Region> children;
}
4.2 主键策略
| 策略 | 说明 |
|---|---|
IdType.AUTO |
数据库自增 |
IdType.NONE |
无状态,跟随全局配置 |
IdType.INPUT |
手动输入 |
IdType.ASSIGN_ID |
雪花算法(默认) |
IdType.ASSIGN_UUID |
UUID |
五、Lambda 查询
MyBatis-Plus 提供了强大的 Lambda 查询能力,避免硬编码字段名,编译期即可发现错误。
5.1 LambdaQueryWrapper
java
// 传统方式(字段名硬编码,容易出错)
QueryWrapper<Region> wrapper = new QueryWrapper<>();
wrapper.eq("level", 1);
wrapper.like("name", "北京");
// Lambda 方式(类型安全,IDE 自动补全)
LambdaQueryWrapper<Region> lambdaWrapper = new LambdaQueryWrapper<>();
lambdaWrapper.eq(Region::getLevel, 1)
.like(Region::getName, "北京")
.orderByAsc(Region::getId);
List<Region> list = regionMapper.selectList(lambdaWrapper);
5.2 链式查询
java
// 链式 Lambda 查询
List<Region> provinces = regionService.lambdaQuery()
.eq(Region::getLevel, 1)
.orderByAsc(Region::getId)
.list();
// 带条件的链式查询
String keyword = "北京";
List<Region> result = regionService.lambdaQuery()
.eq(Region::getLevel, 1)
.like(StringUtils.isNotEmpty(keyword), Region::getName, keyword)
.list();
5.3 常用条件方法
| 方法 | 说明 | 示例 |
|---|---|---|
eq |
等于 | .eq(Region::getLevel, 1) |
ne |
不等于 | .ne(Region::getLevel, 0) |
gt |
大于 | .gt(Region::getLevel, 1) |
ge |
大于等于 | .ge(Region::getLevel, 1) |
lt |
小于 | .lt(Region::getLevel, 4) |
le |
小于等于 | .le(Region::getLevel, 3) |
like |
模糊匹配 | .like(Region::getName, "北") |
likeLeft |
左模糊 | .likeLeft(Region::getName, "京") |
likeRight |
右模糊 | .likeRight(Region::getName, "北") |
between |
区间 | .between(Region::getLevel, 1, 3) |
in |
IN 查询 | .in(Region::getLevel, 1, 2, 3) |
isNull |
为空 | .isNull(Region::getParentId) |
isNotNull |
不为空 | .isNotNull(Region::getParentId) |
orderByAsc |
升序 | .orderByAsc(Region::getId) |
orderByDesc |
降序 | .orderByDesc(Region::getId) |
六、动态查询工具
项目封装了 MybatisUtils 工具类,支持根据请求参数动态构建查询条件。
6.1 支持的查询后缀
| 后缀 | 说明 | 示例参数 |
|---|---|---|
Eq |
等于 | levelEq=1 |
Ne |
不等于 | statusNe=0 |
Gt |
大于 | levelGt=1 |
Ge |
大于等于 | levelGe=1 |
Lt |
小于 | levelLt=4 |
Le |
小于等于 | levelLe=3 |
Like |
模糊匹配 | nameLike=北京 |
LikeLeft |
左模糊 | nameLikeLeft=京 |
LikeRight |
右模糊 | nameLikeRight=北 |
Between |
区间 | levelBetween=1,3 |
In |
IN 查询 | levelIn=1,2,3 |
IsNull |
为空 | parentIdIsNull |
Asc |
升序 | idAsc |
Desc |
降序 | idDesc |
6.2 使用示例
java
// Controller 中使用
@GetMapping("/list")
public AjaxResult list() {
// 自动根据请求参数构建查询条件
QueryWrapper<Region> queryWrapper = getQueryWrapper(Region.class);
return success(regionService.list(queryWrapper));
}
请求示例:
bash
GET /biz/Region/list?levelEq=1&nameLike=北&idAsc
生成的 SQL:
sql
SELECT * FROM biz_region
WHERE level = 1 AND name LIKE '%北%'
ORDER BY id ASC
七、Service 层封装
7.1 继承 IService
java
public interface IRegionService extends IService<Region> {
// 自定义方法
IPage<Region> pageRegion(Page<Region> page, QueryWrapper<Region> queryWrapper);
String idsToNames(String ids);
}
7.2 继承 ServiceImpl
java
@Service
@RequiredArgsConstructor
public class RegionServiceImpl extends ServiceImpl<RegionMapper, Region>
implements IRegionService {
private final RegionMapper regionMapper;
@Override
public IPage<Region> pageRegion(Page<Region> page, QueryWrapper<Region> queryWrapper) {
return regionMapper.selectPage(page, queryWrapper);
}
// 使用继承的方法
public void example() {
// 查询单条
Region region = this.getById(1L);
// 查询列表
List<Region> list = this.list();
// 条件查询
List<Region> provinces = this.lambdaQuery()
.eq(Region::getLevel, 1)
.list();
// 保存
this.save(new Region());
// 批量保存
this.saveBatch(list);
// 更新
this.updateById(region);
// 删除
this.removeById(1L);
}
}
7.3 IService 常用方法
| 方法 | 说明 |
|---|---|
save(T entity) |
插入一条记录 |
saveBatch(Collection<T>) |
批量插入 |
saveOrUpdate(T entity) |
存在则更新,否则插入 |
removeById(Serializable id) |
根据 ID 删除 |
removeByIds(Collection<?>) |
批量删除 |
updateById(T entity) |
根据 ID 更新 |
getById(Serializable id) |
根据 ID 查询 |
list() |
查询所有 |
list(Wrapper<T>) |
条件查询 |
page(IPage<T>, Wrapper<T>) |
分页查询 |
count() |
查询总数 |
lambdaQuery() |
Lambda 链式查询 |
lambdaUpdate() |
Lambda 链式更新 |
八、Mapper 层
8.1 继承 BaseMapper
java
public interface RegionMapper extends BaseMapper<Region> {
/**
* 自定义查询方法(使用 XML)
*/
List<Region> selectRegionList(Region region);
}
8.2 BaseMapper 内置方法
| 方法 | 说明 |
|---|---|
insert(T entity) |
插入一条记录 |
deleteById(Serializable id) |
根据 ID 删除 |
deleteByIds(Collection<?>) |
批量删除 |
updateById(T entity) |
根据 ID 更新 |
selectById(Serializable id) |
根据 ID 查询 |
selectBatchIds(Collection<?>) |
批量查询 |
selectList(Wrapper<T>) |
条件查询 |
selectPage(IPage<T>, Wrapper<T>) |
分页查询 |
selectCount(Wrapper<T>) |
查询总数 |
九、代码生成器适配
RuoYi-SpringBoot3-Pro 的代码生成器已针对 MyBatis-Plus 优化:
9.1 生成的实体类
java
@Data
@TableName("biz_order")
public class Order implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
@Excel(name = "订单号")
@TableField("order_no")
private String orderNo;
@Excel(name = "金额")
@TableField("amount")
private BigDecimal amount;
@Excel(name = "创建时间", dateFormat = "yyyy-MM-dd HH:mm:ss")
@TableField("create_time")
private Date createTime;
}
9.2 生成的 Mapper
java
public interface OrderMapper extends BaseMapper<Order> {
// 自动拥有 CRUD 方法,无需编写
}
9.3 生成的 Service
java
public interface IOrderService extends IService<Order> {
// 业务方法
}
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order>
implements IOrderService {
// 实现
}
十、最佳实践
10.1 分页查询标准写法
java
@GetMapping("/page")
public TableDataInfo page(Order order) {
Page<Order> page = getPage();
QueryWrapper<Order> wrapper = getQueryWrapper(Order.class);
IPage<Order> result = orderService.page(page, wrapper);
return getDataTableByPage(result);
}
10.2 条件构造器复用
java
// 封装通用查询条件
private LambdaQueryWrapper<Order> buildQueryWrapper(Order order) {
return new LambdaQueryWrapper<Order>()
.eq(order.getStatus() != null, Order::getStatus, order.getStatus())
.like(StringUtils.isNotEmpty(order.getOrderNo()), Order::getOrderNo, order.getOrderNo())
.ge(order.getStartTime() != null, Order::getCreateTime, order.getStartTime())
.le(order.getEndTime() != null, Order::getCreateTime, order.getEndTime())
.orderByDesc(Order::getCreateTime);
}
10.3 批量操作优化
java
// 批量插入(默认每批 1000 条)
orderService.saveBatch(orderList);
// 自定义批次大小
orderService.saveBatch(orderList, 500);
// 批量更新
orderService.updateBatchById(orderList);
10.4 逻辑删除配置
yaml
mybatis-plus:
global-config:
db-config:
# 逻辑删除字段
logic-delete-field: deleted
# 逻辑已删除值
logic-delete-value: 1
# 逻辑未删除值
logic-not-delete-value: 0
java
@Data
@TableName("biz_order")
public class Order {
// ...
@TableLogic
private Integer deleted;
}
十一、常见问题
11.1 字段名与数据库列名不一致
使用 @TableField 注解指定:
java
@TableField("order_no")
private String orderNo;
11.2 忽略某个字段
java
@TableField(exist = false)
private String tempField;
11.3 自动填充
java
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
配置填充处理器:
java
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
}
}
十二、总结
RuoYi-SpringBoot3-Pro 的 MyBatis-Plus 集成方案具有以下特点:
- 开箱即用:预配置分页、乐观锁、防误删等核心插件
- 多租户支持:企业级 SaaS 应用必备能力
- Lambda 查询:类型安全,告别硬编码
- 动态查询:根据请求参数自动构建查询条件
- 代码生成适配:生成的代码自动继承 BaseMapper 和 IService
- 多数据库兼容:支持 MySQL、PostgreSQL、Oracle、达梦、瀚高等