Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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. 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.

I see 2 options to do this:

  1. Load all users users from DB and filter them in Java
  2. Write a JPQL query with many ORs and LIKE % :searchString %

Option 1 is not good for performance but quite nice to write.

Option 2 is performant beacuse executed on DB side but cumbersome to write.

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.

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:

   @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. Also there is a problem with translatin boolean to "yes" and "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?

like image 789
Robert Niestroj Avatar asked Nov 25 '15 13:11

Robert Niestroj


People also ask

How do I search in JPA?

If keyword is null, invoke the default findAll() method of the repository interface (provided by Spring Data JPA). As you can see, beside the search result stored in the listProducts object added to the model, the keyword is also added to the model so in the search form we can display the keyword again.

What is the use of @query in JPA?

In order to define SQL to execute for a Spring Data repository method, we can annotate the method with the @Query annotation — its value attribute contains the JPQL or SQL to execute. The @Query annotation takes precedence over named queries, which are annotated with @NamedQuery or defined in an orm. xml file.


1 Answers

Solved this by saving the search string on every persist and update to the DB. First created a column for the searchString:

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

Storage is cheap, overhead on DB is not that big.

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:

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

Or using Query By Example:

  ExampleMatcher matcher = ExampleMatcher.matching().
          withMatcher("searchString", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.CONTAINING).ignoreCase());
like image 153
Robert Niestroj Avatar answered Oct 28 '22 08:10

Robert Niestroj