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

目录
相关文章
|
4天前
|
安全 Java Maven
【RuoYi-SpringBoot3-Pro】:ClassFinal 代码加密
本文介绍RuoYi-SpringBoot3-Pro如何集成ClassFinal实现Java代码加密,保护核心业务逻辑。通过Maven插件对class文件与配置文件进行AES加密,防止反编译泄露,支持选择性加密、密码验证与机器码绑定,适用于商业交付、私有化部署等场景,保障知识产权安全。
84 5
|
4月前
|
测试技术 API 开发者
Postman 旧版本下载方法全解(图文教程)
本文详解如何下载Postman历史版本,涵盖卸载当前版本、查找官方发布记录及旧版下载链接,并探讨版本管理痛点,引出Apifox等集成化接口测试工具的优势,助力开发测试更高效。
Postman 旧版本下载方法全解(图文教程)
|
运维 监控 安全
运维技术——从基础到高阶的全面解析
本文是一篇技术性文章,主要探讨了运维技术。运维不仅仅是保持系统的稳定运行,更包括优化、预防故障和应对突发事件的能力。本文将从运维的基本概念入手,逐步深入到高阶技术和策略,为读者提供一个全面的运维知识体系。希望通过这篇文章,读者能够更好地理解和应用运维技术,提升自己的运维能力。
|
存储 算法 对象存储
网盘与相册服务PDS体验分享
网盘与相册服务PDS体验分享
404 1
|
10月前
|
XML Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于注解的整合
本文介绍了Spring Boot集成MyBatis的两种方式:基于XML和注解的形式。重点讲解了注解方式,包括@Select、@Insert、@Update、@Delete等常用注解的使用方法,以及多参数时@Param注解的应用。同时,针对字段映射不一致的问题,提供了@Results和@ResultMap的解决方案。文章还提到实际项目中常结合XML与注解的优点,灵活使用两者以提高开发效率,并附带课程源码供下载学习。
811 0
|
12月前
|
SQL Java 数据库连接
【潜意识Java】深入理解MyBatis的Mapper层,以及让数据访问更高效的详细分析
深入理解MyBatis的Mapper层,以及让数据访问更高效的详细分析
2186 1
|
资源调度 前端开发 JavaScript
React中classnames库使用
【10月更文挑战第7天】
|
JavaScript 前端开发 Java
解决跨域问题大集合:vue-cli项目 和 java/springboot(6种方式) 两端解决(完美解决)
这篇文章详细介绍了如何在前端Vue项目和后端Spring Boot项目中通过多种方式解决跨域问题。
1049 1
解决跨域问题大集合:vue-cli项目 和 java/springboot(6种方式) 两端解决(完美解决)
SpringBoot项目配置热部署启动 及 热部署失效的问题解决
这篇文章介绍了如何在SpringBoot项目中配置热部署启动,包括在pom文件中添加热部署依赖、在IDEA中进行设置、修改配置文件以及IDEA启动设置,以解决热部署失效的问题。
SpringBoot项目配置热部署启动 及 热部署失效的问题解决