使用场景
在使用 Spring Data JPA + QueryDSL 构建后台分页查询时,前端传入的排序参数(如按某字段升序/降序排列)需要转换为 QueryDSL 的 OrderSpecifier。
通常的做法是在每个 Service 中手动转换,或者针对每个实体写专门的转换逻辑,导致大量重复代码。本工具方法封装了通用的转换逻辑,可直接复用。
前端参数格式
前端传递排序参数时,sort 格式为数组,每个元素为 字段名,排序方向:
javascript
// 单字段排序
sort: ['year,desc']
// 多字段排序(优先级从左到右)
sort: ['year,desc', 'id,desc']
// 升序
sort: ['entryDate,asc']
后端 Spring MVC 通过 Pageable 接收后,自动解析为 org.springframework.data.domain.Sort 对象。
代码实现
PageUtil.java
java
package com.server.common.util;
import cn.hutool.core.util.ObjectUtil;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.dsl.*;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 分页工具类
*/
public class PageUtil {
private static final int DEFAULT_PAGE_SIZE = 10;
private static final Page<?> EMPTY_PAGE = new PageImpl<>(Collections.emptyList(), PageRequest.of(0, DEFAULT_PAGE_SIZE), 0);
// ... 其他空分页方法 ...
/**
* 将 Spring Data Sort 转换为 QueryDSL 的 OrderSpecifier
*
* @param sort Spring Data Sort 对象
* @param entityPathBase QueryDSL EntityPathBase(如 QUser.user)
* @return OrderSpecifier 数组
*/
public static <T> OrderSpecifier<?>[] toOrderSpecifiers(Sort sort, EntityPathBase<T> entityPathBase) {
List<OrderSpecifier<?>> orderSpecifiers = new ArrayList<>();
if (sort.isUnsorted()) {
return new OrderSpecifier[0];
}
PathBuilder<T> pathBuilder = new PathBuilder<>(entityPathBase.getType(), entityPathBase.getMetadata().getName());
for (Sort.Order order : sort) {
String property = order.getProperty();
ComparableExpressionBase<?> path = pathBuilder.getComparable(property, Comparable.class);
if (order.isAscending()) {
orderSpecifiers.add(path.asc());
} else {
orderSpecifiers.add(path.desc());
}
}
return orderSpecifiers.toArray(new OrderSpecifier[0]);
}
}
使用示例
Service 实现
java
@Override
public Page<UserLeaveQuotaVo> findPageByParam(UserLeaveQuotaParam param, Pageable pageable) {
QUserLeaveQuota q = QUserLeaveQuota.userLeaveQuota;
QUser qUser = QUser.user;
BooleanBuilder predicate = getPredicate(param);
// 使用 PageUtil.toOrderSpecifiers 转换排序参数
List<Tuple> tuples = queryFactory
.select(q, qUser.nickName, qUser.gender)
.from(q)
.leftJoin(qUser).on(q.userId.eq(qUser.id))
.where(predicate)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(PageUtil.toOrderSpecifiers(pageable.getSort(), q))
.fetch();
// count query ...
return new PageImpl<>(voList, pageable, total);
}
Vue 前端排序处理
javascript
const sortStr = ref(['year,desc', 'id,desc'])
const handleSortChange = ({prop, order}) => {
if (order) {
const sortOrder = order === 'ascending' ? 'asc' : 'desc'
sortStr.value = [prop + ',' + sortOrder, 'id,desc']
} else {
sortStr.value = ['year,desc', 'id,desc']
}
loadData()
}
const loadData = async () => {
const params = {
...queryParams.value,
page: currentPage.value - 1,
size: pageSize.value,
sort: sortStr.value
}
const data = await api.page(params)
// ...
}
关键点说明
| 要点 | 说明 |
|---|---|
EntityPathBase |
直接传入 QueryDSL 生成的 Q 类(如 QUser.user),利用其元数据构建 PathBuilder |
getComparable |
解决泛型 Comparable 约束问题,避免强制类型转换 |
isUnsorted |
如果前端未传入排序参数,直接返回空数组 |
| 返回类型 | OrderSpecifier[],直接可用于 QueryDSL 的 orderBy() 方法 |
环境版本
| 组件 | 版本 |
|---|---|
| QueryDSL | 5.0.0 |
| Spring Data JPA | 3.x |
| Hibernate | 6.x |