黑马微服务开发与实战学习笔记_MybatisPlus_P4 插件功能

系列博客目录



众多插件

MybatisPlus提供了很多的插件功能,进一步拓展其功能。目前已有的插件有:

  • PaginationInnerInterceptor:自动分页
  • TenantLineInnerInterceptor:多租户
  • DynamicTableNameInnerInterceptor:动态表名
  • OptimisticLockerInnerInterceptor:乐观锁
  • IllegalSQLInnerInterceptor:sql 性能规范
  • BlockAttackInnerInterceptor:防止全表更新与删除

注意:

使用多个插件的时候需要注意插件定义顺序,建议使用顺序如下:

  • 多租户,动态表名
  • 分页,乐观锁
  • sql 性能规范,防止全表更新与删除

这里我们以分页插件为里来学习插件的用法,最常用。MP没有限制分页的方式的,也可以使用之前的PageHelper来实现,不过需要额外引入PageHelper的依赖,既然我们已经用来MP的依赖,我们为了方便可以直接使用MP的分页。

分页插件

在未引入分页插件的情况下,MybatisPlus是不支持分页功能的,IServiceBaseMapper中的分页方法都无法正常起效。所以,我们必须配置分页插件。

首先,要在配置类中注册MyBatisPlus的核心插件,同时添加分页插件。
在项目中新建一个配置类:
在这里插入图片描述

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.annotation.DbType;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        // 1. 初始化核心插件
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 2. new分页插件 DbType.MYSQL 表示 使用的是 MySQL 数据库
        PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        
        // 设置分页查询的最大限制
        pageInterceptor.setMaxLimit(1000L);  // 设置分页上限为 1000
        
        // 将分页插件添加到拦截器中,也可以添加其他MP插件
        interceptor.addInnerInterceptor(pageInterceptor);
        
        return interceptor;
    }
}

IService中的分页代码
第一个函数的第二个参数就是Wrapper查询条件,第一个参数page有个泛型E,E就是前面定义好的E extends IPage,就是IPage这个接口以及其子类类型(如下图所示)。
在这里插入图片描述
用的最多的就是Page,Page里面包含分页的参数,页码,每页的大小,排序方式等信息。
所以调用分页函数的时候,首先传分页的参数,再传查询条件。
函数的返回值也是E,其实和PageHelper一样,传的参数不光是参数还有分页结果,只不过传的时候没有结果而已,填好之后再返回带有结果的E。

/**
     * 翻页查询
     *
     * @param page         翻页对象
     * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
     */
    default <E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper) {
        return getBaseMapper().selectPage(page, queryWrapper);
    }

    /**
     * 无条件翻页查询
     *
     * @param page 翻页对象
     * @see Wrappers#emptyWrapper()
     */
    default <E extends IPage<T>> E page(E page) {
        return page(page, Wrappers.emptyWrapper());
    }

现在可以使用了

@Test
void testPageQuery() {
    // 1. 设置分页参数
    int pageNo = 1;    // 当前页码
    int pageSize = 2;  // 每页显示的记录数

    // 1.1 创建分页对象,使用 Page.of 方法来指定当前页和每页记录数
    Page<User> page = Page.of(pageNo, pageSize);

    // 1.2 设置排序条件,通过 OrderItem 来指定排序字段和排序方式
    page.addOrder(new OrderItem("balance", false));  // 根据 balance 字段降序排序

    // 1.3 执行分页查询,获取分页数据
    Page<User> p = userService.page(page);

    // 2. 打印总记录数
    System.out.println("Total records: " + p.getTotal());  // 获取查询的总记录数

    // 3. 打印总页数
    System.out.println("Total pages: " + p.getPages());  // 获取总页数

    // 4. 获取当前页的数据
    List<User> records = p.getRecords();  // 获取当前页的记录

    // 5. 遍历并打印当前页的数据
    records.forEach(System.out::println);
}

在这里插入图片描述

案例

简单分页查询案例。需求:遵循下面的接口规范,编写一个UserController接口,实现User的分页查询

请求方式

  • GET

请求路径

  • /users/page

请求参数

