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

本文详细介绍 RuoYi-SpringBoot3-Pro[1] 框架中 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 项目依赖


    
    
    
  <!-- 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_UUIDUUID

五、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)
inIN 查询.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
InIN 查询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 集成方案具有以下特点:


引用链接

[1] RuoYi-SpringBoot3-Pro: https://github.com/undsky/RuoYi-SpringBoot3-Pro
[2] RuoYi-SpringBoot3-Pro 文档: https://www.undsky.com/blog/?category=RuoYi-SpringBoot3-Pro#