Hibernate ORM 5 + Hibernate Search integration example

Posted on November 7, 2017


Hibernate Search is an enterprise tools which provides full text search capabilities for your application. It offers full text search support for entities stored by the Hibernate ORM.

Hibernate Search depends on the Apache Lucene, which is a high-performance, full-featured text search engine library written entirely in Java.

Hibernate Search indexes your entity objects and keeps it up to date when entities are created/updated/deleted in the database.

In this post, I will show you how to configure and integrate the Hibernate Search with Hibernate ORM application.

Tools and technologies used for this application are - 

  • Hibernate ORM 5.2.12.Final
  • Hibernate Search 5.8.2.Final
  • Log4j 2.8.2
  • JavaSE 1.8
  • MySQL Server 5.7.12
  • Eclipse Oxygen.1
  • Maven 3.3.9

Project structure

Final project structure of our application will look like as follows.

hibernate5-search-example.png

Jar dependencies

Open pom.xml file of your maven project, add the dependencies below.

pom.xml

<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.boraji.tutorial.hibernate</groupId>
  <artifactId>hibernate-search-example</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>hibernate-search-example</name>

  <dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>5.2.12.Final</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-search-orm</artifactId>
      <version>5.8.2.Final</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>6.0.6</version>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.8.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-api</artifactId>
      <version>2.8.2</version>
    </dependency>
  </dependencies>

  <build>
    <sourceDirectory>src/main/java</sourceDirectory>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.5.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Entity classes

Consider the following @Entity classes - Department and Employee.

Annotate the @Entity class and its properties with Hibernate Search annotations such as @Indexed, @Field, @IndexedEmbedded, @ContainedIn etc.

Department.java

package com.boraji.tutorial.hibernate.entity;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.IndexedEmbedded;
import org.hibernate.search.annotations.Store;

@Entity
@Table(name = "DEPARTMENT")
@Indexed
public class Department {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "DPT_ID")
   private long id;

   @Field(index = Index.YES, analyze = Analyze.YES, store = Store.NO)
   @Column(name = "NAME", nullable = false, unique = true)
   private String name;

   @IndexedEmbedded
   @OneToMany(cascade = CascadeType.ALL, mappedBy = "department", 
         fetch = FetchType.EAGER)
   private List<Employee> employees = new ArrayList<>();

   public Department() {
   }

   // Getter and Setter methods
}

Employee.java

package com.boraji.tutorial.hibernate.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.ContainedIn;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Store;

@Entity
@Table(name = "EMPLOYEE")
public class Employee {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "EMP_ID")
   private long id;

   @Field(index = Index.YES, analyze = Analyze.YES, store = Store.NO)
   @Column(name = "NAME", nullable = false)
   private String name;

   @Column(name = "DESIGNATION")
   private String designation;

   @ContainedIn
   @ManyToOne
   @JoinColumn(name = "DPT_ID")
   private Department department;

   public Employee() {
   }

   //Getter and Setter methods
}

@Indexed - This annotation is used by the Hibernate Search to index an @Entity class. Only @Indexed entities can be searched.

@Field - This annotation is used to mark an @Entity property to be indexed. Only @Field properties are searchable.

@IndexedEmbedded - This annotation is used to index the associated entities, defined via @ManyToMany, @OneToOne, @ManyToOne, @Embedded and @ElementCollection.

@ContainedIn - This annotation is used with @IndexedEmbedded annotation on the inverse side of a bidirectional association.
 

 

Hibernate Utility class

To integrate the Hibernate Search with Hibernate ORM, you need to set the following properties in Hibernate configuration class.

1 - Set the default directory provider to store the index files. In this example, we will use the Apache Lucene Directory which stores the index file on the file system. You can use the Infinispan Directory to store the indexes in memory.

hibernate.search.default.directory_provider=filesystem

2 - If you are using the file system as a directory provider then define the path for index files to be stored.

hibernate.search.default.indexBase=C:/hibernate/lucence/indexes

3 - You can enable the trace level logging by setting the following properties to true.

hibernate.search.default.indexwriter.infostream=true

Here is the complete example of HibernateUitl helper class to bootstrap application.

HibernateUtil.java

package com.boraji.tutorial.hibernate;

import java.util.HashMap;
import java.util.Map;

import org.hibernate.SessionFactory;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Environment;

import com.boraji.tutorial.hibernate.entity.Department;
import com.boraji.tutorial.hibernate.entity.Employee;

public class HibernateUtil {

   private static StandardServiceRegistry registry;
   private static SessionFactory sessionFactory;

