首页 > Java > java教程 > 正文

QueryDSL分组查询与复杂DTO投影:实现按状态聚合技术列表

碧海醫心
发布: 2025-11-07 13:15:18
原创
215人浏览过

querydsl分组查询与复杂dto投影:实现按状态聚合技术列表

本文深入探讨了如何使用QueryDSL实现对实体进行分组查询,并将分组结果投影到包含嵌套列表的复杂数据传输对象(DTO)中。通过详细的代码示例,我们展示了如何利用QueryDSL的`GroupBy`功能来聚合数据,以及如何将聚合后的`Map`结构转换为目标DTO列表,从而解决直接使用`Projections.constructor`处理嵌套列表的编译问题。

在使用Spring Data JPA和QueryDSL进行开发时,我们经常会遇到需要对数据进行分组,并以特定结构(例如,包含子列表的DTO)返回结果的场景。本教程将以一个具体的例子,展示如何将Technology实体按technologyStatus进行分组,并将每个状态下的技术列表投影到一个TechnologyByStatusDTO对象中。

实体与DTO定义

首先,我们定义相关的实体和DTO。

1. Technology 实体

Technology实体包含技术的基本信息、状态、所属分类、坐标以及关联的项目列表。

package com.example.technologyradar.model;

import com.example.technologyradar.dto.constant.TechnologyStatus;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;
import java.util.List;

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Technology {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
    @GenericGenerator(name="native", strategy = "native")
    private Long id;

    private String name;

    @Enumerated(EnumType.STRING)
    private TechnologyStatus technologyStatus;

    @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST, targetEntity = Category.class)
    @JoinColumn(name="category_id", referencedColumnName = "id", nullable = false)
    private Category category;

    @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST, targetEntity = Coordinate.class)
    @JoinColumn(name="coordinate_id", referencedColumnName = "id", nullable = false)
    private Coordinate coordinate;

    @ManyToMany
    @JoinTable(
            name = "projects_technologies",
            joinColumns = @JoinColumn(name="technology_id"),
            inverseJoinColumns = @JoinColumn(name="project_id")
    )
    private List<Project> projects;
}
登录后复制

2. TechnologyStatus 枚举

一个简单的枚举类型,用于表示技术的状态。

package com.example.technologyradar.dto.constant;

public enum TechnologyStatus {
    ADOPT, TRIAL, ASSESS, HOLD
}
登录后复制

3. TechnologyBasicDataDTO DTO

这个DTO用于表示技术的基本数据,通常是Technology实体的一个精简视图。

package com.example.technologyradar.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TechnologyBasicDataDTO {
    private Long id;
    private String name;
    // 可以根据需要添加其他基本字段
}
登录后复制

4. TechnologyByStatusDTO DTO

飞书多维表格
飞书多维表格

表格形态的AI工作流搭建工具,支持批量化的AI创作与分析任务,接入DeepSeek R1满血版

飞书多维表格 26
查看详情 飞书多维表格

这是我们最终希望得到的目标DTO,它包含一个TechnologyStatus和该状态下所有TechnologyBasicDataDTO的列表。

package com.example.technologyradar.dto;

import com.example.technologyradar.dto.constant.TechnologyStatus;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TechnologyByStatusDTO {
    private TechnologyStatus status;
    private List<TechnologyBasicDataDTO> technologies;
}
登录后复制

QueryDSL分组查询的挑战

最初尝试使用QueryDSL进行分组查询时,开发者可能会尝试如下方式:

// 假设 jpaQueryFactory 已初始化,QTechnology technology = QTechnology.technology;
public List<TechnologyByStatusDTO> getTechnologyByStatusIncorrectAttempt() {
    return jpaQueryFactory.from(technology)
            .groupBy(technology.technologyStatus)
            .select(Projections.constructor(TechnologyByStatusDTO.class,
                    technology.technologyStatus,
                    // 尝试在这里直接构造 List<TechnologyBasicDataDTO>,这会导致编译错误
                    list(TechnologyBasicDataDTO.class))) // 编译失败!
            .fetch();
}
登录后复制

问题在于,QueryDSL的Projections.constructor主要用于将单个查询结果行映射到DTO,它不直接支持在select子句中聚合一个List。groupBy操作本身会产生多行结果,QueryDSL需要一种更明确的方式来处理这种聚合和嵌套投影。

使用QueryDSL GroupBy 转换实现复杂投影

QueryDSL提供了GroupBy功能,专门用于处理这种分组和聚合场景。它允许我们先对数据进行分组,然后将每个组的结果转换为所需的结构,通常是一个Map。

1. 核心思路

使用queryFactory.transform(GroupBy.groupBy(keyExpression).as(GroupBy.list(valueExpression)))。

  • groupBy(keyExpression):指定分组的键,这里是technology.technologyStatus。
  • as(GroupBy.list(valueExpression)):指定每个组的值如何聚合。GroupBy.list()用于将组内的所有项收集到一个列表中。valueExpression可以是一个实体本身,或者是一个使用Projections构造的DTO。

