
本文深入探讨了如何使用QueryDSL实现对实体进行分组查询,并将分组结果投影到包含嵌套列表的复杂数据传输对象(DTO)中。通过详细的代码示例,我们展示了如何利用QueryDSL的`GroupBy`功能来聚合数据,以及如何将聚合后的`Map`结构转换为目标DTO列表,从而解决直接使用`Projections.constructor`处理嵌套列表的编译问题。
在使用Spring Data JPA和QueryDSL进行开发时,我们经常会遇到需要对数据进行分组,并以特定结构(例如,包含子列表的DTO)返回结果的场景。本教程将以一个具体的例子,展示如何将Technology实体按technologyStatus进行分组,并将每个状态下的技术列表投影到一个TechnologyByStatusDTO对象中。
首先,我们定义相关的实体和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
这是我们最终希望得到的目标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进行分组查询时,开发者可能会尝试如下方式:
// 假设 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功能,专门用于处理这种分组和聚合场景。它允许我们先对数据进行分组,然后将每个组的结果转换为所需的结构,通常是一个Map。
1. 核心思路
使用queryFactory.transform(GroupBy.groupBy(keyExpression).as(GroupBy.list(valueExpression)))。
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());
}
}代码解析:
通过以上方法,我们能够有效地利用QueryDSL的GroupBy功能,实现按指定字段分组并投影到包含嵌套列表的复杂DTO中,从而满足各种数据聚合和展示的需求。
以上就是QueryDSL分组查询与复杂DTO投影:实现按状态聚合技术列表的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号