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

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

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

GitHub:https://github.com/undsky/RuoYi-SpringBoot3-Pro

点击获取最新AI资讯、n8n工作流、开发经验分享

一、概述

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

相关推荐
thulium_2 小时前
SpringBoot3 配置嵌入Servlet容器
spring boot·servlet
法拉第第2 小时前
CAS乐观锁
后端
高松燈2 小时前
K8s学习文档(1) -- 集群搭建(手把手教学)
后端
czlczl200209252 小时前
SpringBoot自定义Redis
spring boot·redis·后端
踏浪无痕2 小时前
Java 17 升级避坑:如何安全处理反射访问限制
后端·面试·架构
Go高并发架构_王工2 小时前
Redis命令执行原理与源码分析:深入理解内部机制
数据库·redis·后端
唐叔在学习2 小时前
buildozer打包详解:细说那些我踩过的坑
android·后端·python
okseekw2 小时前
Java动态代理实战:手把手教你实现明星经纪人模式
java·后端