QueryDSL Constructor Projection,select想要一个Entity,但是ctor(和结果)是一个List
Posted
技术标签:
【中文标题】QueryDSL Constructor Projection,select想要一个Entity,但是ctor(和结果)是一个List【英文标题】:QueryDSL Constructor Projection, select wants a single Entity, but ctor (and the result) is a List 【发布时间】:2021-11-28 12:58:20 【问题描述】:我有以下(示例)javax.persistence 实体:
@Entity
@Table(name = "example_data")
@Data //lombok for getters/setters
public class ExampleData extends AbstractEntity // AbstractEntity contains the ID only
@Column(name = "data_content")
@NotNull
private String dataContent;
@Column(name = "code")
private String code;
我有这个实体通过外键链接到 ExampleData:
@Entity
@Table(name = "another_entity")
@Data
public class AnotherEntity extends AbstractEntity
// ... stuff
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "example_data_id")
private ExampleData exampleData;
在我的前端,我有一个按钮,可以按需生成所选 ExampleData 实例的 JSON 导出。我创建了以下类来包含导出的 JSON 中所需的所有内容:
@NoArgsConstructor //lombok to generate ctor
@Data //lombok for getters/setters
public class ExampleDataExport extends ExampleData
public ExampleDataExport(Long id,
String dataContent,
String code,
List<AnotherEntity> attachedList)
// ... all set
private List<AnotherEntity> attachedList; //List of other Entity required for export
现在,我想使用 QueryDSL Projections.constructor 来使用 ExampleDataExport 的构造函数来选择此类实例所需的所有内容:
// I have static imported all the QueryDSL QObjects such as:
import static my.study.entity.QExampleData.exampleData;
import static my.study.entity.QAnotherEntity.anotherEntity;
//...
public List<ExampleDataExport> exportSelected(List<String> codes)
return new JPAQuery<ExampleDataExport>(entityManager)
.select(Projections.constructor(
ExampleDataExport.class,
exampleData.id,
exampleData.dataContent,
exampleData.code,
JPAExpressions.select(anotherEntity)
.from(anotherEntity)
.where(anotherEntity.exampleData().eq(exampleData)
))
.leftJoin(anotherEntity.exampleData(), exampleData)
.from(exampleData)
.where(exampleData.code.in(codes))
.fetch();
简而言之,我想要实现的是将所有 ExampleData 及其对应的 AnotherEntitiy-s 选择到一个 ExampleDataExport 实例中(然后我可以将其发送到我的前端)。
问题:我尝试了很多变体来替换“JPAExpression”,但是在所有情况下,问题都是一样的。创建查询时,QueryDSL 在 ExampleDataExport 中找不到“匹配构造函数”,因为它会搜索匹配的构造函数:
[class java.lang.Long, class java.lang.String, class java.lang.String, class my.study.entity.AnotherEntity]
而不是
[class java.lang.Long, class java.lang.String, class java.lang.String, class java.util.List]
我尝试过使用.leftJoin(anotherEntity.exampleData(), exampleData)
,或者只使用select(..., anotherEntity)
而不是JPAExpression 并使用.where(.../*same condition as in JPAExpression*/)
等等,但不知道如何解决这个问题。
我之前也成功地使用了构造函数投影来“选择”一个非实体类,其中我使用左连接来附加所有必需的。
我的问题是:
我做错了什么?是否有可能实现这一目标,而我只是看不到解决方案?我是否缺少某种 QueryDsl 语法,如果我使用过它可以解决我的问题? 我无法使用@ElementCollection,因为我的 ExampleDataExport 类不是实体。我错过了什么吗?
我是不是从错误的角度解决问题,我不应该使用构造函数投影来实现我的目标,而只使用两个单独的查询来获取所有数据?或者将 ExampleDataExport 变成一个实体可以解决我的问题吗?那会是一个好方法/好代码吗?
这是构造函数投影的利基用例吗?我正在努力学习,这似乎是一个很好的例子。
【问题讨论】:
【参考方案1】:select(Projections.constructor(clasz, a, b))
只是select(a, b)
周围的语法糖,它可以像这样转换查询结果:
getResultList().stream().map(tuple -> new Clasz(tuple.get(a), tuple.get(b))).collect(toList())
所以最终呈现的查询片段只是SELECT a, b
,这意味着您最终会得到包含a
和b
的元组,而不是每个a
的b
列表。 JPQL 是 JPA 的查询语言,缺少像元组中的列表这样的嵌入式结构的概念。这并不奇怪,因为 SQL 也缺少这样的概念。虽然确实存在一些特定于供应商的解决方案,例如结合 PostgreSQL 记录和数组类型,但这些解决方案几乎不可能应用于整个 Hibernate 和 Querydsl 的查询链。
最可行的解决方案是在 Java 端收集到内存中的列表。 Querydsl 实际上确实为此提供了语法糖,形式为GroupBy
表达式。所以可以做GroupBy.list(b)
,Querydsl 将在转换过程中尝试分组到List<B>
。但是,当组合投影和 group by 表达式的深度嵌套时,您可能会遇到已知的限制。但是,我认为以下应该可行:
return new JPAQuery<ExampleDataExport>(entityManager)
.select(Projections.constructor(
ExampleDataExport.class,
exampleData.id,
exampleData.dataContent,
exampleData.code,
GroupBy.list( JPAExpressions.select(anotherEntity)
.from(anotherEntity)
.where(anotherEntity.exampleData().eq(exampleData)
))
.leftJoin(anotherEntity.exampleData(), exampleData)
.from(exampleData)
.where(exampleData.code.in(codes))
.fetch();
但是,我个人尽量避免 DTO 返回,只要我可以返回具有相同性能的实体:
Map<AnotherEntity, List<ExampleData>> result = new JPAQuery<ExampleDataExport>(entityManager)
.from(anotherEntity)
.leftJoin(anotherEntity.exampleData(), exampleData)
.on(exampleData.code.in(codes))
.transform(GroupBy.groupBy(anotherEntity).as(GroupBy.list(exampleData));
将返回Map<AnotherEntity, List<ExampleData>>
类型的结果。请注意,子查询也可以很容易地被关联连接替换。同样的优化也适用于其他查询。
【讨论】:
检查我的示例和您的示例生成的 SQL 帮助我了解我做错了什么,这对我来说是一个学习项目,所以我最终使用第二个示例来了解有关转换的更多信息()。谢谢!以上是关于QueryDSL Constructor Projection,select想要一个Entity,但是ctor(和结果)是一个List的主要内容,如果未能解决你的问题,请参考以下文章
QueryDSL 4 与 RowNumber Window 功能