简体   繁体   English

在 Spring 数据 JPA 中使用 @Query 进行动态查询?

[英]Dynamic query with @Query in Spring Data JPA?

I am using Specifications in my Spring Boot app and can filter result by different filter options.我在我的 Spring 引导应用程序中使用规范,可以通过不同的过滤器选项过滤结果。 However, I need to use special filter with @Query in my repository method and as far as I see, I cannot build a dynamic WHERE clause in this query.但是,我需要在我的存储库方法中使用带有@Query的特殊过滤器,据我所知,我无法在此查询中构建动态 WHERE 子句。

There are also QueryDSL and CriteriaAPI options, but I cannot find an example for using them in @Query .还有 QueryDSL 和 CriteriaAPI 选项,但我找不到在@Query中使用它们的示例。

So, is it possible to dynamically build WHERE clause or create filter for the query in @Query ?那么,是否可以动态构建 WHERE 子句或为@Query中的查询创建过滤器? Here is my method:这是我的方法:

// there are more filters that omitted for brevity 

@Query("SELECT r FROM Recipe r WHERE r.title LIKE %:text%")
Page<Recipe> findByFields(@Param("text") String text);

I tried to use my specification in this method, but it is not possible to use them with @Query :((我尝试在此方法中使用我的规范,但无法将它们与@Query一起使用 :((



Update:更新:


@Entity
public class Recipe {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @Enumerated(value = EnumType.STRING)
    private HealthLabel healthLabel;

    // code omitted for brevity

    @OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<RecipeIngredient> recipeIngredients = new ArrayList<>();
}

@Entity
public class RecipeIngredient {

    @EmbeddedId
    private RecipeIngredientId recipeIngredientId = new RecipeIngredientId();

    @Column(nullable = false)
    private BigDecimal amount;

    @ManyToOne(optional = true, fetch = FetchType.LAZY)
    @MapsId("recipeId")
    @JoinColumn(name = "recipe_id", referencedColumnName = "id")
    private Recipe recipe;

    @ManyToOne(optional = true, fetch = FetchType.LAZY)
    @MapsId("ingredientId")
    @JoinColumn(name = "ingredient_id", referencedColumnName = "id")
    private Ingredient ingredient;
}

@Entity
public class Ingredient {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false, length = 50)
    private String name;

    @OneToMany(mappedBy = "ingredient", cascade = CascadeType.ALL)
    private Set<RecipeIngredient> recipeIngredients = new HashSet<>();
}

Here is also my enum that I cannot filter by:这也是我无法过滤的枚举:

@Getter
@AllArgsConstructor
public enum HealthLabel {

    DEFAULT("Default"),
    EGG_FREE("Egg-free"),
    VEGETARIAN("Vegetarian"),
    WHEAT_FREE("Wheat-free");

    private String label;
}

@Query can only do static queries. @Query只能进行 static 次查询。

If you want something more dynamic you have to use another feature, for example Specifications or even fall back to custom method implementations .如果你想要更动态的东西,你必须使用另一个特性,例如Specifications或者甚至回退到自定义方法实现

You can try like this:你可以这样尝试:

@Query("SELECT r FROM Recipe r WHERE r.title LIKE %?1%")
Page<Recipe> findByFields(String text);

There is nothing preventing you from using a Specification (which is basically the Criteria API but easier to use).没有什么可以阻止您使用Specification (基本上是Criteria API 但更易于使用)。 You basically want the dynamic version of the following JPQL.您基本上需要以下 JPQL 的动态版本。

SELECT r FROM Recipe r 
JOIN r.recipeIngredients ri 
JOIN ri.ingredient i
WHERE i.name LIKE :name

Now if you can write it in JPQL you can also use the Criteria API to write it (generally speaking).现在,如果你可以用 JPQL 编写它,你也可以使用Criteria API 来编写它(一般来说)。

public static Specification<Recipe> findByIngredientName(String name) {
  return (root, query, criteriaBuilder) -> {
     Join<Recipe, RecipeIngredient> ingredients = root.join("ingredients");
     Join< RecipeIngredient, Ingredient> ingredient = ingredients.join("ingredient");      
     return criteriaBuilder.like(ingredient.get("name"), "%" + name + "%"); 
  };
}

That is the specification to retrieve a Recipe containing an Ingredient with a name like something.这是用于检索包含name like某物的IngredientRecipe的规范。 You need 2 joins no query just a join.您需要 2 个连接,无需查询,只需一个连接。

You can now even combine multiple predicates by using Predicate.and .您现在甚至可以使用Predicate.and组合多个谓词。 So if you create another Predicate for the Recipe.name you can just chain them together, JPA will handle the rest.因此,如果您为Recipe.name创建另一个Predicate ,您可以将它们链接在一起,JPA 将处理 rest。

public static Specification<Recipe> findName(String name) {
  return (root, query, criteriaBuilder) -> { 
     return criteriaBuilder.like(root.get("title"), "%" + name + "%"); 
  };
}
Specification<Recipe> specs = findByName("recipe").and(findByIngredientName("ingredient"));
recipeRepository.findAll(specs);

That is all you need.这就是你所需要的。 What and how you receive those parameters that is up to you how you build the request/response objects and is highly subjective.您接收什么以及如何接收这些参数取决于您如何构建请求/响应对象并且是高度主观的。 If you want a sort order use the findAll which takes a Specification and a Sort如果您想要排序顺序,请使用findAll ,它需要一个Specification和一个Sort

Specification<Recipe> specs = findByName("recipe").and(findByIngredientName("ingredient"));
recipeRepository.findAll(specs, Sort.by("title"));

The above will generate a SQL in the form of以上将生成一个 SQL 的形式

SELECT recipesapp0_.id as id1_1_, recipesapp0_.title as title2_1_ FROM recipes_application$recipe recipesapp0_ 
INNER JOIN recipes_application$recipe_ingredient recipeingr1_ ON recipesapp0_.id=recipeingr1_.recipe_id 
INNER JOIN recipes_application$ingredient recipesapp2_ ON recipeingr1_.ingredient_id=recipesapp2_.id 
WHERE (recipesapp0_.title like ?) AND (recipesapp2_.name like ?) ORDER BYrecipesapp0_.title ASC

NOTE: Next time when someone asks for clarification of your use-case please give it instead of being unfriendly to them!注意:下次当有人要求澄清你的用例时,请给出而不是对他们不友好!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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