Main Tutorials

Spring Boot + Hibernate Search example

Here we will create a Spring Boot web application example with Hibernate Search + Thymeleaf template engine, and deploy it as a WAR to Wildfly 10.1.

Technologies used:

  1. Spring Boot 1.5.6.RELEASE
  2. Java 8
  3. Hibernate Search 5.6.1.Final
  4. Embedded Tomcat, Wildfly 8.1 Final & 10.1 Final

1. Project Structure

A standard Maven project structure

1. Project Dependencies

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
	http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mkyong</groupId>
    <artifactId>spring-boot-web-wildfly-search</artifactId>
    <version>0.0.1</version>
    <packaging>war</packaging>
    <name>mkyong-wildfly-spring-boot</name>
    <description>Spring Boot Web Hibernate Search Example</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <logback.version>1.1.9</logback.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!--
            Comment out if deploy as WAR file
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
            -->
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-search-engine -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-search-engine</artifactId>
            <version>5.6.1.Final</version>
            <exclusions>
                <exclusion>
                    <groupId>org.jboss.logging</groupId>
                    <artifactId>jboss-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-search-orm -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-search-orm</artifactId>
            <version>5.6.1.Final</version>
            <exclusions>
                <exclusion>
                    <groupId>org.jboss.logging</groupId>
                    <artifactId>jboss-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.jboss.logging</groupId>
            <artifactId>jboss-logging</artifactId>
            <version>3.3.0.Final</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.1.9</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <layout>WAR</layout>
                    <executable>true</executable>
                    <mainClass>com.mkyong.WildflySpringBootApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

2. Model

For this example application, we are creating a website that allows you to search for rare baseball cards. So, we make our model the baseball card and annotate what are searchable fields.

BaseballCard.java

package com.mkyong.model;

import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Indexed
@Entity
public class BaseballCard {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    @Field
    private String name;
    @Field
    private String rarityLevel;
    @Field
    private int year;
	
    //getters n setters

}

3. Repository

Here we have a Spring Data CrudRepository for the BaseballCard model that will allow us to perform the create and read functions needed.

BaseballCardRepository.java

package com.mkyong.dao;

import org.springframework.data.repository.CrudRepository;

import com.mkyong.model.BaseballCard;

public interface BaseballCardRepository extends CrudRepository<BaseballCard,Long> {
}

4. Hibernate Search

HibernateSearchService.java

package com.mkyong.service;

import com.mkyong.model.BaseballCard;
import org.apache.lucene.search.Query;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.Search;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import java.util.List;

@Service
public class HibernateSearchService {


    @Autowired
    private final EntityManager centityManager;


    @Autowired
    public HibernateSearchService(EntityManager entityManager) {
        super();
        this.centityManager = entityManager;
    }


