QueryDSL 构造函数投影,select 想要单个实体,但 ctor(和结果)是一个列表
QueryDSL Constructor Projection, select wants a single Entity, but ctor (and the result) is a List
我有以下(示例)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;
}
我通过外键将此实体链接到示例数据:
@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;
}
在我的前端,我有一个按需生成 JSON 导出 selected ExampleData 实例的按钮。我创建了以下 class 以包含导出的 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 的构造函数 select 将所需的一切都放入此 class:
的实例中
// 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();
}
所以简而言之,我想要实现的是 select 所有 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*/)
等等,但无法弄清楚如何解决这个问题。
我之前也成功地使用构造函数投影“select 进入”一个非实体 class,其中我使用左连接来附加所有必需的。
我的问题是:
我做错了什么?是否有可能实现这一目标,而我只是看不到解决方案?我是否缺少某种 QueryDsl 语法,如果我使用过它可以解决我的问题?
我无法使用 @ElementCollection,因为我的 ExampleDataExport class 不是实体。我在这里错过了什么吗?
我是不是从错误的角度来解决这个问题,我不应该使用构造函数投影来实现我的目标,而是应该使用两个单独的查询来获取所有数据?或者将 ExampleDataExport 变成一个实体会解决我的问题吗?那会是一个好的 approach/good 代码吗?
这是构造函数投影的小众用例吗?我正在努力学习,这似乎是一个很好的例子。
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
的元组,而不是 b
的列表'每个 a
的 s。 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 returns,只要我可以 return 具有相同性能的实体:
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));
将 return 类型为 Map<AnotherEntity, List<ExampleData>>
的结果。请注意,子查询也可以很容易地替换为关联连接。同样的优化也适用于其他查询。
我有以下(示例)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;
}
我通过外键将此实体链接到示例数据:
@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;
}
在我的前端,我有一个按需生成 JSON 导出 selected ExampleData 实例的按钮。我创建了以下 class 以包含导出的 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 的构造函数 select 将所需的一切都放入此 class:
的实例中// 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();
}
所以简而言之,我想要实现的是 select 所有 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*/)
等等,但无法弄清楚如何解决这个问题。
我之前也成功地使用构造函数投影“select 进入”一个非实体 class,其中我使用左连接来附加所有必需的。
我的问题是:
我做错了什么?是否有可能实现这一目标,而我只是看不到解决方案?我是否缺少某种 QueryDsl 语法,如果我使用过它可以解决我的问题? 我无法使用 @ElementCollection,因为我的 ExampleDataExport class 不是实体。我在这里错过了什么吗?
我是不是从错误的角度来解决这个问题,我不应该使用构造函数投影来实现我的目标,而是应该使用两个单独的查询来获取所有数据?或者将 ExampleDataExport 变成一个实体会解决我的问题吗?那会是一个好的 approach/good 代码吗?
这是构造函数投影的小众用例吗?我正在努力学习,这似乎是一个很好的例子。
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
的元组,而不是 b
的列表'每个 a
的 s。 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 returns,只要我可以 return 具有相同性能的实体:
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));
将 return 类型为 Map<AnotherEntity, List<ExampleData>>
的结果。请注意,子查询也可以很容易地替换为关联连接。同样的优化也适用于其他查询。