简体   繁体   中英

Join a many to many relation with QueryDSL and JPASQLQuery

I have the following entities:

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

}

I'm using JPASQLQuery object provided by QueryDSL (4.2.2) to create some native queries in PostgreSQL:

public JPASQLQuery<T> getJPASQLQuery() {
   return new JPASQLQuery<>(
      entityManager,
      PostgreSQLTemplates.builder().printSchema().build()
   );
}

The problem comes trying to use join functions, for example:

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

If I keep the current innerJoin((SubQueryExpression<?>) pizza.ingredients, ingredient) I receive:

class com.querydsl.core.types.dsl.SetPath cannot be cast to class com.querydsl.core.types.SubQueryExpression

I cannot remove current (SubQueryExpression<?>) because innerJoin doesn't accept SetPath as parameter.

On the other hand, the following:

.from(pizza)               
.innerJoin(ingredient)

Doesn't work due to pizza_ingredient is not included in the generated query.

How can I use innerJoin in JPASQLQuery with a many to many relationship like above?

Basically, there are two main approaches trying to solve it:


Include required native functions

As suggest one QueryDSL developer here , replacing JPASQLQuery by JPA alternatives.


Create required Path for many to many table

First is important to add name property into every @Table annotation because internally is the one used by QueryDSL NativeSQLSerializer class to generate from and join clauses.

So, for example:

@Table(schema = "eat")
public class Pizza ...

Should be replaced by:

@Table(name = "pizza", schema = "eat")
public class Pizza ...

Next, create for custom Path for the many to many table:

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");

So the complete code would be:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM