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

2025博客之星年度评选已开启 10w+人浏览 2.8k人参与

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

<!-- 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 集成方案具有以下特点:

  • 开箱即用:预配置分页、乐观锁、防误删等核心插件
  • 多租户支持:企业级 SaaS 应用必备能力
  • Lambda 查询:类型安全,告别硬编码
  • 动态查询:根据请求参数自动构建查询条件
  • 代码生成适配:生成的代码自动继承 BaseMapper 和 IService
  • 多数据库兼容:支持 MySQL、PostgreSQL、Oracle、达梦、瀚高等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

undsky_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值