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

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@NoArgsConstructorpublic 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 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@NoArgsConstructorpublic 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@NoArgsConstructorpublic class TechnologyByStatusDTO {    private TechnologyStatus status;    private List technologies;}

QueryDSL分组查询的挑战

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

// 假设 jpaQueryFactory 已初始化,QTechnology technology = QTechnology.technology;public List getTechnologyByStatusIncorrectAttempt() {    return jpaQueryFactory.from(technology)            .groupBy(technology.technologyStatus)            .select(Projections.constructor(TechnologyByStatusDTO.class,                    technology.technologyStatus,                    // 尝试在这里直接构造 List,这会导致编译错误                    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;@Repositorypublic 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 getTechnologyByStatus() {        // 1. 使用 QueryDSL 的 GroupBy 功能进行分组和投影        Map<TechnologyStatus, List> 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> 转换为 List        return groupedResults.entrySet().stream()                .map(entry -> new TechnologyByStatusDTO(entry.getKey(), entry.getValue()))                .collect(Collectors.toList());    }}

代码解析:

jpaQueryFactory.from(technology): 指定查询的根实体。.transform(…): 这是QueryDSL GroupBy的核心方法。它接收一个GroupBy表达式,并返回一个聚合后的结果。GroupBy.groupBy(technology.technologyStatus): 定义分组的键,即technologyStatus。.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字段。返回类型 Map<TechnologyStatus, List>: transform方法的结果是一个Map,其中键是分组的依据(TechnologyStatus),值是每个组对应的TechnologyBasicDataDTO列表。流式API转换: 最后,我们使用Java 8的流式API将这个Map转换成我们最终需要的List。通过遍历Map.entrySet(),为每个键值对创建一个TechnologyByStatusDTO实例。

注意事项与总结

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

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/575048.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
七日世界官网入口是什么 七日世界网易官方网站
上一篇 2025年11月10日 07:59:59
linux系统入门学习教程推荐
下一篇 2025年11月10日 08:00:08

相关推荐

  • Java TreeMap如何自定义排序规则

    TreeMap默认按键的自然顺序排序,可通过构造函数传入Comparator自定义排序规则。例如字符串可按长度排序:TreeMap map = new TreeMap((s1, s2) -> s1.length() – s2.length()); 对自定义对象如Person可按年龄…

    2026年9月22日
    000
  • 如何使用MLflow训练AI大模型?模型管理与跟踪的实用教程

    如何使用MLflow训练AI大模型?模型管理与跟踪的实用教程如何使用MLflow训练AI大模型?模型管理与跟踪的实用教程如何使用MLflow训练AI大模型?模型管理与跟踪的实用教程如何使用MLflow训练AI大模型?模型管理与跟踪的实用教程

    MLflow通过实验跟踪、可复现的项目封装、标准化模型格式和集中式模型注册表,实现大模型训练的全流程管理。它记录超参数、指标和模型文件,支持分布式环境下的集中日志管理,利用远程跟踪服务器和云存储统一收集数据,并通过模型版本控制与阶段管理提升团队协作与部署效率。 ☞☞☞AI 智能聊天, 问答助手, A…

    2026年9月22日 用户投稿
    000
  • Java Collections.synchronizedList方法如何保证线程安全

    synchronizedList通过同步方法保证线程安全,使用synchronized关键字对每个操作加锁,确保单个操作的原子性;但迭代或复合操作需手动同步,否则可能引发并发异常;其性能较低,适用于读多写少、并发不高的场景,高并发下推荐使用CopyOnWriteArrayList。 Java 中 C…

    2026年9月22日
    100
  • 为什么建议手动定义Java序列化ID

    手动定义serialVersionUID可确保序列化兼容性,避免因类结构变化导致反序列化失败。Java默认生成的ID依赖类名、字段等信息,编译环境或代码微小改动均使其改变,易引发InvalidClassException。显式声明后,可在兼容性变更时主动控制ID更新,保留原ID则允许旧版本读取新对象…

    2026年9月22日
    200
  • 在Java中如何统计List中元素出现次数

    答案是使用Map或Stream API统计List元素频次最高效。通过HashMap手动遍历统计,或用Java 8的Stream结合groupingBy和counting()实现简洁计数,Collections.frequency适用于小数据量但性能较差,推荐Stream方式兼顾性能与可读性。 在J…

    2026年9月22日
    900
  • Java中如何区分逻辑错误和系统异常

    系统异常是程序运行中由JVM抛出的RuntimeException,如空指针、数组越界,会导致程序中断并打印堆栈;逻辑错误是程序语法正确但结果不符预期,如条件写反、循环次数错误,不会崩溃但行为异常。两者区别在于是否抛出异常、是否中断执行及调试方式不同,需通过防御性编程、单元测试和日志调试加以防范。 …

    2026年9月22日
    000
  • Spring Boot 应用中的单元测试、Mockito 和集成测试:最佳实践

    第一段引用上面的摘要: 本文旨在帮助初学者理解在 Spring Boot 应用中何时以及如何使用 JUnit、Mockito 和集成测试。我们将探讨这些测试框架在 Controller、Service 和 Repository 层中的应用,并提供示例说明何时使用 Mockito 模拟对象,以及何时使…

    2026年9月22日
    000
  • 如何查询命令所属包 yum provides反向查找

    如何查询命令所属包 yum provides反向查找如何查询命令所属包 yum provides反向查找如何查询命令所属包 yum provides反向查找如何查询命令所属包 yum provides反向查找

    使用 yum provides 可以查找某个命令或文件属于哪个软件包,解决“command not found”问题。1. 使用时建议带上完整路径,如 yum provides /usr/sbin/ifconfig;2. 支持通配符模糊查找,如 yum provides */python3;3. 若…

    2026年9月22日 用户投稿
    100
  • Karate框架中处理带方括号和日期范围的GET请求参数

    本文旨在解决Karate框架中构建包含复杂、带方括号(如filters[start_date])及日期范围的GET请求参数时遇到的URL编码问题。通过对比直接定义查询对象和使用param关键字的方法,详细阐述了如何正确地构造URL,确保参数格式符合预期,从而有效进行API测试。 1. 问题背景与挑战…

    2026年9月22日
    000
  • 谷歌浏览器窗口透明边框显示异常如何修复

    首先尝试修改快捷方式添加–disable-gpu –disable-software-rasterize参数,若可正常运行则关闭硬件加速,并重置chrome://flags实验功能及清除ShaderCache缓存文件。 谷歌浏览器出现窗口透明边框显示异常,通常和硬件加速或GP…

    2026年9月22日
    000
  • 家庭NAS搭建:硬件选型与RAID模式对传输速度的影响

    家庭NAS搭建需综合考虑CPU、内存、硬盘接口、网络和RAID模式。CPU至少四核,内存8GB起,推荐N5105/N100或AMD嵌入式处理器;千兆网口成瓶颈,应升级至2.5G/10G;SATA III限制SSD性能,建议支持NVMe主板。RAID 0提升速度但无冗余,RAID 1保障安全但写速低,…

    2026年9月22日
    100
  • Android自定义开关UI实现教程

    本文详细介绍了在Android应用中实现自定义开关UI的两种主要方法:一是通过集成第三方库如StickySwitch,快速实现美观且功能丰富的开关;二是通过结合Drawable XML和ToggleButton,实现高度定制化的开关外观。文章提供了详细的代码示例和配置说明,旨在帮助开发者灵活地创建符…

    2026年9月22日
    000
  • 如何用RunwayML导出AI生成的图片?高效保存图像的实用教程

    导出RunwayML生成的图片需先完成生成任务并进入详情视图,点击“下载”选择PNG或JPG等格式,推荐PNG以保留高质量细节;批量导出时使用多选功能统一设置分辨率和格式,提升效率;建议采用项目化文件夹结构与规范化命名规则管理海量图片,并利用标签、云同步辅助整理;后续应用中可结合Photoshop、…

    2026年9月22日
    100
  • 在Java中如何对集合进行分区处理

    Java中集合分区是将大集合拆分为小集合,适用于并行处理、分页等场景;2. 可使用Guava库的Lists.partition()快速实现,但返回的是原列表视图,修改会影响原数据;3. 也可用Java 8 Stream结合IntStream和Collectors自定义分区,灵活性高;4. 按条件分区…

    2026年9月22日
    300
  • google浏览器如何导入其他浏览器的书签和密码_google浏览器导入书签和密码方法

    首先使用Google浏览器内置导入功能迁移书签和密码,选择源浏览器并勾选数据类型后导入;若无法识别,则通过HTML文件导入书签;密码可手动导出为CSV文件并在密码管理器中导入。 如果您需要将其他浏览器中的书签或密码迁移到 Google 浏览器,可以通过内置的导入功能快速完成数据转移。该操作适用于更换…

    2026年9月22日
    700
  • Karate教程:优雅处理GET请求中的复杂查询参数(含日期范围)

    本教程将详细介绍在Karate框架中如何正确发送包含复杂查询参数(特别是带有方括号的参数名,如filters[start_date])的GET请求。我们将通过实际示例,演示如何利用Karate的* param关键字优雅地构建URL,确保参数被正确编码并传递给后端服务,尤其适用于日期范围等场景。 理解…

    2026年9月22日
    200
  • Java项目中利用.class文件:Classpath配置与接口实现

    在Java项目中引用并实现来自.class文件的接口是常见的需求,尤其当仅提供编译后的字节码文件时。本文将深入讲解Java Classpath的核心概念及其重要性,并提供在命令行环境下配置Classpath的详细步骤和示例,确保编译器和JVM能够正确找到并加载所需的.class文件,从而顺利完成接口…

    2026年9月22日
    800
  • 怎样在iPhone情侣模式中启用双人定位?实时查看对方位置的方法

    答案是利用“查找”App实现情侣位置共享。通过开启“共享我的位置”并邀请伴侣加入,选择无限期共享,双方互享位置后即可实时查看对方位置,确保定位准确需开启定位服务、稳定网络并更新系统;也可选用“微爱”“亲宝宝”或“Google 地图”等替代App。 iPhone情侣模式,其实就是利用苹果自带的“查找”…

    2026年9月22日
    000
  • safari浏览器怎么阻止网站访问剪贴板_safari浏览器阻止网站访问剪贴板方法

    可通过关闭网站剪贴板权限、启用无痕浏览、禁用JavaScript或使用内容拦截扩展来阻止Safari网站访问剪贴板,保护隐私安全。 如果您在使用 Safari 浏览器时发现某些网站尝试自动读取或写入剪贴板内容,可能会导致隐私泄露或意外粘贴敏感信息。为防止此类行为,您可以采取以下措施限制网站对剪贴板的…

    2026年9月22日
    1900
  • Java算术运算符优先级解析

    算术运算符优先级决定Java表达式执行顺序,、/、% 高于 +、-,同级从左到右计算,括号可改变顺序,如 (5+3)2=16;整数除法需注意类型,5/2*3 结果为 6。 Java中的算术运算符优先级决定了表达式中各个运算的执行顺序。理解这些优先级规则,能帮助开发者正确编写和解读复杂的数学表达式。 …

    2026年9月22日
    900

发表回复

登录后才能评论
关注微信