{
    "pageNo": 1,      // 当前页码,表示从第几页开始查询
    "pageSize": 5,    // 每页显示的记录数
    "sortBy": "balance",  // 排序字段,表示根据哪个字段进行排序
    "isAsc": false,   // 排序方式,true 表示升序,false 表示降序
    "name": "o",      // 用户名筛选条件,支持模糊查询
    "status": 1       // 用户状态筛选条件,1 表示正常状态
}

返回值

{
    "total": 100006,  // 总记录数
    "pages": 50003,   // 总页数
    "list": [         // 当前页的用户列表
        {
            "id": 1685100878975279298,   // 用户 ID
            "username": "user_9****",    // 用户名
            "info": {                    // 用户详细信息
                "age": 24,              // 用户年龄
                "intro": "英文老师",     // 用户简介
                "gender": "female"      // 用户性别
            },
            "status": "正常",            // 用户状态
            "balance": 2000             // 用户余额
        }
    ]
}

特殊说明

  • 排序字段为空:如果 sortBy 字段为空,则默认按照更新时间进行排序。
  • 排序字段不为空:如果 sortBy 字段有值,则根据该字段进行排序,排序顺序由 isAsc 字段决定(true 为升序,false 为降序)。

分析一下:查询的参数一般会构建一个查询实体来存,返回的参数可以通过VO或者DTO来存放。

实体

这里需要定义3个实体:

  • UserQuery:分页查询条件的实体,包含分页、排序参数、过滤条件
  • PageDTO:分页结果实体,包含总条数、总页数、当前页数据
  • UserVO:用户页面视图实体
package com.itheima.mp.domain.query;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery {
    @ApiModelProperty("用户名关键字")
    private String name;
    @ApiModelProperty("用户状态:1-正常,2-冻结")
    private Integer status;
    @ApiModelProperty("余额最小值")
    private Integer minBalance;
    @ApiModelProperty("余额最大值")
    private Integer maxBalance;
}

其中缺少的仅仅是分页条件,而分页条件不仅仅用户分页查询需要,以后其它业务也都有分页查询的需求。因此建议将分页查询条件单独定义为一个PageQuery实体:

PageQuery是前端提交的查询参数,一般包含四个属性:

  • pageNo:页码
  • pageSize:每页数据条数
  • sortBy:排序字段
  • isAsc:是否升序
@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {
    @ApiModelProperty("页码")
    private Long pageNo;
    @ApiModelProperty("页码")
    private Long pageSize;
    @ApiModelProperty("排序字段")
    private String sortBy;
    @ApiModelProperty("是否升序")
    private Boolean isAsc;
}

然后,让我们的UserQuery继承这个实体:

package com.itheima.mp.domain.query;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery extends PageQuery {
    @ApiModelProperty("用户名关键字")
    private String name;
    @ApiModelProperty("用户状态:1-正常,2-冻结")
    private Integer status;
    @ApiModelProperty("余额最小值")
    private Integer minBalance;
    @ApiModelProperty("余额最大值")
    private Integer maxBalance;
}

返回值的用户实体沿用之前定一个UserVO实体:

package com.itheima.mp.domain.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.List;

@Data
@ApiModel(description = "用户VO实体")
public class UserVO {

    @ApiModelProperty("用户id")
    private Long id;

    @ApiModelProperty("用户名")
    private String username;

    @ApiModelProperty("详细信息")
    private String info;

    @ApiModelProperty("使用状态(1正常 2冻结)")
    private Integer status;

    @ApiModelProperty("账户余额")
    private Integer balance;
    @ApiModelProperty("收货地址列表")
    private List<AddressVO> addresses;
}

最后,则是分页实体PageDTO:

package com.itheima.mp.domain.dto;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.List;

@Data
@ApiModel(description = "分页结果")
public class PageDTO<T> {
    @ApiModelProperty("总条数")
    private Long total;
    @ApiModelProperty("总页数")
    private Long pages;
    @ApiModelProperty("集合")
    private List<T> list;
}

开发接口

我们在UserController中定义分页查询用户的接口:

package com.itheima.mp.controller;

import com.itheima.mp.domain.dto.PageDTO;
import com.itheima.mp.domain.query.PageQuery;
import com.itheima.mp.domain.vo.UserVO;
import com.itheima.mp.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("users")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @GetMapping("/page")
    public PageDTO<UserVO> queryUsersPage(UserQuery query){
        return userService.queryUsersPage(query);
    }

    // 。。。 略
}

