【RuoYi-SpringBoot3-Pro】:MyBatis-Plus 集成

简介: RuoYi-SpringBoot3-Pro 集成 MyBatis-Plus 3.5.12,提供分页、乐观锁、多租户、Lambda 查询等核心功能,支持动态条件构建与代码生成,提升开发效率,助力企业级 SaaS 应用快速开发。

【RuoYi-SpringBoot3-Pro】:MyBatis-Plus 集成

本文详细介绍 RuoYi-SpringBoot3-Pro 框架中 MyBatis-Plus 的集成方案,包括核心插件配置、多租户支持、Lambda 查询、代码生成等实战技巧。

一、概述

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 项目依赖

<!-- 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 配置

# 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 中配置了四大核心插件:

@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、达梦、瀚高等多种数据库。

/**
 * 分页插件配置
 */
public PaginationInnerInterceptor paginationInnerInterceptor() {
   
    PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
    // 可选:设置数据库类型(不设置则自动识别)
    // paginationInnerInterceptor.setDbType(DbType.MYSQL);
    // 可选:设置最大单页限制数量,默认 500 条,-1 不受限制
    // paginationInnerInterceptor.setMaxLimit(-1L);
    return paginationInnerInterceptor;
}

使用示例:

// 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 乐观锁插件

防止并发修改导致数据丢失,通过版本号机制实现。

/**
 * 乐观锁插件配置
 */
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
   
    return new OptimisticLockerInnerInterceptor();
}

实体类配置:

@Data
@TableName("biz_order")
public class Order {
   
    @TableId(type = IdType.AUTO)
    private Long id;

    private String orderNo;

    // 乐观锁版本号字段
    @Version
    private Integer version;
}

使用示例:

// 更新时自动检查版本号
Order order = orderService.getById(1L);
order.setOrderNo("NEW_ORDER_NO");
// 如果版本号不匹配,更新失败返回 0
int rows = orderMapper.updateById(order);

3.3 防全表更新删除插件

避免误操作造成数据丢失,当执行不带 WHERE 条件的 UPDATE 或 DELETE 时,会抛出异常。

/**
 * 防全表更新删除插件
 */
public BlockAttackInnerInterceptor blockAttackInnerInterceptor() {
   
    return new BlockAttackInnerInterceptor();
}

拦截示例:

// ❌ 以下操作会被拦截并抛出异常
orderMapper.delete(null);  // 全表删除
orderMapper.update(order, null);  // 全表更新

// ✅ 正确的操作方式
QueryWrapper<Order> wrapper = new QueryWrapper<>();
wrapper.eq("status", "CANCELLED");
orderMapper.delete(wrapper);  // 带条件删除

3.4 多租户插件

企业级 SaaS 应用必备能力,自动为 SQL 添加租户条件。

配置文件:

# 多租户配置
tenant:
  # 是否启用多租户
  enable: true
  # 租户 ID 字段名
  column: tenant_id
  # 需要过滤的表(可选)
  filterTables:
  # 忽略多租户的表
  ignoreTables:
    - sys_config
    - sys_dict_data
    - sys_dict_type
    - sys_menu
    # ... 其他系统表
  # 忽略多租户的用户(如超级管理员)
  ignoreLoginNames:
    - admin

多租户处理器:

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
SELECT * FROM biz_order WHERE status = 'PAID'

-- 自动添加租户条件后
SELECT * FROM biz_order WHERE status = 'PAID' AND tenant_id = 1001

四、实体类注解

4.1 常用注解说明

@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

// 传统方式(字段名硬编码,容易出错)
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 链式查询

// 链式 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 使用示例

// Controller 中使用
@GetMapping("/list")
public AjaxResult list() {
   
    // 自动根据请求参数构建查询条件
    QueryWrapper<Region> queryWrapper = getQueryWrapper(Region.class);
    return success(regionService.list(queryWrapper));
}

请求示例:

GET /biz/Region/list?levelEq=1&nameLike=北&idAsc

生成的 SQL:

SELECT * FROM biz_region 
WHERE level = 1 AND name LIKE '%北%' 
ORDER BY id ASC

七、Service 层封装

7.1 继承 IService

public interface IRegionService extends IService<Region> {
   
    // 自定义方法
    IPage<Region> pageRegion(Page<Region> page, QueryWrapper<Region> queryWrapper);
    String idsToNames(String ids);
}

7.2 继承 ServiceImpl

@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

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 生成的实体类

@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

public interface OrderMapper extends BaseMapper<Order> {
   
    // 自动拥有 CRUD 方法,无需编写
}

9.3 生成的 Service

public interface IOrderService extends IService<Order> {
   
