如何使用Spring Data REST进行高级搜索?

29

我的任务是使用Spring Data REST实现高级搜索。我该如何实施?

我已经成功地创建了一个方法来执行简单的搜索,类似于这种方式:

public interface ExampleRepository extends CrudRepository<Example, UUID>{

    @RestResource(path="searchByName", rel="searchByName")
    Example findByExampleName(@Param("example") String exampleName);

}

如果我只需要访问URL,则此示例可正常工作:

.../api/examples/search/searchByName?example=myExample

如果有多个字段需要搜索,我该怎么办?

例如,如果我的Example类有5个字段,我应该如何实现使用所有可能字段进行高级搜索?

考虑一下这个:

.../api/examples/search/searchByName?filed1=value1&field2=value2&field4=value4

还有这一个:

.../api/examples/search/searchByName?filed1=value1&field3=value3

我该怎么做才能以合适的方式实现这个搜索功能?

谢谢。


2
请查看此处 https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/ - Muhammad Hewedy
你找到比你开始用的更好的方法了吗? - Lemonade
请看一下我的答案。 - Alessandro C
我相信我完成了你所寻找的内容,请检查我的答案。 - Jefferson Lima
5个回答

15

Spring Data Rest已经将QueryDSL与Web支持集成在一起,您可以在高级搜索需求中使用它。 您需要更改存储库以实现QueryDslPredicateExecutor,然后一切就可以直接运行了。

以下是来自博客文章的一个示例,介绍了此功能:

$ http :8080/api/stores?address.city=York    
{
    "_embedded": {
        "stores": [
            {
                "_links": {}, 
                "address": {
                    "city": "New York", 
                    "location": { "x": -73.938421, "y": 40.851 }, 
                    "street": "803 W 181st St", 
                    "zip": "10033-4516"
                }, 
                "name": "Washington Hgts/181st St"
            }, 
            {
                "_links": {}, 
                "address": {
                    "city": "New York", 
                    "location": { "x": -73.939822, "y": 40.84135 }, 
                    "street": "4001 Broadway", 
                    "zip": "10032-1508"
                }, 
                "name": "168th & Broadway"
            },]
    }, 
    "_links": {}, 
    "page": {
        "number": 0, 
        "size": 20, 
        "totalElements": 209, 
        "totalPages": 11
    }
}

12

我使用Query by Example实现了此功能。

假设您有以下模型:

@Entity
public class Company {

  @Id
  @GeneratedValue
  Long id;

  String name;
  String address;

  @ManyToOne
  Department department;

}


@Entity
public class Department {

  @Id
  @GeneratedValue
  Long id;

  String name;

}

还有代码库:

@RepositoryRestResource
public interface CompanyRepository extends JpaRepository<Company, Long> {
}

请注意,JpaRepository 实现了 QueryByExampleExecutor

现在您需要实现一个自定义控制器:

@RepositoryRestController
@RequiredArgsConstructor
public class CompanyCustomController {

  private final CompanyRepository repository;

  @GetMapping("/companies/filter")
  public ResponseEntity<?> filter(
      Company company,
      Pageable page,
      PagedResourcesAssembler assembler,
      PersistentEntityResourceAssembler entityAssembler
  ){

    ExampleMatcher matcher = ExampleMatcher.matching()
        .withIgnoreCase()
        .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING);

    Example example = Example.of(company, matcher);

    Page<?> result = this.repository.findAll(example, page);

    return ResponseEntity.ok(assembler.toResource(result, entityAssembler));

  }
}

然后你可以像这样查询:

localhost:8080/companies/filter?name=google&address=NY

您甚至可以查询嵌套实体,例如:

localhost:8080/companies/filter?name=google&department.name=finances

为了简洁起见,我省略了一些细节,但是我在Github上创建了一个工作示例


7

查询方法的实现在Spring 参考 文档和大量技术博客中广泛记录,尽管其中相当多已经过时。

由于您的问题可能是“如何执行具有任意字段组合的多参数搜索而不声明大量findBy*方法?”,答案是Querydsl,它受到Spring的支持。


不要认为查询 DSL 可以与 SDR 结合使用。 - alexei-grigoriev
1
@matr0s,请看一下QueryDslPredicateExecutor接口。自Spring的Gosling版本发布以来,Spring中的Querydsl支持已经推出。我已经使用它几个月了,我可以谦虚地为它背书 ;) - Marc Tarin
从来不知道查询 DSL 已经集成到 SDR 中。非常感谢提供这个链接。 - alexei-grigoriev

6
我找到了一个解决此任务的有效方法。
@RepositoryRestResource(excerptProjection=MyProjection.class)
public interface MyRepository extends Repository<Entity, UUID> {

    @Query("select e from Entity e "
          + "where (:field1='' or e.field1=:field1) "
          + "and (:field2='' or e.field2=:field2) "
          // ...
          + "and (:fieldN='' or e.fieldN=:fieldN)"
    Page<Entity> advancedSearch(@Param("field1") String field1,
                               @Param("field2") String field2,
                               @Param("fieldN") String fieldN,
                               Pageable page);

}

使用此解决方案,基本URL为:

http://localhost:8080/api/examples/search/advancedSearch

我们可以使用所有需要的字段进行高级搜索。
一些例子:
http://localhost:8080/api/examples/search/advancedSearch?field1=example
    // filters only for the field1 valorized to "example"

http://localhost:8080/api/examples/search/advancedSearch?field1=name&field2=surname
    // filters for all records with field1 valorized to "name" and with field2 valorized to "surname"

出现编译错误,修改如下。@Query("select e from Entity e " +"where (:field1='' or e.field1=:field1) " +"and (:field2='' or e.field2=:field2) " //... +"and (:fieldN='' or e.fieldN=:fieldN) ") Page<Entity> advancedSearch(@Param("field1") String field1, @Param("field2") String field2, @Param("fieldN") String fieldN, Pageable page); - Janath

2

2
是的,这是我早前回答中提到的链接之一。最多两个参数还好,但如果有三个或更多,定义所有findBy方法就变得有点尴尬了。如果您的搜索字段是“A”,“B”和“C”,您很快就会得到:findByA,findByB,findByC,findByAAndBAndC,findByAAndB,findByAAndC,findByBAndC...现在想象一下A,B,C是有意义的变量名,例如“名称,描述,创建日期”。 - Marc Tarin
目前我正在尝试这样做:personRepo.findAll(new PersonAdvancedSearch(name, surname, age, etc...)),其中PersonAdvancedSearch实现了Specification<Person>。PersonAdvancedSearch覆盖了toPredicate方法。它似乎可以工作,但如果属性是复杂类型,我仍然会遇到问题。 - Alessandro C
@AlessandroC,也许使用@Query注释手动编写你的方法可以更好地处理域对象的搜索。 - Marc Tarin
即使是这样,你的资源和存储库都在一个类中。除了在存储库接口中定义方法之外,您不需要编写任何SQL代码... - Lemonade

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接