然后在IUserService中创建queryUsersPage方法:

PageDTO<UserVO> queryUsersPage(PageQuery query);

接下来,在UserServiceImpl中实现该方法:

@Override
public PageDTO<UserVO> queryUsersPage(PageQuery query) {
    // 1.构建条件
    // 1.1.分页条件
    Page<User> page = Page.of(query.getPageNo(), query.getPageSize());
    // 1.2.排序条件
    if (query.getSortBy() != null) {
        page.addOrder(new OrderItem(query.getSortBy(), query.getIsAsc()));
    }else{
        // 默认按照更新时间排序
        page.addOrder(new OrderItem("update_time", false));
    }
    // 2.查询
    page(page);
    // 3.数据非空校验
    List<User> records = page.getRecords();
    if (records == null || records.size() <= 0) {
        // 无数据,返回空结果
        return new PageDTO<>(page.getTotal(), page.getPages(), Collections.emptyList());
    }
    // 4.有数据,转换
    List<UserVO> list = BeanUtil.copyToList(records, UserVO.class);
    // 5.封装返回
    return new PageDTO<UserVO>(page.getTotal(), page.getPages(), list);
}

启动项目,在页面查看:
在这里插入图片描述

通用分页实体

  1. 之前的代码中queryUsersPage函数中构建分页条件这一部分和业务没有太大关系,应该是通用的,而且麻烦,任何一个业务都要编写这部分代码显然不合理,应该封装一下,作为一个工具,实现在PageQuery中定义方法,将PageQuery对象转为MyBatisPlus中的Page对象。
  2. 之前的代码中queryUsersPage函数中拿到返回值结果后的代码,和业务也没有太大关系,也应该封装,实现在PageDTO中定义方法,将MyBatisPlus中的Page结果转为PageDTO结果。

需求也是上面问题的解决方案

  • 在PageQuery中定义方法,将PageQuery对象转为MyBatisPlus中的Page对象
  • 在PageDTO中定义方法,将MyBatisPlus中的Page结果转为PageDTO结果

我们完全可以在PageQuery这个实体中定义一个工具方法,简化开发。

package com.itheima.mp.domain.query;

import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Data;

@Data
public class PageQuery {
    private Integer pageNo;
    private Integer pageSize;
    private String sortBy;
    private Boolean isAsc;

    public <T>  Page<T> toMpPage(OrderItem ... orders){
        // 1.分页条件
        Page<T> p = Page.of(pageNo, pageSize);
        // 2.排序条件
        // 2.1.先看前端有没有传排序字段
        if (sortBy != null) {
            p.addOrder(new OrderItem(sortBy, isAsc));
            return p;
        }
        // 2.2.再看有没有手动指定排序字段
        if(orders != null){
            p.addOrder(orders);
        }
        return p;
    }

    public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc){
        return this.toMpPage(new OrderItem(defaultSortBy, isAsc));
    }

    public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {
        return toMpPage("create_time", false);
    }

    public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() {
        return toMpPage("update_time", false);
    }
}

这样我们在开发也时就可以省去对从PageQueryPage的的转换:

// 1.构建条件
Page<User> page = query.toMpPageDefaultSortByCreateTimeDesc();
//这里的query是UserQuery 是 PageQuery的子类,可以调用父类的方法

在查询出分页结果后,数据的非空校验,数据的vo转换都是模板代码,编写起来很麻烦。
我们完全可以将其封装到PageDTO的工具方法中,简化整个过程。

