使用 QueryDSL 和 JPASQLQuery 加入多对多关系
Join a many to many relation with QueryDSL and JPASQLQuery
我有以下实体:
@AllArgsConstructor
@EqualsAndHashCode(of = {"name"})
@Data
@NoArgsConstructor
@Entity
@Table(schema = "eat")
public class Pizza {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="pizza_id_seq")
private Integer id;
@NotNull
private String name;
@NotNull
@Positive
private Double cost;
@ManyToMany
@JoinTable(schema = "eat",
name = "pizza_ingredient",
inverseJoinColumns = { @JoinColumn(name = "ingredient_id") })
private Set<Ingredient> ingredients;
}
@AllArgsConstructor
@EqualsAndHashCode(of = {"name"})
@Data
@NoArgsConstructor
@Entity
@Table(schema = "eat")
public class Ingredient {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="ingredient_id_seq")
private Integer id;
@NotNull
@Size(min=1, max=64)
private String name;
}
我正在使用 QueryDSL (4.2.2) 提供的 JPASQLQuery
对象在 PostgreSQL 中创建一些本机查询:
public JPASQLQuery<T> getJPASQLQuery() {
return new JPASQLQuery<>(
entityManager,
PostgreSQLTemplates.builder().printSchema().build()
);
}
尝试使用 join
函数时出现问题,例如:
QIngredient ingredient = QIngredient.ingredient;
QPizza pizza = QPizza.pizza;
StringPath ingredientPath = Expressions.stringPath("ingredient");
StringPath pizzaPath = Expressions.stringPath("pizza");
NumberPath<Double> costPath = Expressions.numberPath(Double.class, "cost");
Expression rowNumber = SQLExpressions.rowNumber().over().partitionBy(ingredientPath).orderBy(costPath.desc()).as("rnk");
JPASQLQuery subQuery = getJPASQLQuery()
.select(ingredient.name.as(ingredientPath), pizza.name.as(pizzaPath), pizza.cost.as(costPath), rowNumber)
.from(pizza)
// The error is in next innerJoin
.innerJoin((SubQueryExpression<?>) pizza.ingredients, ingredient)
.where(ingredient.name.in(ingredientNames));
如果我保持当前 innerJoin((SubQueryExpression<?>) pizza.ingredients, ingredient)
我收到:
class com.querydsl.core.types.dsl.SetPath cannot be cast to class com.querydsl.core.types.SubQueryExpression
我无法删除当前 (SubQueryExpression<?>)
,因为 innerJoin
不接受 SetPath
作为参数。
另一方面,以下内容:
.from(pizza)
.innerJoin(ingredient)
由于 pizza_ingredient
未包含在生成的查询中,因此无法正常工作。
如何在 JPASQLQuery
中使用 innerJoin
与上面的多对多关系?
基本上,有两种主要方法试图解决它:
包括所需的本机函数
按照一位 QueryDSL 开发人员的建议 here,用 JPA 替代品替换 JPASQLQuery
。
为多对多 table
创建所需 Path
首先将 name
属性 添加到每个 @Table
注释中很重要,因为在内部是 QueryDSL NativeSQLSerializer
class 用于生成 from
和 join
子句。
因此,例如:
@Table(schema = "eat")
public class Pizza ...
应替换为:
@Table(name = "pizza", schema = "eat")
public class Pizza ...
接下来,为多对多 table 创建自定义 Path
:
RelationalPathBase<Object> pizzaIngredient = new RelationalPathBase<>(Object.class, "pi", "eat", "pizza_ingredient");
NumberPath<Integer> pizzaIngredient_PizzaId = Expressions.numberPath(Integer.class, pizzaIngredient, "pizza_id");
NumberPath<Integer> pizzaIngredient_IngredientId = Expressions.numberPath(Integer.class, pizzaIngredient, "ingredient_id");
所以完整的代码是:
QIngredient ingredient = QIngredient.ingredient;
QPizza pizza = QPizza.pizza;
RelationalPathBase<Object> pizzaIngredient = new RelationalPathBase<>(Object.class, "pi", "eat", "pizza_ingredient");
NumberPath<Integer> pizzaIngredient_PizzaId = Expressions.numberPath(Integer.class, pizzaIngredient, "pizza_id");
NumberPath<Integer> pizzaIngredient_IngredientId = Expressions.numberPath(Integer.class, pizzaIngredient, "ingredient_id");
StringPath ingredientPath = Expressions.stringPath("ingredient");
StringPath pizzaPath = Expressions.stringPath( "pizza");
NumberPath<Double> costPath = Expressions.numberPath(Double.class, "cost");
Expression rowNumber = SQLExpressions.rowNumber().over().partitionBy(ingredientPath).orderBy(costPath.desc()).as("rnk");
NumberPath<Long> rnk = Expressions.numberPath(Long.class, "rnk");
SubQueryExpression subQuery = getJPASQLQuery()
.select(ingredient.name.as(ingredientPath), pizza.name.as(pizzaPath), pizza.cost.as(costPath), rowNumber)
.from(pizza)
.innerJoin(pizzaIngredient).on(pizzaIngredient_PizzaId.eq(pizza.id))
.innerJoin(ingredient).on(ingredient.id.eq(pizzaIngredient_IngredientId))
.where(ingredient.name.in(ingredientNames));
return getJPASQLQuery()
.select(ingredientPath, pizzaPath, costPath)
.from(
subQuery,
Expressions.stringPath("temp")
)
.where(rnk.eq(1l))
.fetch();
我有以下实体:
@AllArgsConstructor
@EqualsAndHashCode(of = {"name"})
@Data
@NoArgsConstructor
@Entity
@Table(schema = "eat")
public class Pizza {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="pizza_id_seq")
private Integer id;
@NotNull
private String name;
@NotNull
@Positive
private Double cost;
@ManyToMany
@JoinTable(schema = "eat",
name = "pizza_ingredient",
inverseJoinColumns = { @JoinColumn(name = "ingredient_id") })
private Set<Ingredient> ingredients;
}
@AllArgsConstructor
@EqualsAndHashCode(of = {"name"})
@Data
@NoArgsConstructor
@Entity
@Table(schema = "eat")
public class Ingredient {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="ingredient_id_seq")
private Integer id;
@NotNull
@Size(min=1, max=64)
private String name;
}
我正在使用 QueryDSL (4.2.2) 提供的 JPASQLQuery
对象在 PostgreSQL 中创建一些本机查询:
public JPASQLQuery<T> getJPASQLQuery() {
return new JPASQLQuery<>(
entityManager,
PostgreSQLTemplates.builder().printSchema().build()
);
}
尝试使用 join
函数时出现问题,例如:
QIngredient ingredient = QIngredient.ingredient;
QPizza pizza = QPizza.pizza;
StringPath ingredientPath = Expressions.stringPath("ingredient");
StringPath pizzaPath = Expressions.stringPath("pizza");
NumberPath<Double> costPath = Expressions.numberPath(Double.class, "cost");
Expression rowNumber = SQLExpressions.rowNumber().over().partitionBy(ingredientPath).orderBy(costPath.desc()).as("rnk");
JPASQLQuery subQuery = getJPASQLQuery()
.select(ingredient.name.as(ingredientPath), pizza.name.as(pizzaPath), pizza.cost.as(costPath), rowNumber)
.from(pizza)
// The error is in next innerJoin
.innerJoin((SubQueryExpression<?>) pizza.ingredients, ingredient)
.where(ingredient.name.in(ingredientNames));
如果我保持当前 innerJoin((SubQueryExpression<?>) pizza.ingredients, ingredient)
我收到:
class com.querydsl.core.types.dsl.SetPath cannot be cast to class com.querydsl.core.types.SubQueryExpression
我无法删除当前 (SubQueryExpression<?>)
,因为 innerJoin
不接受 SetPath
作为参数。
另一方面,以下内容:
.from(pizza)
.innerJoin(ingredient)
由于 pizza_ingredient
未包含在生成的查询中,因此无法正常工作。
如何在 JPASQLQuery
中使用 innerJoin
与上面的多对多关系?
基本上,有两种主要方法试图解决它:
包括所需的本机函数
按照一位 QueryDSL 开发人员的建议 here,用 JPA 替代品替换 JPASQLQuery
。
为多对多 table
创建所需Path
首先将 name
属性 添加到每个 @Table
注释中很重要,因为在内部是 QueryDSL NativeSQLSerializer
class 用于生成 from
和 join
子句。
因此,例如:
@Table(schema = "eat")
public class Pizza ...
应替换为:
@Table(name = "pizza", schema = "eat")
public class Pizza ...
接下来,为多对多 table 创建自定义 Path
:
RelationalPathBase<Object> pizzaIngredient = new RelationalPathBase<>(Object.class, "pi", "eat", "pizza_ingredient");
NumberPath<Integer> pizzaIngredient_PizzaId = Expressions.numberPath(Integer.class, pizzaIngredient, "pizza_id");
NumberPath<Integer> pizzaIngredient_IngredientId = Expressions.numberPath(Integer.class, pizzaIngredient, "ingredient_id");
所以完整的代码是:
QIngredient ingredient = QIngredient.ingredient;
QPizza pizza = QPizza.pizza;
RelationalPathBase<Object> pizzaIngredient = new RelationalPathBase<>(Object.class, "pi", "eat", "pizza_ingredient");
NumberPath<Integer> pizzaIngredient_PizzaId = Expressions.numberPath(Integer.class, pizzaIngredient, "pizza_id");
NumberPath<Integer> pizzaIngredient_IngredientId = Expressions.numberPath(Integer.class, pizzaIngredient, "ingredient_id");
StringPath ingredientPath = Expressions.stringPath("ingredient");
StringPath pizzaPath = Expressions.stringPath( "pizza");
NumberPath<Double> costPath = Expressions.numberPath(Double.class, "cost");
Expression rowNumber = SQLExpressions.rowNumber().over().partitionBy(ingredientPath).orderBy(costPath.desc()).as("rnk");
NumberPath<Long> rnk = Expressions.numberPath(Long.class, "rnk");
SubQueryExpression subQuery = getJPASQLQuery()
.select(ingredient.name.as(ingredientPath), pizza.name.as(pizzaPath), pizza.cost.as(costPath), rowNumber)
.from(pizza)
.innerJoin(pizzaIngredient).on(pizzaIngredient_PizzaId.eq(pizza.id))
.innerJoin(ingredient).on(ingredient.id.eq(pizzaIngredient_IngredientId))
.where(ingredient.name.in(ingredientNames));
return getJPASQLQuery()
.select(ingredientPath, pizzaPath, costPath)
.from(
subQuery,
Expressions.stringPath("temp")
)
.where(rnk.eq(1l))
.fetch();