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:22
下一篇 2025年11月10日 08:00:33

相关推荐

  • 遵循 PHP 函数命名约定可获得的社区支持

    遵循 php 函数命名约定可获得以下社区支持:提高代码可读性,使代码易于阅读和理解。简化维护,使代码易于维护和更新。更好的社区支持,在在线论坛中更容易获得帮助。 遵循 PHP 函数命名约定可获得的社区支持 PHP 函数命名约定是一种行业规范,旨在确保代码一致且易于维护。遵循这些约定可以提高代码可读性…

    2025年12月9日
    000
  • PHP函数中参数类型检查与其他语言的比较

    php函数的参数类型检查通过强制转换和类型声明进行,与其他语言相比,它提供了更高的灵活性,如java和c#的强制类型安全,python和javascript的可选类型检查,使php能够在确保类型安全性和代码灵活性之间取得平衡。 PHP 函数中参数类型检查与其他语言的比较 PHP 中的参数类型检查可以…

    2025年12月9日
    000
  • 函数中返回多返回值时如何保持代码简洁?

    在 python 中,函数可以通过元组、命名元组和字典返回多个值。元组提供有序的值集合,命名元组可通过属性访问值,字典提供键值对映射,这些技巧有助于保持代码简洁。 函数中返回多返回值的简洁方法 在 Python 中,函数可以返回多个值。虽然这在某些情况下可能很有用,但它会使代码变得冗长且难以阅读。下…

    2025年12月9日
    000
  • 函数中返回异常时如何捕捉和处理异常?

    函数中返回异常时如何捕捉和处理异常 简介: 函数在返回异常时,调用方无法直接获取异常信息,如果不进行处理,将导致程序崩溃。因此,捕捉和处理函数中返回的异常非常重要。 方法: Python提供了多种机制来捕捉和处理函数中返回的异常: try-except 块: try: # 调用可能引发异常的函数ex…

    2025年12月9日
    000
  • PHP函数如何在参数数组中使用键值对?

    php 函数可使用键值对在参数数组中传递数据,键值对用作识别和访问数据的方法。例如,一个计算平均值的函数可以接收一个包含数字和权重的数组,并根据权重计算平均值。键值对可以通过索引或 = 运算符在函数体内访问。 PHP 函数如何在参数数组中使用键值对 PHP 函数可以使用键值对在参数数组中传递数据。键…

    2025年12月9日
    000
  • 函数中返回元组值时如何保持数据的完整性?

    元组和字典可用于从函数返回多个值,保持数据完整性。命名元组为元组元素提供名称,方便阅读。字典使用键值对存储数据,提供更多灵活性。示例函数 calculate_area() 使用命名元组或字典返回矩形的面积和周长。 在返回元组值时保持数据完整性 元组是不可变有序序列,这使得它们在维护数据的完整性方面非…

    2025年12月9日
    000
  • PHP – 发现最新最好的

    php 8.4 计划于 2024 年 11 月 21 日发布,包含一些令人兴奋的新功能和改进。在这篇博文中,我们将探讨一些最有趣的添加和更改: 新的数组辅助函数属性挂钩不带括号的“新”已弃用隐式可为空的参数声明新的多字节函数 1.新的数组辅助函数 php 8.4 中将添加以下数组辅助函数变体: ar…

    2025年12月9日
    000
  • PHP函数如何解析参数字符串

    如何使用 php 的 parse_str() 函数解析参数字符串?语法:parse_str(string $str, array $arr = null) : void解析参数字符串并将其转换为键值对数组。常用于解析 http 请求中的查询字符串和提交的表单数据。 PHP 解析参数字符串 在 PHP…

    2025年12月9日
    000
  • PHP 函数中如何使用递归来处理嵌套数据结构?

    是的,php 函数可以使用递归处理嵌套数据结构。具体步骤如下:定义一个处理函数,该函数接收数据和缩进级别作为参数。检查数据是否为数组,如果不是,则退出函数。增加缩进。遍历数据中的键值对。打印键。如果值是数组,则递归调用函数以处理它。如果值不是数组,则打印值。 PHP 函数中使用递归处理嵌套数据结构 …

    2025年12月9日
    000
  • PHP 函数中哪些变量类型适合存储数组?

    在 php 函数中存储数组时,可以使用数组或对象变量类型。数组类型直接存储键值对,而对象类型将数据组织成具有键和值的属性。如存储用户数据,可以使用数组存储用户的 id、姓名、邮箱等信息。 PHP 函数中存储数组的变量类型 在 PHP 函数中存储数组时,可以使用以下变量类型: 数组 立即学习“PHP免…

    2025年12月9日
    000
  • 如何将 Go 函数扩展到 PHP 中?

    通过安装 cgo、创建 go 包、编写 go 函数、生成 c 头文件、创建 php 扩展并编译和安装它,可以将 go 函数扩展到 php 中。这样,php 代码就可以直接调用扩展后的 go 函数,从而结合两种语言的优势。 如何将 Go 函数扩展到 PHP 中 Go 是一种跨平台编译语言,以其高性能和…

    2025年12月9日
    000
  • PHP 函数中常用的变量类型有哪些?

    php函数中的常用变量类型包括整数、浮点数、字符串、布尔值、数组和对象。例如,整数表示整数值,浮点数表示带小数部分的数字,字符串表示文本数据,布尔值表示逻辑值,数组包含有序键值对的集合,对象表示具有属性和方法的对象。 PHP 函数中常用变量类型 PHP 函数中支持多种变量类型。理解这些类型对于编写高…

    2025年12月9日
    000
  • PHP 函数如何与 Go 交互:提升跨语言性能

    php 函数可通过 syscall.syscall 函数与 go 交互,提升跨语言性能。步骤如下:在 php 中创建函数 callgofunction,接受函数名称和参数数组。在 go 中声明要导出的函数,例如 gofunction(a, b uint64) uint64。编译 go 代码并加载 s…

    2025年12月9日
    000
  • 如何用 PHP 调用 Java 函数?

    使用 java bridge 类库可从 php 脚本中调用 java 函数,通过以下步骤实现:使用 composer 安装 java bridge 类库。使用 setjavaclasspath() 方法配置 php 代码和 java 类路径之间的链接。使用 javaclass::callstatic…

    2025年12月9日
    000
  • PHP 函数如何与 Go 交互?

    PHP 函数如何与 Go 交互 PHP 和 Go 是两种截然不同的编程语言,具有不同的语法和特性。然而,在某些情况下,您可能需要在 PHP 应用程序和 Go 服务之间进行交互。 方法 1:使用 HTTP 请求 您可以使用标准 HTTP 请求在 PHP 和 Go 之间发送数据。 立即学习“PHP免费学…

    2025年12月9日
    000
  • 使用第三方 PHP 函数扩展应用程序功能

    第三方 php 函数通过 composer 安装后,可以通过 psr-4 自动加载。它们可用于扩展应用程序功能,例如使用 guzzle 进行 http 请求或使用 emailvalidator 验证电子邮件地址。通过利用第三方函数,开发人员可以轻松地在应用程序中添加新功能,而无需重新编写代码。 使用…

    2025年12月9日
    000
  • 使用第三方 PHP 函数时避免常见陷阱

    使用第三方 php 函数时,必须注意陷阱,包括:确保依赖关系明确,检查函数签名,处理错误,验证结果。这些准则可避免错误和意外行为,确保代码的可靠性和健壮性。实时案例:使用 guzzlehttp 时,请记住将响应对象转换为字符串或数组,以避免常见陷阱。 使用第三方 PHP 函数时避免常见陷阱 在使用第…

    2025年12月9日
    000
  • PHP 引用传递:加速你的函数开发流程

    引用传递允许函数通过修改变量引用来修改其参数的原始值,从而提高函数的效率,尤其适用于处理大型或复杂数据结构。语法为在参数前面加上”&”符号;实战案例中,通过引用传递数组,可以修改原始数组,而非仅打印副本。 PHP 引用传递:加速你的函数开发流程 引用传递允许函数修改其…

    2025年12月9日
    000
  • PHP 递归函数在解析 JSON 数据中的作用

    递归函数在解析 json 数据中发挥着关键作用,通过使用 json_decode() 函数和递归算法,可以有效解析复杂嵌套的数据结构:定义递归函数 parsejson(),将 json 字符串作为输入。对 json 结果中的每个键值对进行迭代。如果值是数组,则递归调用 parsejson(),将数组…

    2025年12月9日
    000
  • PHP 函数如何与 Go 交互

    php 和 go 可通过结合使用实现优势互补,php 擅长处理 http 请求和数据库交互,而 go 具有高性能和并发性。通过 go 创建服务,并通过 php 发出请求,可以实现语言间的交互。实战中,php 应用程序可通过 curl 请求访问由 go 编写并部署的 api,扩展应用程序功能。 使用 …

    2025年12月9日
    000

发表回复

登录后才能评论
关注微信