package com.itheima.mp.domain.dto;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageDTO<V> {
    private Long total;
    private Long pages;
    private List<V> list;

    /**
     * 返回空分页结果
     * @param p MybatisPlus的分页结果
     * @param <V> 目标VO类型
     * @param <P> 原始PO类型
     * @return VO的分页对象
     */
    public static <V, P> PageDTO<V> empty(Page<P> p){
        return new PageDTO<>(p.getTotal(), p.getPages(), Collections.emptyList());
    }

    /**
     * 将MybatisPlus分页结果转为 VO分页结果
     * @param p MybatisPlus的分页结果
     * @param voClass 目标VO类型的字节码
     * @param <V> 目标VO类型
     * @param <P> 原始PO类型
     * @return VO的分页对象
     */
    public static <V, P> PageDTO<V> of(Page<P> p, Class<V> voClass) {
        // 1.非空校验
        List<P> records = p.getRecords();
        if (records == null || records.size() <= 0) {
            // 无数据,返回空结果
            return empty(p);
        }
        // 2.数据转换
        List<V> vos = BeanUtil.copyToList(records, voClass);
        // 3.封装返回
        return new PageDTO<>(p.getTotal(), p.getPages(), vos);
    }

    /**
     * 将MybatisPlus分页结果转为 VO分页结果,允许用户自定义PO到VO的转换方式
     * @param p MybatisPlus的分页结果
     * @param convertor PO到VO的转换函数
     * @param <V> 目标VO类型
     * @param <P> 原始PO类型
     * @return VO的分页对象
     */
    public static <V, P> PageDTO<V> of(Page<P> p, Function<P, V> convertor) {
        // 1.非空校验
        List<P> records = p.getRecords();
        if (records == null || records.size() <= 0) {
            // 无数据,返回空结果
            return empty(p);
        }
        // 2.数据转换
        List<V> vos = records.stream().map(convertor).collect(Collectors.toList());
        // 3.封装返回
        return new PageDTO<>(p.getTotal(), p.getPages(), vos);
    }
}

最终,业务层的代码可以简化为:

@Override
public PageDTO<UserVO> queryUserByPage(PageQuery query) {
    // 1.构建条件
    Page<User> page = query.toMpPageDefaultSortByCreateTimeDesc();
    // 2.查询
    page(page);
    // 3.封装返回
    return PageDTO.of(page, UserVO.class);
}

如果是希望自定义PO到VO的转换过程,可以这样做:

@Override
public PageDTO<UserVO> queryUserByPage(PageQuery query) {
    // 1.构建条件
    Page<User> page = query.toMpPageDefaultSortByCreateTimeDesc();
    // 2.查询
    page(page);
    // 3.封装返回
    return PageDTO.of(page, user -> {
        // 拷贝属性到VO
        UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
        // 用户名脱敏
        String username = vo.getUsername();
        vo.setUsername(username.substring(0, username.length() - 2) + "**");
        return vo;
    });
}
### 黑马程序员微服务开发实战教程概述 黑马程序员提供了全面的微服务技术栈教程,涵盖了从基础到高级的各种主题。对于希望深入理解并实践微服务架构的学习者来说,这些资源非常有价值。 #### 课程内容概览 - **环境搭建**:在开始之前,需要准备好CentOS7的操作系统以及配置好Docker环境[^1]。这一步骤至关重要,因为后续所有的操作都将基于此平台展开。 - **Spring Cloud微服务治理**:该部分介绍了如何利用Spring Cloud框架来管理和协调多个微服务实例之间的交互。具体原则包括但不限于避免重复开发相同的业务逻辑、保持各微服务的数据独立性和通过API网关暴露必要的接口给其他服务调用[^2]。 - **防止循环依赖问题**:当不同服务间存在互相调用的情况时,可能会引发循环依赖的问题。为此,MyBatis Plus提供了一个名为`Db`的静态工具类,它能够有效地简化CRUD操作的同时减少此类风险的发生概率[^3]。 - **常见错误处理**:开发者们经常会在实际编码过程中碰到各种意想不到的小麻烦。为了节省时间成本,在官方文档里还特别整理了一份针对这些问题的有效解决方案列表,使得大家可以快速定位并解决问题而不必花费过多精力在网上搜索答案[^4]。 - **Elasticsearch集成案例**:视频教程展示了如何使用Java REST Client完成对Elasticsearch索引设置的任务,这对于构建高效搜索引擎或者日志分析系统具有重要意义[^5]。 ```java // Java代码示例:创建一个新的索引,并定义其mapping结构 RestHighLevelClient client = new RestHighLevelClient( RestClient.builder(new HttpHost("localhost", 9200, "http"))); CreateIndexRequest request = new CreateIndexRequest("product"); request.mapping("{\n" + " \"properties\": {\n" + " \"name\": {\"type\": \"text\"},\n" + " \"price\": {\"type\": \"float\"}\n" + " }\n" + "}", XContentType.JSON); client.indices().create(request, RequestOptions.DEFAULT); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值