【问题标题】:Spring Data - Multi-column searchesSpring Data - 多列搜索
【发布时间】:2014-09-16 15:21:21
【问题描述】:

我正在使用 Spring Data 进行分页和排序。但是,我想执行多列搜索。

现在,我在我的存储库界面中使用注释 @Query,如下所示:

public interface MyRepository extends PagingAndSortingRepository<Item,Long> {

    @Query(value="select mt from MY_TABLE mt where mt.field1 = %searchtext% or mt.field2 = %searchtext% or mt.field3 = %searchtext%")    
    Page<Item> findByAllColumns(@Param("searchtext") String searchtext, Pageable pageable);

}

我想知道是否有其他方法可以做,因为表中的列数可能很多。

感谢您的帮助。

编辑(在 Brandon Oakley 评论后澄清问题):此解决方案中的问题在于 @Query 注释的 where 子句,因为我们必须为我们想要的每一列重复完全相同的 searchtext 参数搜索

【问题讨论】:

  • 这可能会回答你的问题...stackoverflow.com/a/20121813/2267073
  • 我不确定如何:我当前解决方案中的问题在于 @Query 注释的 where 子句,因为我们必须重复完全相同的 searchtext 我们要搜索的每一列的参数。所以我想知道是否有另一种更优雅的方法可以让我们只给出列的名称。
  • 您的回答很有趣,但对于其他情况。所以,谢谢你的建议。

标签: java spring spring-data spring-data-jpa


【解决方案1】:

这是用户规范的示例:

public static Specification<User> containsTextInName(String text) {
    if (!text.contains("%")) {
        text = "%" + text + "%";
    }
    String finalText = text;
    return (root, query, builder) -> builder.or(
            builder.like(root.get("lastname"), finalText),
            builder.like(root.get("firstname"), finalText)
    );
}

甚至更可定制的实现:

public static Specification<User> containsTextInAttributes(String text, List<String> attributes) {
    if (!text.contains("%")) {
        text = "%" + text + "%";
    }
    String finalText = text;
    return (root, query, builder) -> builder.or(root.getModel().getDeclaredSingularAttributes().stream()
            .filter(a -> attributes.contains(a.getName()))
            .map(a -> builder.like(root.get(a.getName()), finalText))
            .toArray(Predicate[]::new)
    );
}

public static Specification<User> containsTextInName(String text) {
    return containsTextInAttributes(text, Arrays.asList("lastname", "firstname"));
}

用法:

userRepository.findAll(Specifications.where(UserSpecifications.containsTextInName("irs")))

【讨论】:

  • 请注意:Specifications 现在已弃用,取而代之的是 Specification
  • @DenissM。 - 你知道我们应该改用吗?你能分享一些信息吗?你是如何解决这个问题的?
【解决方案2】:

您可以使用specifications。这也为您提供了更大的灵活性。您可以使用一种方法,但对查询使用多种规范:

Page<Item> findAll(Specification<T> spec, Pageable pageable);

myRepository.findAll(textInAllColumns(searchText), pageable);

【讨论】:

  • 感谢您的指点。我们目前正在调查是否符合我们的需要。这篇博文很有意思:spring.io/blog/2011/04/26/…
  • 这很好用,这正是我们想要的:我们甚至不再需要指定 Query 注释。分页仍然可以正常工作。它是面向对象的(即完全可重用)。非常感谢!
  • 虽然我们最终没有使用,但是这个相关的项目也很有意思:blog.kaczmarzyk.net/2014/03/23/…
  • 您有“textInAllColumns”规范构建器的示例实现吗?
  • @Jadda 示例查询在 2014 年不受支持 ^^ 但是是的,它可以替代规范。
【解决方案3】:

结合前两个答案:如果您不想将 API 和数据库架构耦合起来,或者换句话说,您不希望用户提供字符串列名 - 您可以过滤掉那些不是字符串的属性并将like 应用于所有这些。在以下示例中,它将尝试在列值中搜索 textnamefield1field2field3

实体示例:

@Entity
public class MyEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public int id;
    public String name;
    public String field2;
    public String field3;
    public String field4;
}

规格示例:

public class EntitySpecification {

    public static Specification<MyEntity> textInAllColumns(String text) {

        if (!text.contains("%")) {
            text = "%"+text+"%";
        }
        final String finalText = text;

        return new Specification<MyEntity>() {
            @Override
            public Predicate toPredicate(Root<MyEntity> root, CriteriaQuery<?> cq, CriteriaBuilder builder) {
                return builder.or(root.getModel().getDeclaredSingularAttributes().stream().filter(a-> {
                    if (a.getJavaType().getSimpleName().equalsIgnoreCase("string")) {
                        return true;
                    }
                    else {
                        return false;
                }}).map(a -> builder.like(root.get(a.getName()), finalText)
                    ).toArray(Predicate[]::new)
                );
            }
        };
    }

 }

存储库示例:

public interface MyEntityRepository extends PagingAndSortingRepository<MyEntity, Integer> {
    List<MyEntity> findAll(Specification<MyEntity> spec);
}

使用示例:

List<MyEntity> res = failureRepository.findAll(Specifications.where(FailureSpecification.textInAllColumns(text)));

