简体   繁体   English

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

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

i'm using JPA 2.1 (Hibernate 4 as impl) and Spring Data JPA 1.9.0. 我正在使用JPA 2.1(Hibernate 4 as impl)和Spring Data JPA 1.9.0。 How do i implement full text search? 我如何实现全文搜索?

My scenario is as follows. 我的方案如下。 I have a User entity and on the UI a have a table which display's most of users properties and i want the user to give text box enter there a search term and search in all properties. 我有一个User实体,在UI上有一个显示大多数用户属性的表,我希望用户给文本框输入一个搜索词并在所有属性中搜索。

I see 2 options to do this: 我看到有两个选项可以做到这一点:

  1. Load all users users from DB and filter them in Java 从DB加载所有用户用户并使用Java过滤它们
  2. Write a JPQL query with many ORs and LIKE % :searchString % 编写带有许多ORsLIKE % :searchString %的JPQL查询LIKE % :searchString %

Option 1 is not good for performance but quite nice to write. 选项1对性能不利,但写得很好。

Option 2 is performant beacuse executed on DB side but cumbersome to write. 选项2是在DB端执行的高性能,但编写起来很麻烦。

Right now im suing option 1 because i need to translate boolean to "yes"/"no" and also have a profile enum where i want to search by it's field description and not by actual enum value. 现在即时通过选项1,因为我需要将布尔值转换为"yes"/"no" ,并且还有一个配置文件枚举,我想通过它的字段描述而不是实际的枚举值进行搜索。

In the User entity i have a method which returns all fields i want to be searched seperated by spaces: 在用户实体中,我有一个方法,它返回我希望被空格分隔的所有字段:

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

The in a service i load all users from DB and filter by this search string: 在服务中我从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;
   }

On the other side in JPQL i end up with queries like this, which i dont think is the nice'est and easiest way to implement this functionality. 在JPQL的另一方面,我最终得到这样的查询,我认为这不是实现此功能的最好和最简单的方法。 Also there is a problem with translatin boolean to "yes" and "no". 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);

Is there a better solution to this problem? 这个问题有更好的解决方案吗?

Solved this by saving the search string on every persist and update to the DB. 通过在每个持久化合物上保存搜索字符串并更新到数据库来解决此问题。 First created a column for the searchString: 首先为searchString创建一个列:

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

Storage is cheap, overhead on DB is not that big. 存储很便宜,DB上的开销并不大。

Then the saving on update and persist: 然后保存更新并保持:

   @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);
   }

Then i can have a normal JPQL query with LIKE : 然后我可以使用LIKE进行正常的JPQL查询:

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

Or using Query By Example : 或者使用Query By Example

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

Well, for the most cases, option 1 is not a really an alternative. 嗯,对于大多数情况, 选项1不是真正的替代方案。 If your application grow up of registers the memory and performance problems will hit you after some time. 如果您的应用程序在寄存器中长大,那么一段时间后内存和性能问题就会受到影响。 I guarantee you. 我向你保证。

I cannot see a problem in the option 2 . 我在选项2中看不到问题。 Is not performant but it's simple to understand. 理解不高,但理解起来很简单。

If performance on database is a problem, you can create a native query to call the native function of your database to do a full text search. 如果数据库上的性能存在问题,则可以创建本机查询以调用数据库的本机函数来执行全文搜索。 Will not be a JPQL query, but for this kind of query you are trying to do this solution can be used. 不会是JPQL查询,但是对于这种类型的查询,您可以尝试使用此解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM