单表CRUD通用分页service方法抽取(MP、JPA支持)

1. ORM单表CRUD

在流行的JAVA ORM框架中有Mybatis以及JPA,其中JPA提供了JpaRepository JpaSpecificationExecutor等接口,提供了大量单表基础CRUD方法。而在Mybatis中,虽然官方提供的功能有限,但实际的第三方插件例如Mybatis-Plus 、通用Mapper等也提供极其丰富的方法。

以Jpa为例,一个继承JpaRepository JpaSpecificationExecutor接口的BaseRepository就包含如下方法

image.png

2. 单表复杂分页列表需求

如下图,我们常见的一个分页列表需求


image.png

分析请求参数如下:

  • 分页参数:起始页,页大小
  • 查询条件参数:属性名,属性值,逻辑条件
  • 排序参数 :属性名,排序类型

返回参数:

  • 分页统计数据:当前页,页大小,记录数 ,页数
  • 数据列表

根据上述需求,可以发现,这类单表的高级分页列表查询的需求很常见,JPA提供的抽象方法均可组合实现其功能,但没有提供一个“万能”的方法。

3. 单表分页列表方法实现思路

  • 提供自定义请求参数的封装,如下


    image.png
  • 提供自定义统一分页返回数据


    image.png

    注意:其中返回数据UserDTO为泛型数据

  • 根据实际项目框架,开发BaseController BaseService BaseServiceImpl BaseRepository等基础MVC类,并添加Page方法,Page分页逻辑在BaseServiceImpl中实现,具体代码如下

package com.tba.sc.common.base.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.kichun.common.base.assembler.BaseAssembler;
import com.kichun.common.base.dto.BaseDTO;
import com.kichun.common.base.dto.page.EnumOperateType;
import com.kichun.common.base.dto.page.PageParameter;
import com.kichun.common.base.dto.page.PageResponse;
import com.kichun.common.base.entity.BaseEntity;
import com.kichun.common.base.repository.BaseRepository;
import com.kichun.common.base.service.BaseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.domain.JpaSort;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class BaseServiceImpl<D extends BaseDTO, E extends BaseEntity, ID extends Serializable> implements BaseService<D, E, ID> {
    @Autowired
    private BaseRepository<E, ID> baseRepository;

    @Autowired
    private BaseAssembler<D, E> baseAssembler;

    @Override
    public PageResponse<D> page(PageParameter pageParam) {
        //初始化当前页及页大小参数
        Integer page = ObjectUtil.isNotNull(pageParam) ? ObjectUtil.isNotNull(pageParam.getPageidx()) ? pageParam.getPageidx() > 0 ? pageParam.getPageidx() : 0 : 0 : 0;
        Integer size = ObjectUtil.isNotNull(pageParam) ? ObjectUtil.isNotNull(pageParam.getPagesize()) ? pageParam.getPagesize() > 0 ? pageParam.getPagesize() : 10 : 10 : 10;

        //将自定义排序SORT参数封装为JPA Sort.Order对象
        List<Sort.Order> orders = new ArrayList<>();
        if (CollUtil.isNotEmpty(pageParam.getSort())) {
            pageParam.getSort().forEach(sort -> {
                if (Sort.Direction.DESC.toString().equals(sort.getSortType())) {
                    orders.add(Sort.Order.desc(sort.getField()));
                } else {
                    orders.add(Sort.Order.asc(sort.getField()));
                }
            });
        }
        //根据当前页,页大小,排序参数封装JPA PageRequest对象
        PageRequest pageRequest = PageRequest.of(page, size, JpaSort.by(orders));
        Page<E> pageResult = null;

        //封装查询条件
        if (CollUtil.isNotEmpty(pageParam.getQuery())) {
            pageResult = baseRepository.findAll(new Specification<E>() {
                @Override
                public Predicate toPredicate(Root<E> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                    List<Predicate> predicates = new ArrayList();
                    pageParam.getQuery().forEach(query -> {
                        if (StrUtil.isBlank(query.getField()) || StrUtil.isBlank(query.getValue()) || StrUtil.isBlank(query.getOperateType())) {
                            return;
                        }
                        if (EnumOperateType.EQ.toString().equals(query.getOperateType())) {
                            predicates.add(criteriaBuilder.equal(root.get(query.getField()), query.getValue()));
                        } else if (EnumOperateType.GT.toString().equals(query.getOperateType())) {
                            predicates.add(criteriaBuilder.gt(root.get(query.getField()), Long.valueOf(query.getValue())));
                        } else if (EnumOperateType.LT.toString().equals(query.getOperateType())) {
                            predicates.add(criteriaBuilder.lt(root.get(query.getField()), Long.valueOf(query.getValue())));
                        } else if (EnumOperateType.NOT_EQ.toString().equals(query.getOperateType())) {
                            predicates.add(criteriaBuilder.notEqual(root.get(query.getField()), query.getValue()));
                        } else if (EnumOperateType.LIKE.toString().equals(query.getOperateType())) {
                            predicates.add(criteriaBuilder.like(root.get(query.getField()).as(String.class), "%" + query.getValue() + "%"));
                        } else if (EnumOperateType.IN.toString().equals(query.getOperateType())) {
                            String[] split = query.getValue().split(",");
                            if (ArrayUtil.isNotEmpty(split)) {
                                predicates.add(criteriaBuilder.in(root.get(query.getField()).in(split)));
                            }
                        }
                        if (query.isOr()) {
                            criteriaBuilder.or(predicates.toArray(new Predicate[predicates.size()]));
                        } else {
                            criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
                        }
                    });
                    return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
                }
            }, pageRequest);
        } else {
            pageResult = baseRepository.findAll(pageRequest);
        }

        //封装自定义分页返回参数
        PageResponse<D> pageResponse = new PageResponse<>();
        pageResponse.setPageIndex(new Long(pageResult.getPageable().getPageNumber()));
        pageResponse.setTotal(new Long(pageResult.getTotalElements()));
        pageResponse.setTotalPage(new Long(pageResult.getTotalPages()));
        if (CollUtil.isNotEmpty(pageResult.getContent())) {
            pageResponse.setPageData(baseAssembler.toDTOs(pageResult.getContent()));
        }
        return pageResponse;
    }
}

