java - QueryDsl JPAQuery Projections + Pageable + Predicate
问题描述
为了避免在实体之间定义不必要的关系,我们决定实现轻量级 CQRS。这意味着我们使用 QueryDslJPAQuery
和查询端的连接和 DTO 投影。
我很难@QueryDslPredicate
与Pageable
构造的JPAQuery
. 理想情况下,我希望能够根据预计的 DTO 类而不是源实体来表达谓词和可分页。
例如:
final QFieldOfExpertise expertise = new QFieldOfExpertise("expertise");
final QFieldOfExpertise speciality = new QFieldOfExpertise("speciality");
return new JPAQuery<>(entityManager)
.select(Projections.constructor(CaseDto.class,
case$.id,
case$.caseNumber.numberOfCase,
case$.businessInfo.businessNumber,
case$.businessInfo.businessName,
case$.financialReview.isNeeded,
case$.financialReview.assignedRequestId,
case$.technicalReview.isNeeded,
case$.technicalReview.assignedRequestId,
projectExpertise(expertise),
projectExpertise(speciality)
))
.from(case$)
.leftJoin(expertise)
.on(expertise.id.eq(case$.technicalReview.neededExpertise.expertiseId))
.leftJoin(speciality)
.on(speciality.id.eq(case$.technicalReview.neededExpertise.specialityId));
}
private static FactoryExpression<ExpertiseDto> projectExpertise(QFieldOfExpertise expertise) {
return Projections.constructor(ExpertiseDto.class, expertise.id, expertise.nameEn, expertise.nameFr).skipNulls();
}
什么是正确的方法,以便我们可以使用@QueryDslPredicate(root = CaseDto.class)
或表达Pageable
DTO 根中的排序字段,例如?sort=businessNumber,asc
?
到目前为止,我唯一的想法是创建一个额外的抽象层来定义select
允许捕获目标到源映射的语句,以便我可以(与访问者)将 DTO 表示的谓词转换为源实体谓词。我可能不得不做一些类似的事情来转换 DTO 表示的可分页排序。
例如
public final class QueryMappings<D> {
private final Class<D> type;
private final List<Expression<?>> expressions;
private final Map<Path<?>, Path<?>> targetToSourceMappings;
private QueryMappings(Class<D> type) {
this.type = type;
this.expressions = new ArrayList<>();
this.targetToSourceMappings = new HashMap<>();
}
public <T> QueryMappings<D> map(Path<T> source, Path<T> target) {
expressions.add(Expressions.as(source, target));
this.targetToSourceMappings.put(target, source);
return this;
}
public <T> QueryMappings<D> map(QueryMappings<T> source, Path<T> target) {
this.expressions.add(Expressions.as(source.toProjection(), target));
for (Map.Entry<Path<?>, Path<?>> entry : source.targetToSourceMappings().entrySet()) {
final Path<?> targetPath = entry.getKey();
final Path<?> absTargetPath = replaceParent(targetPath, target);
this.targetToSourceMappings.put(absTargetPath, entry.getValue());
}
return this;
}
private static <T> Path<T> replaceParent(Path<T> path, Path<?> parent) {
PathMetadata metadata = new PathMetadata(parent, path.getMetadata().getElement(),
path.getMetadata().getPathType());
return ExpressionUtils.path(path.getType(), metadata);
}
static <D> QueryMappings<D> of(Class<D> type) {
return new QueryMappings<>(type);
}
public FactoryExpression<D> toProjection() {
return Projections.bean(type, expressions.toArray(new Expression<?>[expressions.size()])).skipNulls();
}
public Function<Predicate, Predicate> toPredicateConverter() {
PathMapperVisitor pathMapperVisitor = new PathMapperVisitor(targetToSourceMappings());
return (source) -> (Predicate)source.accept(pathMapperVisitor, null);
}
protected Map<Path<?>, Path<?>> targetToSourceMappings() {
return Collections.unmodifiableMap(targetToSourceMappings);
}
}
@RequiredArgsConstructor
final class PathMapperVisitor extends ReplaceVisitor<Void> {
private final Map<Path<?>, Path<?>> mappings;
@Override
public Expression<?> visit(Path<?> path, @Nullable Void context) {
if (mappings.containsKey(path)) {
return mappings.get(path);
}
return super.visit(path, context);
}
}
然后我可以做类似的事情......
QueryMappings<EntityDto> mappings = QueryMappings.of(EntityDto.class)
.map(QEntity.entity.someProperty, QEntityDto.entityDto.someProperty)
.map(QueryMappings.of(NestedEntityDto.class)
.map(QNestedEntity.nestedEntity.nestedProp, QNestedEntityDto.nestedEntityDto.nestedProp),
QEntityDto.entityDto.nested
);
// DTO-rooted predicate
Predicate targetPredicate = QEntityDto.nested.nestedProp.eq(...);
// Entity-rooted predicate
Predicate sourcePredicate = mappings.toPredicateConverter().apply(dtoPredicate);
new JPAQuery<>().select(mappings.toProjection())
.from(...)
.where(sourcePredicate);
我还没有彻底测试过,也有一些我没有涵盖的边缘情况,但这是我现在能想到的唯一途径,而且对于我希望做起来很简单的事情来说,这似乎是很多样板,所以我'我想知道我是否错过了什么?
解决方案
推荐阅读
- sql - 通过参数传递列名时删除引号
- java - IntelliJ 中的缩短命令行不起作用
- java - SqlException: DELETE on table 'COUPON' caused a violation of foreign key constraint
- java - Mediastore albums and media columns
- angularjs - Comparing two object arrays in angular ng-repeat
- javascript - 浏览器性能问题以及使用 Bodymovin 和 CSS 动画
- bash - 詹金斯管道循环执行ssh命令
- inno-setup - 无法在 Inno Setup 'AppPublisher' 指令中显示外语
- python - Python - Groupby a DataFrameGroupBy object
- unity3d - Moving GameObject with RayCast hit position causing object to move towards Raycast start position