Spring Boot and Hibernate Search integration

Below we show how to use Hibernate Search in a Spring Boot web application.

The prerequisite is to already have Spring Boot configured for Hibernate using JPA. To do that you can see one of our previous posts: this one using JPA via Spring Data JPA (recommended) or another one using JPA directly (that requires little more configurations). In following examples we refer to the first case (using Spring Data JPA).

Add and configure Hibernate Search for Spring Boot

In the pom.xml file add the following dependencies:

<!-- Maven artifact identifier for Hibernate Search -->
<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-search-orm</artifactId>
  <version>4.5.1.Final</version>
</dependency>

<!-- Optional: to use JPA 2.1 -->
<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-entitymanager</artifactId>
</dependency>

See here an example of the whole pom.xml and here for more informations about above dependencies.

To configure Hibernate Search add the following configurations in the application.properties file:

src/main/resources/application.properties
# Specify the DirectoryProvider to use (the Lucene Directory)
spring.jpa.properties.hibernate.search.default.directory_provider = filesystem

# Using the filesystem DirectoryProvider you also have to specify the default
# base directory for all indexes (make sure that the application have write
# permissions on such directory)
spring.jpa.properties.hibernate.search.default.indexBase = /var/netgloo_blog/lucene/indexes/

In a few words it is specified where the Lucene index will be stored (in the directory /var/netgloo_blog/lucene/indexes/). To explore such index (once created) it can be used luke (a free tool).

See here for an example of a whole application.properties file configured for Hibernate Search. For more details about Hibernate Search configuration see here.

Configure a Java entity class

Take for example the User entity from this post, configured for JPA (and Hibernate), then add the @Indexed annotation on the class and the @Field annotation on class’s fields that you want to make searchable by Hibernate Search:

src/main/java/netgloo/models/User.java
/**
 * An entity User composed by four fields (id, email, name, city).
 * The annotation Indexed marks User as an entity which needs to be indexed by
 * Hibernate Search.
 */
@Entity
@Indexed
@Table(name = "users")
public class User {

  // Hibernate Search needs to store the entity identifier in the index for 
  // each entity. By default, it will use for this purpose the field marked 
  // with Id.
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long id;
  
  // You have to mark the fields you want to make searchable annotating them
  // with Field.
  // The parameter Store.NO ensures that the actual data will not be stored in
  // the index (mantaining the ability to search for it): Hibernate Search
  // will execute a Lucene query in order to find the database identifiers of
  // the entities matching the query and use these identifiers to retrieve
  // managed objects from the database.
  @Field(store = Store.NO)
  @NotNull
  private String email;
  
  // store=Store.NO is the default values and could be omitted.
  @Field
  @NotNull
  private String name;

  @Field
  @NotNull
  private String city;
  
  // getter and setter methods

  // ....
  
} // class User

Look here for more details about Hibernate Search entity mapping.

The Lucene analyzer

An important thing that will affects the search is the Lucenze analyzer used to process each indexed field. By default Hibernate Search use the StandardAnalyzer that is based on a grammar-based tokenizer (a search string is tokenized also on punctuation and other rules other than whitespaces) and will exclude a list of stop words from the searched text.

For more informations on how to specify the analyzer in Hibernate Search see here. For a list of availables Lucene analyzers you can see here.

Use Hibernate Search in a web application

Create a class UserSearch containing a method for search user entities via Hibernate Search:

src/main/java/netgloo/search/UserSearch.java
/**
 * Search methods for the entity User using Hibernate search.
 * The Transactional annotation ensure that transactions will be opened and
 * closed at the beginning and at the end of each method.
 */
@Repository
@Transactional
public class UserSearch {

  // Spring will inject here the entity manager object
  @PersistenceContext
  private EntityManager entityManager;
    
