Search code examples
hibernatespring-bootspring-data-jpafull-text-searchhibernate-search

How to implement full text search in Spring Boot with JPA + MySQL


public class Product {

    private Long id;    

    private String name;

    private String description;

}

Is there any way to implement a full text search with JpaRepository for the description of the class Product ?


Solution

  • You can use Hibernate Search, a library that plugs into Hibernate ORM to index your entities into a Lucene index on the filesystem, on the fly, when you send them to your database. See the Getting started guide.

    (EDIT: Nowadays Hibernate Search also supports using Elasticsearch as a backend; it will automatically duplicate part of your database in Elasticsearch OR in a local Lucene index, whichever you chose.)

    Querying the Lucene/Elasticsearch index is a bit different than querying the database, so you cannot use HQL or Criteria like you usually would. Hibernate Search offers its own query DSL.

    If you want to use auto-magically generated method implementations in your repository, you can rely on Snowdrop, which is a Hibernate Search / Spring Data integration, but it hasn't been updated in a while.

    Your best bet is probably to define query methods in your repository interface, then implement them yourself using Hibernate Search APIs. It's really not that complicated, and it's generally recommended for all but the most obvious queries. See the Spring Data JPA documentation.

    Essentially you'll have something like the snippet below. Remember that you will need to reindex your database before this works! See the getting started guide for more information.

    With Hibernate Search 6+:

    @Indexed // Add this
    public class Product {
    
        private Long id;    
    
        @FullTextField // And this
        private String name;
    
        @FullTextField // And this
        private String description;
    
    }
    
    public interface ProductRepository extends CrudRepository<Product, Long>, CustomizedProductRepository {
      // Declare automatically generated methods here
    }
    
    
    public interface CustomizedProductRepository {
      List<Product> search(String terms, int limit, int offset);
    }
    
    
    
    public class CustomizedProductRepositoryImpl implements CustomizedProductRepository {
    
      @PersistenceContext
      private EntityManager em;
    
      @Override
      public List<Product> search(String terms, int limit, int offset) {
        return Search.session(em).search(Product.class)
                .where(f -> f.match()
                        .fields("name", "description")
                        .matching(terms))
                .fetchHits(offset, limit);
      }
    }
    

    OR with Hibernate Search 5:

    @Indexed // Add this
    public class Product {
    
        private Long id;    
    
        @Field // And this
        private String name;
    
        @Field // And this
        private String description;
    
    }
    
    public interface ProductRepository extends CrudRepository<Product, Long>, CustomizedProductRepository {
      // Declare automatically generated methods here
    }
    
    
    public interface CustomizedProductRepository {
      List<Product> search(String terms, int limit, int offset);
    }
    
    
    
    public class CustomizedProductRepositoryImpl implements CustomizedProductRepository {
    
      @PersistenceContext
      private EntityManager em;
    
      @Override
      public List<Product> search(String terms, int limit, int offset) {
        FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);
    
        QueryBuilder queryBuilder = fullTextEntityManager.getSearchFactory()
            .buildQueryBuilder().forEntity(Product.class).get();
        org.apache.lucene.search.Query luceneQuery = queryBuilder
            .keyword()
            .onFields("name", "description")
            .matching(terms)
            .createQuery();
    
        // wrap Lucene query in a javax.persistence.Query
        javax.persistence.Query jpaQuery =
            fullTextEntityManager.createFullTextQuery(luceneQuery, Product.class);
    
        jpaQuery.setMaxResults(limit);
        jpaQuery.setFirstResult(offset);
    
        // execute search
        return jpaQuery.getResultList();
      }
    }