使用 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 用于生成 fromjoin 子句。

因此,例如:

@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();