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

How to implement simple full text search in JPA (Spring Data JPA)?

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

我的场景如下。我有一个 User 实体,在 UI 上有一个 table,它显示了大部分用户属性,我希望用户在文本框中输入一个搜索词并搜索所有属性.

我看到了 2 个选项:

  1. 从数据库中加载所有用户并在 Java 中过滤
  2. 编写一个包含多个 ORsLIKE % :searchString %
  3. 的 JPQL 查询

选项 1 性能不佳,但写起来很好。

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

现在我正在起诉选项 1,因为我需要将布尔值转换为 "yes"/"no" 并且还有一个配置文件枚举,我想通过它的字段描述而不是实际枚举值进行搜索。

在用户实体中,我有一个方法returns我想搜索的所有字段用空格分隔:

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

在服务中,我从数据库加载所有用户并按此搜索字符串过滤:

   @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 中,我最终得到了这样的查询,我认为这不是实现此功能的最好和最简单的方法。将布尔值翻译成 "yes" 和 "no".

也存在问题
@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);

这个问题有更好的解决办法吗?

嗯,在大多数情况下,选项 1 并不是一个真正的替代方案。如果你的应用程序长大了寄存器内存和性能问题将在一段时间后打击你。我向你保证。

我在选项 2 中看不到问题。性能不佳,但易于理解。

如果数据库性能有问题,您可以创建本机查询来调用数据库的本机函数来执行全文搜索。不会是 JPQL 查询,但对于您正在尝试执行的此类查询,可以使用此解决方案。

通过在每次持久化和更新数据库时保存搜索字符串解决了这个问题。 首先为 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);
   }

然后我可以使用 LIKE 进行正常的 JPQL 查询:

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

或使用Query By Example:

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