一个结合MybatisPlus轻松进行分页的处理方案

68 阅读4分钟

1、前言:


项目开发中经常会用到许多分页处理,特别是做管理后台开发,大多数情况下都会去根据请求参数按照指定格式进行分页,返回时,也还需对返回数据进行类型转换,这一系列下来特别麻烦,所以突发奇想,写一个分页工具,可以满足基本的分页需求

2、代码:


所需依赖:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.2</version>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.4.M1</version>
</dependency>

这边就直接贴代码了,总共分为三个部分

  • 定义分页实体类
  • 解析分页实体类
  • 响应实体类

2.1、分页实体类

代码:

package com.migu.migumall.common.request.requestpage.v2;

import cn.hutool.core.util.NumberUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.migu.migumall.utils.SqlHandlerUtils;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;

import java.util.Map;
import java.util.stream.Collectors;

/**
 * 请求分页对象
 *
 * @param <T> 数据库实体类型
 * @param <R> 请求分页对象
 * @author kkmigu 664325656@qq.com
 */
@Data
@ApiModel("请求分页实例")
@Slf4j
public class RequestPageInstance<T, R> {

    /**
     * 当前请求页码
     */
    @ApiModelProperty("当前页码")
    private Long current;

    /**
     * 当前请求数量
     */
    @ApiModelProperty("请求数据数量")
    private Long size;

    /**
     * 数据库映射的实体类型
     */
    @ApiModelProperty("实际分页类型")
    private Class<T> destClass;

    /**
     * 当前请求数据
     */
    @ApiModelProperty("请求对象")
    private R reqInstance;

    public RequestPageInstance() {
        this.current = 1L;
        this.size = 10L;
    }

    public RequestPageInstance(Long current) {
        this.current = current;
        this.size = 10L;
    }

    public RequestPageInstance(Long current, Long size) {
        this.current = current;
        this.size = size;
    }

    public RequestPageInstance(Long current, Long size, R reqInstance) {
        this.current = current;
        this.size = size;
        this.reqInstance = reqInstance;
    }

    public WrapperConstructor wrapperConstructor() {
        return new WrapperConstructor();
    }

    /**
     * 普通分页查询
     *
     * @param service
     * @return
     */
    public IPage<T> startPage(IService<T> service) {
        return service.page(new Page<>(current, size));
    }

    /**
     * 支持给定返回类型 的分页查询
     *
     * @param service 分页服务对象
     * @param p       响应对象
     * @param <P>     返回类型
     * @return
     * @throws IllegalAccessException
     */
    public  <P> IPage<P> startPage(IService<T> service, Class<P> p) throws IllegalAccessException {
        return copyPageAndRsp(p, startPage(service));
    }

    /**
     * 根据 reqInstance实例属性值 进行分页查询
     *
     * @param service
     * @return
     */
    public IPage<T> startPageByParam(IService<T> service) {
        return service.page(new Page<>(current, size),wrapperConstructor().defaultWrapper());
    }

    /**
     * 支持指定类型返回进行转换
     *
     * @param service 分页服务对象
     * @param p       响应对象
     * @param <P>     返回类型
     * @return
     * @throws IllegalAccessException
     */
    public <P> IPage<P> startPageByParam(IService<T> service, Class<P> p) throws IllegalAccessException {
        return copyPageAndRsp(p, startPageByParam(service));
    }

    /**
     * 基于reqInstance实例属性值并可结合可扩展的wrapper构造使用
     *
     * @param service 分页服务对象
     * @return
     * @throws IllegalAccessException
     */
    private IPage<T> startPageByParam(IService<T> service, LambdaQueryWrapper<T> wrapper) throws IllegalAccessException {
        return service.page(new Page<>(current, size),wrapper);
    }


    /**
     * 结合可扩展的wrapper构造使用
     *
     * @param service 分页服务对象
     * @param p       响应对象
     * @param <P>     返回类型
     * @return
     * @throws IllegalAccessException
     */
    private <P> IPage<P> startPageByParam(IService<T> service, Class<P> p, LambdaQueryWrapper<T> wrapper) throws IllegalAccessException {
        return copyPageAndRsp(p, startPageByParam(service,wrapper));
    }


    /**
     * 返回指定类型的分页处理
     *
     * @param p    指定类型
     * @param page 分页对象
     * @return
     */
    private <P> Page<P> copyPageAndRsp(Class<P> p, IPage<T> page) {
        Page<P> rPage = new Page<>();
        BeanUtils.copyProperties(page, rPage);
        if (p != null) {
            rPage.setRecords(page.getRecords().stream().map(item -> JSONUtil.toBean(JSONUtil.toJsonStr(item), p)).collect(Collectors.toList()));
        }
        return rPage;
    }


    /**
     * 扩展分页查询逻辑
     */
    public class WrapperConstructor {

        private LambdaQueryWrapper<T> wrapper = defaultWrapper();

        @SneakyThrows
        public IPage<T> startPage(IService<T> service) {
            return RequestPageInstance.this.startPageByParam(service,wrapper);
        }

        @SneakyThrows
        public <P> IPage<P> startPage(IService<T> service, Class<P> p) {
            return RequestPageInstance.this.startPageByParam(service,p,wrapper);
        }

        /**
         * 默认分页参数
         *
         * @return
         */
        @SneakyThrows
        private LambdaQueryWrapper<T> defaultWrapper() {
            return parseParam().lambda();
        }

        /**
         * 扩展方法 排序
         * @param columns
         * @return
         */
        public WrapperConstructor orderByDesc(SFunction<T, ?> columns) {
            this.wrapper = wrapper.orderByDesc(columns);
            return this;
        }

