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

时间:2015-11-25 13:13:15

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

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

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

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

  1. 从DB加载所有用户用户并使用Java过滤它们
  2. 编写包含许多ORsLIKE % :searchString %
  3. 的JPQL查询

    选项1 对性能不利,但写得很好。

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

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

    在用户实体中,我有一个方法,它返回我希望被空格分隔的所有字段:

       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的另一方面,我最终得到这样的查询,我认为这不是实现此功能的最好和最简单的方法。 translatin boolean也有“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);
    

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

2 个答案:

答案 0 :(得分:7)

通过在每个持久化合物上保存搜索字符串并更新到数据库来解决此问题。 首先为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());

答案 1 :(得分:0)

嗯,在大多数情况下,选项1 不是真正的替代方案。如果您的应用程序在寄存器中长大,那么一段时间后内存和性能问题就会受到影响。我向你保证。

我在选项2 中看不到问题。不是高效的,但理解起来很简单。

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