另一个更新(在所有类型的列中搜索带有 lambda 字段的白名单 - 未检查代码)

public class EmployeeSpecification {
    public static Specification<Employee> textInAllColumns(String text, Set<String> fields) {
        if (!text.contains("%")) {
            text = "%" + text + "%";
        }
        final String finalText = text;

        return  (Specification<Employee>) (root, query, builder) -> 
                builder.or(root.getModel().getDeclaredSingularAttributes().stream().filter(a -> {
                return fields.contains(a.getName());
            }).map(a -> builder.like(root.get(a.getName()), finalText)).toArray(Predicate[]::new));
    }
} 

【讨论】:

  • 可靠的答案。你知道是否有办法在 Java 7 中构建谓词/不使用流?
  • 谢谢!也许是这样的:dzone.com/articles/java-using-specification?应该可以对属性进行 for 循环来进行过滤和映射。 root.getModel().getDeclaredSingularAttributes() 是否可用于旧版本的 Java/Spring Data 是个问题
  • 请注意:Specifications 现在已弃用,取而代之的是 Specification
  • @MichailMichailidis - 看起来规格已被弃用,是否有任何替代方案?新问题:stackoverflow.com/questions/57112319/…
  • 感谢您的出色回答,但有一件事是搜索仅适用于列类型字符串。对于像 Double 和 long 数据类型这样的数字,此搜索将不起作用。有什么想法吗?
【解决方案4】:

如果你想实现,

1.分页,

2。在所有字符串列中搜索,

3.排序方式,

4.排序顺序

在相同的服务/请求中,这是给你的!

Michail Michailidis 的回答给我留下了深刻的印象,我确实以我的方式对其进行了更新,以便它可以用于任何具有分页的实体(页码和页面大小是动态的) 、排序依据、排序顺序

首先将这个类复制到你的末尾:

    public class EntitySpecification {

    public static <T> Specification<T> textInAllColumns(String text) {
        if (!text.contains("%")) {
            text = "%" + text + "%";
        }
        final String finalText = text;

        return (Specification<T>) (root, cq, builder) ->
                builder.or(root.getModel()
                        .getDeclaredSingularAttributes()
                        .stream()
                        .filter(a -> a.getJavaType()
                                .getSimpleName().equalsIgnoreCase("string"))
                        .map(a -> builder.like(root.get(a.getName()), finalText)
                        ).toArray(Predicate[]::new)
                );
    }
}

现在,在您的服务类中,例如在您的UserService class 中,如果您想通过搜索、排序、分页等实现用户列表之类的功能,那么只用这个

Pageable paging;
    if (paginationRequest.getSortOrder().matches("ASC")) {
        paging = PageRequest.of(paginationRequest.getPageNo(),
                paginationRequest.getPageSize(), Sort.by(
                        paginationRequest.getSortBy()).ascending());
    } else {
        paging = PageRequest.of(paginationRequest.getPageNo(),
                paginationRequest.getPageSize(), Sort.by(paginationRequest.getSortBy()).descending());
    }

    List<User> userList = userRepository.findAll(
            EntitySpecification.textInAllColumns(paginationRequest.getSearch())
            , paging).getContent();

现在不要在这里混淆,

PaginationRequest 是请求 POJO 类,getterssetters 最初具有以下功能,

Integer pageNo = 0;
Integer pageSize = 10;
String sortBy = "createdTimeStamp";
String sortOrder;
String search = "";

【讨论】:

    【解决方案5】:

    以上所有解决方案都很棒,但我们也可以使用 Example 和 ExampleMatcher 进行多列搜索

    1. 首先使用搜索参数定义搜索对象
    2. 其次,使用 ExampleMatcher 和 Example 定义自定义示例匹配器
    3. 三、在findAll()方法中使用customExampleMatcher
    /* Build Search object */
    Employee employee=new Employee();
            employee.setFirstName(requestDTO.getFilterText());
            employee.setLastName(requestDTO.getFilterText());
            employee.setEmail(requestDTO.getFilterText());
    
    /* Build Example and ExampleMatcher object */
    ExampleMatcher customExampleMatcher = ExampleMatcher.matchingAny()
                    .withMatcher("firstName", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
                    .withMatcher("lastName", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
                    .withMatcher("email", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase());
    
    Example<Employee> employeeExample= Example.of(employee, customExampleMatcher);
    
    /* Get employees based on search criteria*/
    employeetRepository.findAll(employeeExample, PageRequest.of(requestDTO.getCurrentPageNumber(), requestDTO.getPageSize(), Sort.by(requestDTO.getSortingOrderColumnName()).descending()));
    
    

    【讨论】:

    • 这个答案是有效的。但是,我的问题可以追溯到 2014 年,当时这还不可能。
    • 如果您不想要硬写的列名怎么办?为了以后的可维护性目的,如果编辑或添加列名,它可能会损坏或丢失列。
    猜你喜欢
    • 1970-01-01
    • 2017-08-28
    • 2018-07-13
    • 1970-01-01
    • 1970-01-01
    • 2021-07-08
    • 2018-06-08
    • 2014-05-30
    相关资源
    最近更新 更多