4. 参数实体代码

EnumOperateType

public enum EnumOperateType {
    EQ,
    NOT_EQ,
    LIKE,
    GT,
    LT,
    IN,
    NOT_IN;
    EnumOperateType() {
    }
}

PageParameter

@Data
public class PageParameter implements Serializable {

    @ApiModelProperty(value = "请求页,默认为0")
    private Integer pageidx = 0;

    @ApiModelProperty(value = "分页大小,默认为10")
    private Integer pagesize = 10;

    @ApiModelProperty(value = "查询参数")
    private List<QueryParam> query;

    @ApiModelProperty(value = "排序参数")
    private List<SortParam> sort;
}

QueryParam

/**
 * 查询参数dto,封装一个查询的字段,支持且或条件,支持EQ NOT_EQ LIKE GT LT IN NOT_IN等判断
 */
@Data
public class QueryParam implements Serializable {

    @ApiModelProperty(value = "查询条件是否为或,默认为且")
    private boolean or;

    @ApiModelProperty(value = "查询的字段名")
    private String field;

    @ApiModelProperty(value = "匹配类型:EQ 等于,NOT_EQ 不等于,LIKE 模糊查询,GT大于,LT小于 ,IN在其中,NOT_IN不在其中value为,分隔字符串")
    private String operateType;

    @ApiModelProperty(value = "匹配值")
    private String value;
}

SortParam

@Data
public class SortParam implements Serializable {

    @ApiModelProperty(value = "排序字段")
    private String field;

    @ApiModelProperty(value = "排序类型:ASC DESC 默认ASC")
    private String sortType = "ASC";
}

PageResponse

public class PageResponse<T> {

    /**
     * 总页数
     * 当前页页码
     * 每一页的数据
     */
    private Long total;
    private Long totalPage;
    private Long pageIndex;
    private List<T> pageData;
}

5 效果展示

请求参数示例


image.png

返回参数示例


image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,859评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,772评论 2 374
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,956评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,729评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,611评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,441评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,850评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,485评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,770评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,803评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,598评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,434评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,856评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,065评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,365评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,852评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,065评论 2 338

推荐阅读更多精彩内容