  /**
   * A basic search for the entity User. The search is done by exact match per
   * keywords on fields name, city and email.
   * 
   * @param text The query text.
   */
  public List search(String text) {
    
    // get the full text entity manager
    FullTextEntityManager fullTextEntityManager =
        org.hibernate.search.jpa.Search.
        getFullTextEntityManager(entityManager);
    
    // create the query using Hibernate Search query DSL
    QueryBuilder queryBuilder = 
        fullTextEntityManager.getSearchFactory()
        .buildQueryBuilder().forEntity(User.class).get();
    
    // a very basic query by keywords
    org.apache.lucene.search.Query query =
        queryBuilder
          .keyword()
          .onFields("name", "city", "email")
          .matching(text)
          .createQuery();

    // wrap Lucene query in an Hibernate Query object
    org.hibernate.search.jpa.FullTextQuery jpaQuery =
        fullTextEntityManager.createFullTextQuery(query, User.class);
  
    // execute search and return results (sorted by relevance as default)
    @SuppressWarnings("unchecked")
    List results = jpaQuery.getResultList();
    
    return results;
  } // method search

} // class UserSearch

More informations on how to build queries can be found here.

Use the UserSearch class inside a controller, taking the query text from the HTTP request and returning a view showing search results:

src/main/java/netgloo/controllers/MainController.java
/**
 * MainController class
 */
@Controller
public class MainController {
  
  // Inject the UserSearch object
  @Autowired
  private UserSearch userSearch;

  /**
   * Show search results for the given query.
   *
   * @param q The search query.
   */
  @RequestMapping("/search")
  public String search(String q, Model model) {
    List searchResults = null;
    try {
      searchResults = userSearch.search(q);
    }
    catch (Exception ex) {
      // here you should handle unexpected errors
      // ...
      // throw ex;
    }
    model.addAttribute("searchResults", searchResults);
    return "search";
  }
  
} // class MainController

With Thymeleaf you can add the following view to render such results:

src/main/resources/templates/search.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<ol>
  <li th:each="user : ${searchResults}">
    <b><span th:text="${user.name}"></span></b> - 
    <span th:text="${user.city}"></span> -
    <span th:text="${user.email}"></span>
  </li>
</ol>
</body>
</html>

Launching the Spring Boot application, this url will shown a list of results for the entity user:

/search?q=some text to search

Note: due to the StandardAnalyzer a search for only a stop word (like “a” or “the“) will be seen as an empty search (and an error will occur).

Build the Lucene index

Using Hibernate to add, remove or update entities the Lucene index will be automatically updated by Hibernate Search.

But if you already have some entries in your database the first time you should manually build the index. For test purpose you can use the following class in order to build the index at Spring Boot start up:

src/main/java/netgloo/BuildSearchIndex.java
@Component
public class BuildSearchIndex
implements ApplicationListener<ApplicationReadyEvent> {
  
  @PersistenceContext
  private EntityManager entityManager;
  
  /**
   * Create an initial Lucene index for the data already present in the
   * database.
   * This method is called when Spring's startup.
   */
  @Override
  public void onApplicationEvent(final ApplicationReadyEvent event) {
    try {
      FullTextEntityManager fullTextEntityManager =
        Search.getFullTextEntityManager(entityManager);
      fullTextEntityManager.createIndexer().startAndWait();
    }
    catch (InterruptedException e) {
      System.out.println(
        "An error occurred trying to build the serach index: " +
         e.toString());
    }
    return;
  }


} // class

Note: the code above works with Spring Boot 1.3.0 or later, if you are using previous versions you should use the ContextRefreshedEvent event instead, as described here.

Try it yourself

You can try the above code getting the whole project from our github repository:
https://github.com/netgloo/spring-boot-samples/tree/master/spring-boot-hibernate-search

References

https://docs.jboss.org/hibernate/search/4.5/reference/en-US/html/
https://lucene.apache.org/core/4_10_2/index.html
http://www.lucenetutorial.com/lucene-query-syntax.html
https://stackoverflow.com/questions/25474445/spring-boot-hibernate-search-properties/26496091#26496091

  • hzms

    hello, can i using this example with spring mvc instead of spring boot?

    • Hello.

      Sorry, but I’m not expert about Spring MVC so I can’t help you in this. But I’m pretty sure you can use almost all of the code above using Spring MVC if you configure it correctly.

      You can try to ask your question on StackOverflow.

Categories

Category BootstrapCategory CoffeescriptCategory DrupalCategory GravCategory HTMLCategory JavascriptCategory JoomlaCategory jQueryCategory LaravelCategory MagentoCategory PHPCategory SharePointCategory SpringCategory ThymeleafCategory WordPressCategory Workflow

Comments

Developed and designed by Netgloo
© 2016 Netgloo