    public void initializeHibernateSearch() {

        try {
            FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(centityManager);
            fullTextEntityManager.createIndexer().startAndWait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Transactional
    public List<BaseballCard> fuzzySearch(String searchTerm) {

        FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(centityManager);
        QueryBuilder qb = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(BaseballCard.class).get();
        Query luceneQuery = qb.keyword().fuzzy().withEditDistanceUpTo(1).withPrefixLength(1).onFields("name")
                .matching(searchTerm).createQuery();

        javax.persistence.Query jpaQuery = fullTextEntityManager.createFullTextQuery(luceneQuery, BaseballCard.class);

        // execute search

        List<BaseballCard> BaseballCardList = null;
        try {
            BaseballCardList = jpaQuery.getResultList();
        } catch (NoResultException nre) {
            ;// do nothing

        }

        return BaseballCardList;


    }
}

This configures the HibernateSearchService to be accessible.

HibernateSearchConfiguration.java

package com.mkyong;

import javax.persistence.EntityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.mkyong.service.HibernateSearchService;
@EnableAutoConfiguration 
@Configuration
public class HibernateSearchConfiguration {

	@Autowired
	private EntityManager bentityManager;

	@Bean
	HibernateSearchService hibernateSearchService() {
		HibernateSearchService hibernateSearchService = new HibernateSearchService(bentityManager);
		hibernateSearchService.initializeHibernateSearch();
		return hibernateSearchService;
	}
}

5. Service for Model

This is a simple service that adds three cards to the repository for our example. For demonstration of SOLID programming principles, there is a separate interface for the service.

CardService.java

package com.mkyong.service;

public interface CardService {

    void addCards();
    
}
CardServiceImpl.java

package com.mkyong.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.mkyong.dao.BaseballCardRepository;
import com.mkyong.model.BaseballCard;

@Service
public class CardServiceImpl implements CardService {

    @Autowired
    BaseballCardRepository cardrepository;

    BaseballCard TedWilliams = new BaseballCard();
    BaseballCard BobGibson = new BaseballCard();
    BaseballCard HonusWagner = new BaseballCard();

    public void addCards() {
        TedWilliams.setName("Ted Williams");
        TedWilliams.setYear(1954);
        TedWilliams.setRarityLevel("Very Rare");

        cardrepository.save(TedWilliams);

        BobGibson.setName("Bob Gibson");
        BobGibson.setYear(1959);
        BobGibson.setRarityLevel("Very Rare");

        cardrepository.save(BobGibson);

        HonusWagner.setName("Honus Wagner");
        HonusWagner.setYear(1909);
        HonusWagner.setRarityLevel("Rarest");

        cardrepository.save(HonusWagner);

        System.out.println("Cards have been added : " + cardrepository.findAll());

    }
}

6. Controller

The controller is responsible for connecting the backend services to our front end Thymeleaf template.

CardController.java

package com.mkyong.controller;

import com.mkyong.model.BaseballCard;
import com.mkyong.service.CardService;
import com.mkyong.service.HibernateSearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@Controller
public class CardController {

    @Autowired
    private HibernateSearchService searchservice;

    @Autowired
    private CardService cardservice;

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String search(@RequestParam(value = "search", required = false) String q, Model model) {
        List<BaseballCard> searchResults = null;
        try {
            cardservice.addCards();
            searchResults = searchservice.fuzzySearch(q);

        } catch (Exception ex) {
            // here you should handle unexpected errors
            // ...
            // throw ex;
        }
        model.addAttribute("search", searchResults);
        return "index";

    }

}

7. Configuration

Now we need to configure our Thymeleaf and Hibernate Search.

application.properties

#==================================
# = Thymeleaf configurations 
#==================================
spring.thymeleaf.check-template-location=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.content-type=text/html
spring.thymeleaf.cache=false
server.contextPath=/
#===================================
#= 
# Specify 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 
spring.jpa.properties.hibernate.search.default.indexBase = indexpath

8. Thymeleaf Template

For our Thymeleaf template, we have two purposes: allow the user to search and display the search results once a search is complete. Thankfully, Thymeleaf has conditional statements that allow us to display results if a user’s search returns items.

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <!-- META SECTION -->
    <title>Mkyong Wildfly Example</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <!-- END META SECTION -->
    <!--  BEGIN STYLE -->
    <style>
        table, th, td {
            border: 1px solid black;
            padding: 1px;
        }
    </style>
    <!--  END STYLE -->

</head>
<body>
<form action="#" th:action="@{/}" th:object="${search}">
    <label for="search_input">Search:</label> <input name="search"
                                                     id="search">
    </input>
    <div th:if="${not #lists.isEmpty(search)}">
        <h2>Search Results</h2>
        <table>
            <thead>
            <tr>
                <th>ID</th>
                <th>name</th>
                <th>rarity level</th>
                <th>year</th>
            </tr>
            </thead>
            <tbody>
            <tr th:each="search : ${search}">
                <td th:text="${search.id}">Text ...</td>
                <td th:text="${search.name}">Text ...</td>
                <td th:text="${search.rarityLevel}">Text ...</td>
                <td th:text="${search.year}">Text...</td>
            </tr>
            </tbody>
        </table>
    </div>
</form>
</body>
</html>

9. SpringBootApplication

WildflySpringBootApplication.java

package com.mkyong;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class WildflySpringBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(WildflySpringBootApplication.class, args);
    }

}

10. Demo

Start Spring Boot Application with the default embedded Tomcat container.


$ mvn spring-boot:run

Access http://localhost:8080/

I’ve searched “Ted” and the results are displayed in the table, http://localhost:8080/?search=ted

11. Deploy WAR file to Wildfly

The Wildfly is a JBoss open-source application server. Wildfly can be downloaded from their official website