    // 业务方法
}

@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> 
    implements IOrderService {
   
    // 实现
}

十、最佳实践

10.1 分页查询标准写法

@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 条件构造器复用

// 封装通用查询条件
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 批量操作优化

// 批量插入(默认每批 1000 条)
orderService.saveBatch(orderList);

// 自定义批次大小
orderService.saveBatch(orderList, 500);

// 批量更新
orderService.updateBatchById(orderList);

10.4 逻辑删除配置

mybatis-plus:
  global-config:
    db-config:
      # 逻辑删除字段
      logic-delete-field: deleted
      # 逻辑已删除值
      logic-delete-value: 1
      # 逻辑未删除值
      logic-not-delete-value: 0
@Data
@TableName("biz_order")
public class Order {
   
    // ...

    @TableLogic
    private Integer deleted;
}

十一、常见问题

11.1 字段名与数据库列名不一致

使用 @TableField 注解指定:

@TableField("order_no")
private String orderNo;

11.2 忽略某个字段

@TableField(exist = false)
private String tempField;

11.3 自动填充

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

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

配置填充处理器:

@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、达梦、瀚高等

目录
相关文章
|
5天前
|
存储 JavaScript 前端开发
JavaScript基础
本节讲解JavaScript基础核心知识:涵盖值类型与引用类型区别、typeof检测类型及局限性、===与==差异及应用场景、内置函数与对象、原型链五规则、属性查找机制、instanceof原理,以及this指向和箭头函数中this的绑定时机。重点突出类型判断、原型继承与this机制,助力深入理解JS面向对象机制。(238字)
|
4天前
|
云安全 人工智能 安全
阿里云2026云上安全健康体检正式开启
新年启程,来为云上环境做一次“深度体检”
1590 6
|
6天前
|
安全 数据可视化 网络安全
安全无小事|阿里云先知众测,为企业筑牢防线
专为企业打造的漏洞信息收集平台
1327 2
|
5天前
|
缓存 算法 关系型数据库
深入浅出分布式 ID 生成方案:从原理到业界主流实现
本文深入探讨分布式ID的生成原理与主流解决方案,解析百度UidGenerator、滴滴TinyID及美团Leaf的核心设计,涵盖Snowflake算法、号段模式与双Buffer优化,助你掌握高并发下全局唯一ID的实现精髓。
348 160
|
5天前
|
人工智能 自然语言处理 API
n8n:流程自动化、智能化利器
流程自动化助你在重复的业务流程中节省时间,可通过自然语言直接创建工作流啦。
408 6
n8n:流程自动化、智能化利器
|
7天前
|
人工智能 API 开发工具
Skills比MCP更重要?更省钱的多!Python大佬这观点老金测了一周终于懂了
加我进AI学习群,公众号右下角“联系方式”。文末有老金开源知识库·全免费。本文详解Claude Skills为何比MCP更轻量高效:极简配置、按需加载、省90% token,适合多数场景。MCP仍适用于复杂集成,但日常任务首选Skills。推荐先用SKILL.md解决,再考虑协议。附实测对比与配置建议,助你提升效率,节省精力。关注老金,一起玩转AI工具。
|
14天前
|
机器学习/深度学习 安全 API
MAI-UI 开源:通用 GUI 智能体基座登顶 SOTA!
MAI-UI是通义实验室推出的全尺寸GUI智能体基座模型,原生集成用户交互、MCP工具调用与端云协同能力。支持跨App操作、模糊语义理解与主动提问澄清,通过大规模在线强化学习实现复杂任务自动化,在出行、办公等高频场景中表现卓越,已登顶ScreenSpot-Pro、MobileWorld等多项SOTA评测。
1550 7
|
4天前
|
Linux 数据库
Linux 环境 Polardb-X 数据库 单机版 rpm 包 安装教程
本文介绍在CentOS 7.9环境下安装PolarDB-X单机版数据库的完整流程,涵盖系统环境准备、本地Yum源配置、RPM包安装、用户与目录初始化、依赖库解决、数据库启动及客户端连接等步骤,助您快速部署运行PolarDB-X。
252 1
Linux 环境 Polardb-X 数据库 单机版 rpm 包 安装教程
|
9天前
|
人工智能 前端开发 API
Google发布50页AI Agent白皮书,老金帮你提炼10个核心要点
老金分享Google最新AI Agent指南:让AI从“动嘴”到“动手”。Agent=大脑(模型)+手(工具)+协调系统,可自主完成任务。通过ReAct模式、多Agent协作与RAG等技术,实现真正自动化。入门推荐LangChain,文末附开源知识库链接。
676 119