一尘不染

如何在JPA(Spring Data JPA)中实现简单的全文本搜索?

hibernate

我正在使用JPA 2.1(将Hibernate 4作为实现)和Spring Data JPA 1.9.0。如何实现全文搜索?

我的情况如下。我有一个User实体,在UI上有一个表,其中显示了大多数用户属性,我希望用户在文本框中输入搜索词并搜索所有属性。

我看到2个选项可以做到这一点:

  1. 从数据库加载所有用户用户并使用Java过滤用户
  2. 写了许多JPQL查询ORsLIKE % :searchString %

选项1 的性能不好,但是写起来很不错。

选项2 是高性能的,因为在DB端执行但编写起来很麻烦。

现在我正在使用选项1,因为我需要将boolean转换为"yes"/"no"并且还需要一个配置文件枚举,我想在其中按字段描述而不是实际枚举值进行搜索。

在User实体中,我有一个方法,该方法返回要搜索的所有字段,并用空格分隔:

   public String getSearchString(){
      return StringUtils.join(
              Arrays.asList(
                      login, 
                      firstName, 
                      lastName, 
                      email, 
                      active ? "yes" : "no", 
                      profile.getDescription())
              , " ");
   }

我在一项服务中从DB加载所有用户,并通过此搜索字符串进行过滤:

   @Override
   public List<User> getUsers(final String searchText) {
      final List<User> users = getUsers();
      if(StringUtils.isBlank(searchText)){
         return users;
      }
      CollectionUtils.filter(users, new Predicate<User>() {
         @Override
         public boolean evaluate(User object) {
            return StringUtils.containsIgnoreCase(object.getSearchString(), searchText);
         }
      });
      return users;
   }

在JPQL的另一端,我最终遇到这样的查询,我认为这不是实现此功能的最好,最简单的方法。将布尔值转换为“是”和“否”也存在问题。

@Query("SELECT r FROM User r WHERE "
        + "r.firstname LIKE '%' || :searchString || '%' "
        + "OR r.lastname LIKE '%' || :searchString || '%' "
        + "OR r.login LIKE '%' || :searchString || '%' "
        + "OR r.profile.description LIKE '%' || :searchString || '%' "
        + "OR r.active LIKE '%' || :searchString || '%' "
        + "OR r.email LIKE '%' || :searchString || '%'")
List<User> selectUsers(@Param("searchString")String searchString, Pageable page);

有没有更好的解决方案来解决这个问题?


阅读 527

收藏
2020-06-20

共1个答案

一尘不染

通过在每个持久存储上保存搜索字符串并更新到数据库来解决此问题。首先为searchString创建一列:

   @Column(name = "SEARCH_STRING", length = 1000)
   private String searchString;

存储很便宜,DB上的开销并不大。

然后保存更新并继续:

   @PreUpdate
   @PrePersist
   void updateSearchString() {
      final String fullSearchString = StringUtils.join(Arrays.asList(
              login,
              firstName,
              lastName,
              email,
              Boolean.TRUE.equals(active) ? "tak" : "nie",
              profile.getDescription()),
              " ");
      this.searchString = StringUtils.substring(fullSearchString, 0, 999);
   }

然后我可以使用以下常规JPQL查询LIKE

SELECT u FROM User u WHERE u.searchString LIKE '%' || :text || '%'

或使用示例查询

  ExampleMatcher matcher = ExampleMatcher.matching().
          withMatcher("searchString", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.CONTAINING).ignoreCase());
2020-06-20