Wildfly 10 comes with:
ActiveMQ Artemis, HA Singleton Deployments, HA Singleton Message Driven Beans (MDBs) and MDB Delivery Groups, Stateless Session Bean and Message Driven Bean Automatic Pool Sizing, Hibernate 5

Wildfly 8 and 9:
These versions do not come with the technologies built-in that Wildfly 10 has. This means we will not need to make exclusions in their configuration file.

11.1 Exclude the embedded Tomcat container.

pom.xml

	<dependency>
           <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

11.2 Extends SpringBootServletInitializer

WildflySpringBootApplication.java

package com.mkyong;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;

@SpringBootApplication
public class WildflySpringBootApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(WildflySpringBootApplication.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(WildflySpringBootApplication.class, args);
    }

}

11.3 Maven build and copy the WAR file to JBoss Wildfly and start it.

Terminal

$ mvn package

11.4 Considerations for Wildfly 10

For Wildfly 10 we need to make additional changes because of the technologies that come prepackaged with it. In our case, we are using Hibernate Search and that is packaged in Wildfly 10.

For users of Wildfly 10, you need to create a persistence.xml file and enter:

persistence.xml

wildfly.jpa.hibernate.search.module = none

Done.

Download Source Code

References

  1. Hibernate Search 5.6 Documentation
  2. Deploying Spring Boot Applications
  3. Thymeleaf + Spring
  4. Spring Boot – Deploy WAR file to Tomcat

About Author

author image
Michael is a software engineer located in the Washington DC area that is interested in Java, cyber security, and open source technologies. Follow his personal blog to read more from Michael.

Comments

Subscribe
Notify of
8 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
Manjil Tamang
6 years ago

Hi,
Anyone can tell how pagination is done here???

thar
4 years ago

Then why u don’t show how to carry input data “Ted” to show like “search=Ted”.This is the most important part.

sunil
4 years ago

Thanks for the article.
I am getting BeanCreationException exception. can someone help me out here?
Error starting ApplicationContext. To display the conditions report re-run your application with ‘debug’ enabled.
2019-09-07 14:12:28.057 ERROR 54460 — [ main] o.s.boot.SpringApplication : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘hibernateSearchConfiguration’: Injection of autowired dependencies failed; nested exception is java.lang.NoSuchFieldError: session
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:380) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1411) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:592) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:843) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877) ~[spring-context-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549) ~[spring-context-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:142) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) [spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) [spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) [spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) [spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) [spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at com.deals.warangaldeals.WarangaldealsApplication.main(WarangaldealsApplication.java:14) [classes/:na]
Caused by: java.lang.NoSuchFieldError: session
at org.hibernate.search.impl.FullTextSessionImpl.getSearchIntegrator(FullTextSessionImpl.java:193) ~[hibernate-search-orm-5.6.1.Final.jar:5.6.1.Final]
at org.hibernate.search.impl.FullTextSessionImpl.createMassIndexerFactory(FullTextSessionImpl.java:205) ~[hibernate-search-orm-5.6.1.Final.jar:5.6.1.Final]
at org.hibernate.search.impl.FullTextSessionImpl.createIndexer(FullTextSessionImpl.java:175) ~[hibernate-search-orm-5.6.1.Final.jar:5.6.1.Final]
at org.hibernate.search.jpa.impl.FullTextEntityManagerImpl.createIndexer(FullTextEntityManagerImpl.java:340) ~[hibernate-search-orm-5.6.1.Final.jar:5.6.1.Final]
at com.deals.warangaldeas.search.HibernateSearchService.initializeHibernateSearch(HibernateSearchService.java:34) ~[classes/:na]
at com.deals.warangaldeals.HibernateSearchConfiguration.hibernateSearchService(HibernateSearchConfiguration.java:20) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_161]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_161]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_161]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_161]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:708) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
… 17 common frames omitted

Aaron
4 years ago

Great article. One question, why do you do both field and constructor injection for centityManager in HibernateSearchService?

Alioune
5 years ago

Hi,
This page is so important to follow this tutorial : http://hibernate.org/search/releases/ (about problem compatibilty – I had many problems)
btw the pom dependency hibernate-search-engine is useless, already included in hibernate-search-orm
BTW nice article, clear and precise 🙂

Jayashree
6 years ago

Thank you

Navneet
6 years ago

Hi Michael,

One of the well explained article. Keep up good work.

Thanks.

AbrahamStalin
6 years ago

Thanks, great article.