【问题标题】:How to make a dynamic Spring (Boot) JPA query with paging?如何使用分页进行动态 Spring (Boot) JPA 查询?
【发布时间】:2020-11-13 14:24:06
【问题描述】:

为了简化问题:我们有一个类/表 Wine(表“wines”),它具有以下属性:

  • 名称:字符串
  • 描述:字符串
  • 原产地:原产地

...其中 Origin 是另一个类(带有表“origins”),只有 region: Stringcountry: String

我正在尝试在我的存储库中创建一个供 RestController 使用的搜索方法。

RestController中的方法声明是这样的:

@GetMapping("/search")
public Wine searchProduct(
        @RequestParam Optional<String> searchTerm,
        @RequestParam Optional<Origin> origin) {
    // ???
}

我现在要做的是:为使用 searchTerm 的数据库创建一个查询,如果给定的话,与 origin 相同。它应该是可分页的。 示例:

SELECT * FROM wines JOIN origins ON wines.origin_id = origins.id 
    WHERE (name LIKE $searchTerm OR description LIKE $searchTerm) AND (/*origin check*/)

如果没有给出搜索词,则整个“() AND”部分不应出现在查询中。如果没有给出 Origin... 你明白了。

我尝试过的事情:

  1. (天真地)在我的存储库中构建一个大型查询(实现 CrudRepository),例如 here

    Page&lt;Wine&gt; findWinesByNameLikeOrDescriptionLikeAndOriginEquals(..., Pageable pageable);

    • 这(除了超级难看,尤其是对于更多属性)可能不起作用,因为:
      1. 未定义 OR 或 AND 更重要(无括号)。
      2. 不知道我是否可以将“起源”对象推入其中以使其工作。
      3. 不知道 Pageable 是否可以工作,如果这是一种自定义方法。
  2. 按照 spring.io here 的建议使用“规范和 Querydsl”。

    • 我只是愚蠢地理解这一点,尤其是在帖子底部的那些 Q 类或开头的 _ 类。它应该做的事情似乎太复杂了。
    • 也没有分页选项。不过,有一个可能的修复方法,例如 here,但我没有解决,因为一开始使用这些 _ 和/或 Q 类的开销很大。
  3. 另外,this 只是我从 2013 年发现的一个例子,我什至不理解它,但看起来很合适。

【问题讨论】:

  • 您的 Wine 实体是否与 Origin@ManyToOne 关系?
  • stackoverflow.com/questions/48882265/… 这和你的问题有关吗
  • @KavithakaranKanapathippillai 葡萄酒和产地是 OneToOne。
  • @Justas 我所说的是一个带分页的动态查询。您提供的链接仅用于后者的实现。

标签: java sql spring spring-boot jpa


【解决方案1】:

如果您觉得难以理解Specifications,则只有这两个搜索参数,并假设Wine 实体有@ManyToOneOrigin,您可以执行以下操作:

    @Query("SELECT w FROM wines w join w.origin o where " +
            "(   :searchTerm is null or " +
            "    w.name like CONCAT('%',:searchTerm ,'%') or " +
            "    w.description like CONCAT('%', :searchTerm ,'%')" +
            ") " +
            "AND (:origin is null or  o = :origin)")
    Page<Wine> searchWines(String searchTerm, Origin origin, Pageable p);

当你调用它时,

    wineRepository.searchWines(searchTerm.orElse(null), origin.orElse(null));

【讨论】:

  • 是的,我知道这种可能性,还有一种带有谓词的方法可以实现这一点,但如果只设置了 5 个过滤器中的一个,我不想让我的查询膨胀。不过感谢您的示例!
【解决方案2】:

所以我实际上找到了一个有效的解决方案!对于好奇的人,我是这样做的:

ProductController.java

@GetMapping("search")
public Page<Wine> searchProducts(
        @RequestParam(name = "text", required = false) String searchTerm,
        @RequestParam(required = false) Origin origin,
        @RequestParam(required = false) Integer page) {
    // generate PageRequest based on Integer page if given:
    Pageable pageRequest = PageRequest.of(page != null ? page : 0, 10);
    if(Objects.isNull(searchTerm) && Objects.isNull(origin)) {
        return wineService.findAll(pageRequest);
    }
    return wineService.searchWines(
            searchTerm,
            origin,
            pageRequest
    );
}

WineService.java

public Page<Wine> searchWines(String searchTerm, Origin origin, Pageable pageable) {
    List<Specification<Wine>> specifications = new LinkedList<>();
    if (searchTerm != null) {
        specifications.add(ProductSpecification.hasSearchStringInNameOrDescription(searchTerm));
    }
    if (origin != null) {
        specifications.add(ProductSpecification.hasOrigin(origin));
    }
    if (specifications.isEmpty()) {
        return wineRepository.findAll(pageable);
    } else {
        Specification<Wine> query = Specification.where(specifications.remove(0));
        for (Specification<Wine> wineSpecification : specifications) {
            query = query.and(wineSpecification);
        }
        return wineRepository.findAll(query, pageable);
    }
}

例如,这里是名称/描述字符串的规范:

ProductSpecification.java

public static Specification<Wine> hasSearchStringInNameOrDescription(String input) {
    final String searchTerm = input.toLowerCase();
    return (root, criteriaQuery, criteriaBuilder) -> {
        log.info("SearchTerm: " + searchTerm);
        Predicate pName = criteriaBuilder.like(
                criteriaBuilder.lower(root.get(Wine_.NAME)),
                "%" + searchTerm + "%"
        );
        Predicate pDescription = criteriaBuilder.like(
                criteriaBuilder.lower(root.get(Wine_.DESCRIPTION)),
                "%" + searchTerm + "%"
        );
        return criteriaBuilder.or(pName, pDescription);
    };
}

这些 Wine_ 类的生成是使用 Maven 插件自动生成的。见this。 此外,为了将请求正文片段转换为像 Origin 这样的复杂对象,您需要包含像 here 这样的转换器类。

【讨论】:

    猜你喜欢
    • 2012-07-25
    • 2017-09-10
    • 2015-12-03
    • 2019-07-16
    • 2022-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多