        public WrapperConstructor orderByAsc(SFunction<T, ?> columns) {
            this.wrapper = wrapper.orderByAsc(columns);
            return this;
        }

        // 后续可以再次添加其他分页操作,建议不要太复杂,否则推荐使用原生分页sql


        /**
         * 根据实例 解析 wrapper参数
         *
         * @return
         * @throws IllegalAccessException
         */
        private QueryWrapper<T> parseParam() throws IllegalAccessException {
            QueryWrapper<T> wrapper = new QueryWrapper<>();
            Map<String, Object> map = SqlHandlerUtils.getInstanceKeyValue(RequestPageInstance.this.reqInstance);
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();
                if (value instanceof CharSequence) {
                    wrapper = NumberUtil.isNumber((CharSequence) value) ? wrapper.eq(key, value) : wrapper.like(key, value);
                } else if (value instanceof Number) {
                    wrapper = wrapper.eq(key, value);
                } else {
                    wrapper = wrapper.like(key, value);
                }
            }
            return wrapper;
        }
    }
    
    public void setCurrent(Long current) {
        if (current == null || current == 0) {
            this.current = 1L;
            return;
        }
        this.current = current;
    }

    public void setSize(Long size) {
        if (size == null || size == 0) {
            this.size = 10L;
            return;
        }
        this.size = size;
    }
}

2.2、解析分页实体工具类

代码:

package com.migu.migumall.utils;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.TableField;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;

/**
 * 数据库处理相关工具类
 *
 * @author kkmigu 664325656@qq.com
 */
public class SqlHandlerUtils {

    /**
     * 获取所有key:
     * 配置所有key: k: 默认由 实例的字段 转为驼峰命名方式 / @TableField 指定   --->过滤 @TableField(exist = false) | v is null
     * @param instance
     * @return
     * @throws IllegalAccessException
     */
    public static <T> HashMap<String, Object> getInstanceKeyValue(T instance) throws IllegalAccessException {
        // 存储实例中进行校验的 k v
        HashMap<String, Object> map = new HashMap<>();
        if (instance != null) {
            Field[] instanceFields = instance.getClass().getDeclaredFields();
            for (Field field : instanceFields) {
                // static | final 不操作
                if (Modifier.isFinal(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) {
                    continue;
                }
                field.setAccessible(true);
                Object o = field.get(instance);
                if (o == null) {
                    continue;
                }
                boolean b = field.isAnnotationPresent(TableField.class);
                // 如果存在该注解
                if (b) {
                    TableField annotationInstance = field.getAnnotation(TableField.class);
                    boolean isExist = annotationInstance.exist();
                    if (!isExist) {
                        continue;
                    }
                    String value = annotationInstance.value();
                    if (StrUtil.isNotBlank(value)) {
                        map.put(value, o);
                        continue;
                    }
                }
                // 执行驼峰转下划线
                map.put(StrUtil.toUnderlineCase(field.getName()), o);
            }
        }
        return map;
    }
}

2.3、返回实体类

返回实体接口代码:

package com.migu.migumall.common.result;

public interface Result<T> {
}

基本分页实现类:

package com.migu.migumall.common.result;

import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.Data;

import java.util.List;

@Data
public class CommonPageResult<T> implements Result<T>{

    private long code = 200;

    private String message = "success";

    /**
     * 总量
     */
    private long total;

    /**
     * 当前所在页码
     */
    private long current;

    /**
     * 页码数量
     */
    private long pageSize;

    /**
     * 数据集合
     */
    protected List<T> data;

    public CommonPageResult (IPage<T> page) {
        this.total = page.getTotal();
        this.data = page.getRecords();
        this.current = page.getCurrent();
        this.pageSize = page.getPages();
    }


    public static <T> CommonPageResult<T> success(IPage<T> page){
        return new CommonPageResult<T>(page);
    }

}

3、使用


第一个例子:根据请求参数进行分页

@ApiOperation("获取所有商品列表")
@PostMapping("list")
@PreAuthorize("hasAuthority('pms:product:read')")
public Result<PmsProduct> getList(@RequestBody RequestPageInstance<PmsProduct,PmsProductReq> pageInstance) throws IllegalAccessException {
    return CommonPageResult.success(pageInstance.startPageByParam(pmsProductService));
}

image.png

第二个例子:对于用户数据,密码等隐私信息我们不能返回,那么可指定返回类型进行分页

@ApiOperation("获取所有用户")
@PostMapping("list")
@PreAuthorize("hasAnyAuthority('ums:user:read')")
public Result<AdminParamRsp> getList(@RequestBody RequestPageInstance<UmsAdmin, AdminQueryParam> pageInstance) throws IllegalAccessException {
    return CommonPageResult.success(pageInstance.startPageByParam(umsAdminService, AdminParamRsp.class));
}

image.png

第三个例子:有时,我们需要查找最新订单,那么就可额外根据wrapper进行分页

@ApiOperation("获取所有订单")
@PostMapping("list")
@PreAuthorize("hasAuthority('oms:order:read')")
public Result<OmsOrder> getList(@RequestBody RequestPageInstance<OmsOrder,OmsOrderReq> pageInstance) throws IllegalAccessException {
    return CommonPageResult.success(
            pageInstance.wrapperConstructor().orderByDesc(OmsOrder::getCreateTime).startPage(omsOrderService)
    );
}

image.png

4、总结

该方案对于个人而言使用起来非常方便,最近一直在写CRUD,受不了了,就写了这一套工具出来

image.png

同时该实现也结合到作者本人正在学习的一个项目中,地址:migu-mall: 依据 https://github.com/macrozheng/mall-swarm 教程 学习搭建 (gitee.com)

在这之中也封装了一些基本的CRUD实现