1. ORM单表CRUD
在流行的JAVA ORM框架中有Mybatis以及JPA,其中JPA提供了JpaRepository
JpaSpecificationExecutor
等接口,提供了大量单表基础CRUD方法。而在Mybatis中,虽然官方提供的功能有限,但实际的第三方插件例如Mybatis-Plus
、通用Mapper等也提供极其丰富的方法。
以Jpa为例,一个继承JpaRepository
JpaSpecificationExecutor
接口的BaseRepository就包含如下方法
2. 单表复杂分页列表需求
如下图,我们常见的一个分页列表需求
分析请求参数如下:
- 分页参数:起始页,页大小
- 查询条件参数:属性名,属性值,逻辑条件
- 排序参数 :属性名,排序类型
返回参数:
- 分页统计数据:当前页,页大小,记录数 ,页数
- 数据列表
根据上述需求,可以发现,这类单表的高级分页列表查询的需求很常见,JPA提供的抽象方法均可组合实现其功能,但没有提供一个“万能”的方法。
3. 单表分页列表方法实现思路
-
提供自定义请求参数的封装,如下
-
提供自定义统一分页返回数据
注意:其中返回数据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 效果展示
请求参数示例
返回参数示例