首页 > 解决方案 > QueryDsl JPAQuery Projections + Pageable + Predicate

问题描述

为了避免在实体之间定义不必要的关系,我们决定实现轻量级 CQRS。这意味着我们使用 QueryDslJPAQuery和查询端的连接和 DTO 投影。

我很难@QueryDslPredicatePageable构造的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)或表达PageableDTO 根中的排序字段,例如?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);

我还没有彻底测试过,也有一些我没有涵盖的边缘情况,但这是我现在能想到的唯一途径,而且对于我希望做起来很简单的事情来说,这似乎是很多样板,所以我'我想知道我是否错过了什么?

标签: javaspringquerydsl

解决方案


推荐阅读