2. 实现代码

package com.example.technologyradar.repository.impl;

import com.example.technologyradar.dto.TechnologyBasicDataDTO;
import com.example.technologyradar.dto.TechnologyByStatusDTO;
import com.example.technologyradar.dto.constant.TechnologyStatus;
import com.example.technologyradar.model.QTechnology;
import com.querydsl.core.group.GroupBy;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Repository
public class TechnologyRepositoryImpl {

    private final JPAQueryFactory jpaQueryFactory;
    private final QTechnology technology = QTechnology.technology;
    // QCoordinate coordinate = QCoordinate.coordinate; // 如果需要关联查询,可以引入

    public TechnologyRepositoryImpl(JPAQueryFactory jpaQueryFactory) {
        this.jpaQueryFactory = jpaQueryFactory;
    }

    public List<TechnologyByStatusDTO> getTechnologyByStatus() {
        // 1. 使用 QueryDSL 的 GroupBy 功能进行分组和投影
        Map<TechnologyStatus, List<TechnologyBasicDataDTO>> groupedResults = jpaQueryFactory
                .from(technology)
                // 如果需要关联查询,可以在这里添加 .innerJoin(technology.coordinate, coordinate) 等
                .transform(
                        GroupBy.groupBy(technology.technologyStatus) // 按 technologyStatus 分组
                                .as(
                                        GroupBy.list(
                                                Projections.constructor( // 投影每个组内的 TechnologyBasicDataDTO
                                                        TechnologyBasicDataDTO.class,
                                                        technology.id,
                                                        technology.name
                                                )
                                        )
                                )
                );

        // 2. 将 Map<TechnologyStatus, List<TechnologyBasicDataDTO>> 转换为 List<TechnologyByStatusDTO>
        return groupedResults.entrySet().stream()
                .map(entry -> new TechnologyByStatusDTO(entry.getKey(), entry.getValue()))
                .collect(Collectors.toList());
    }
}
登录后复制

代码解析:

  1. jpaQueryFactory.from(technology): 指定查询的根实体。
  2. .transform(...): 这是QueryDSL GroupBy的核心方法。它接收一个GroupBy表达式,并返回一个聚合后的结果。
  3. GroupBy.groupBy(technology.technologyStatus): 定义分组的键,即technologyStatus。
  4. .as(GroupBy.list(Projections.constructor(TechnologyBasicDataDTO.class, technology.id, technology.name))): 定义每个组的值如何聚合。
    • GroupBy.list(...): 表示将当前组中的所有符合条件的项收集到一个List中。
    • Projections.constructor(TechnologyBasicDataDTO.class, technology.id, technology.name): 这是内层投影,用于将每个Technology实体投影为TechnologyBasicDataDTO。它会根据构造函数参数的顺序和类型匹配technology.id和technology.name字段。
  5. 返回类型 Map<TechnologyStatus, List<TechnologyBasicDataDTO>>: transform方法的结果是一个Map,其中键是分组的依据(TechnologyStatus),值是每个组对应的TechnologyBasicDataDTO列表。
  6. 流式API转换: 最后,我们使用Java 8的流式API将这个Map转换成我们最终需要的List<TechnologyByStatusDTO>。通过遍历Map.entrySet(),为每个键值对创建一个TechnologyByStatusDTO实例。

注意事项与总结

  1. Q类生成: 确保您的项目中已配置QueryDSL APT插件,以便自动生成QTechnology等Q类。这些类是QueryDSL进行类型安全查询的基础。
  2. JPAQueryFactory初始化: JPAQueryFactory需要一个EntityManager来构造。在Spring环境中,通常通过构造函数注入EntityManager来初始化它。
  3. 性能考虑: GroupBy.list()会在内存中构建列表。对于非常大的数据集,如果单个组的列表过大,可能会有内存消耗问题。在大多数常见场景下,这种方法是高效且实用的。
  4. 高级场景: 如果您的DTO投影需求更加复杂,例如涉及多层嵌套的集合或复杂的聚合函数,可以考虑使用像Blaze-Persistence Entity Views这样的高级库。它提供了更强大的DTO映射能力,可以直接将复杂的查询结果映射到嵌套的DTO结构,而无需手动转换Map。
  5. 关联查询: 如果TechnologyBasicDataDTO需要Technology实体中关联实体的字段,例如category.name,可以在from之后添加innerJoin(technology.category, category),然后在Projections.constructor中包含category.name。

通过以上方法,我们能够有效地利用QueryDSL的GroupBy功能,实现按指定字段分组并投影到包含嵌套列表的复杂DTO中,从而满足各种数据聚合和展示的需求。

以上就是QueryDSL分组查询与复杂DTO投影:实现按状态聚合技术列表的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号