   public static SessionFactory getSessionFactory() {
      if (sessionFactory == null) {
         try {
            StandardServiceRegistryBuilder registryBuilder = 
                  new StandardServiceRegistryBuilder();

            Map<String, Object> settings = new HashMap<>();
            settings.put(Environment.DRIVER, "com.mysql.cj.jdbc.Driver");
            settings.put(Environment.URL, "jdbc:mysql://localhost:3306/BORAJI?useSSL=false");
            settings.put(Environment.USER, "root");
            settings.put(Environment.PASS, "admin");
            settings.put(Environment.HBM2DDL_AUTO, "update");
            settings.put(Environment.SHOW_SQL, true);

            // Hibernate search properties
            settings.put("hibernate.search.default.directory_provider", "filesystem");
            settings.put("hibernate.search.default.indexBase", "C:/hibernate/lucence/indexes");
            settings.put("hibernate.search.default.indexwriter.infostream", true);

            registryBuilder.applySettings(settings);

            registry = registryBuilder.build();
            MetadataSources sources = new MetadataSources(registry)
                  .addAnnotatedClass(Employee.class)
                  .addAnnotatedClass(Department.class);

            Metadata metadata = sources.getMetadataBuilder().build();
            sessionFactory = metadata.getSessionFactoryBuilder().build();
         } catch (Exception e) {
            if (registry != null) {
               StandardServiceRegistryBuilder.destroy(registry);
            }
            e.printStackTrace();
         }
      }
      return sessionFactory;
   }

   public static void shutdown() {
      if (registry != null) {
         StandardServiceRegistryBuilder.destroy(registry);
      }
   }
}

Hibernate Search logging

In this example, we will use the log4j 2 API for logging Hibernate Search.

Create a log4j2.xml file under src/main/resources folder and write the following code in it.

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
  <Appenders>
    <!-- Console Appender -->
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout
        pattern="%d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %msg%n" />
    </Console>

  </Appenders>
  <Loggers>
    <!-- Log everything in hibernate -->
    <Logger name="org.hibernate.search" level="trace" additivity="false">
      <AppenderRef ref="Console" />
    </Logger>

    <Root level="error">
      <AppenderRef ref="Console" />
    </Root>
  </Loggers>
</Configuration>

 

Run Application

Create the MainApp class to test the Hibernate Search and Hibernate ORM configuration.

To execute the full-text queries you need to use the method of org.hibernate.search.FullTextSession, which extend the hibernate Session.

MainApp.java

package com.boraji.tutorial.hibernate;

import java.util.List;

import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.query.Query;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.hibernate.search.query.dsl.QueryBuilder;

import com.boraji.tutorial.hibernate.entity.Department;
import com.boraji.tutorial.hibernate.entity.Employee;

public class MainApp {

   public static void main(String[] args) {

      // Insert some records into department and employee tables
      insertData();

      // Search department names
      List<Department> departments2 = searchData("Department");
      for (Department department : departments2) {
         System.out.println("Department Name:-" + department.getName());
         for (Employee employee : department.getEmployees()) {
            System.out.println("\tEmployee Name:- " + employee.getName());
         }
      }

      HibernateUtil.shutdown();
   }

   private static void insertData() {
      Session session = null;
      Transaction transaction = null;
      try {
         session = HibernateUtil.getSessionFactory().openSession();
         transaction = session.getTransaction();
         transaction.begin();

         Department department1 = new Department();
         department1.setName("IT Department");
         Department department2 = new Department();
         department2.setName("HR Department");

         Employee employee1 = new Employee();
         employee1.setName("Robin Edward");
         employee1.setDesignation("Manager");
         employee1.setDepartment(department1);

         Employee employee2 = new Employee();
         employee2.setName("Vivian Jackman");
         employee2.setDesignation("Senior HR Manager");
         employee2.setDepartment(department1);

         Employee employee3 = new Employee();
         employee3.setName("Eliza Edward");
         employee3.setDesignation("Software Engineer");
         employee3.setDepartment(department2);

         Employee employee4 = new Employee();
         employee4.setName("Nancy Newman");
         employee4.setDesignation("Senior Software Engineer");
         employee4.setDepartment(department2);

         department1.getEmployees().add(employee1);
         department1.getEmployees().add(employee2);

         department2.getEmployees().add(employee3);
         department2.getEmployees().add(employee4);

         session.persist(department1);
         session.persist(department2);

         transaction.commit();
      } catch (Exception e) {
         if (transaction != null) {
            transaction.rollback();
         }
         e.printStackTrace();
      } finally {
         if (session != null) {
            session.close();
         }
      }
   }

   private static List<Department> searchData(String text) {
      List<Department> departments = null;
      Session session = null;
      Transaction transaction = null;
      try {
         session = HibernateUtil.getSessionFactory().openSession();
         transaction = session.getTransaction();
         transaction.begin();

         FullTextSession fullTextSession = Search.getFullTextSession(session);
         fullTextSession.createIndexer().startAndWait();

         QueryBuilder qb = fullTextSession.getSearchFactory()
               .buildQueryBuilder().forEntity(Department.class).get();

         // Create lucene query
         // Set indexed field
         org.apache.lucene.search.Query lucenceQuery = 
               qb.keyword().onFields("name", "employees.name")
               .matching(text).createQuery();

         // Warp lucene query in org.hibernate.query.Query
         @SuppressWarnings("unchecked")
         Query<Department> query = fullTextSession.createFullTextQuery(lucenceQuery,
               Department.class);
         departments = query.getResultList();

         transaction.commit();
      } catch (Exception e) {
         e.printStackTrace();
         if (transaction != null) {
            transaction.rollback();
         }
      } finally {
         if (session != null) {
            session.close();
         }
      }
      return departments;

   }
}

After executing the MainApp, the output of your program will look like as follows.

hibernate5-search-example_01.png

And, your indexed entity in the specified path by hibernate.search.default.indexBase properties, will look like as follows.

hibernate5-search-example_02.png

Download Sources