Hibernate Search Tutorial - (Hibernate Search API)-Hibernate 4 on Baby Steps


Welcome to Hibernate Search Tutorial. In this hibernate search tutorial we will discuss about Hibernate 4 (Hibernate Search API). Actually Hibernate 4 provide some extra features related to searching than Hibernate 3. The following chapter will guide you through the initial steps required to integrate Hibernate Search into an existing Hibernate enabled application. In case you are new to Hibernate so we recommend you start here.

Hibernate 4 = Hibernate 3 + Hibernate Search API(or Lucene API)

As see above in this tutorial we will discuss only about the Hibernate Search API. All hibernate functionality we already discuss in the Hibernate 3 on Baby Step tutorial.

System Requirement for Hibernate Search API-
1. Java Runtime: A JDK or JRE version 6 or greater.
You can download a Java Runtime for Windows/Linux/Solaris download here.

2. Hibernate Search: hibernate-search-4.2.0.Final.jar and all runtime dependencies. You can get the jar artifacts either from the dist/lib directory of the Hibernate Search distribution.

3. Hibernate Core: These instructions have been tested against Hibernate 4.1. You will need hibernate-core-4.1.9.Final.jar and its download here.

4. JPA 2: Even though Hibernate Search can be used without JPA annotations the following instructions will use them for basic entity configuration (@Entity, @Id, @OneToMany,...). This part of the configuration could also be expressed in xml or code. Hibernate Search, however, has also its own set of annotations (@Indexed, @DocumentId, @Field,...) for which there exists so far no XML based alternative.

Configuration for Hibernate Search API: Once you have downloaded and added all required libs to your application you have to add a couple of properties to your hibernate configuration file (hibernate.cfg.xml or hibernate.properties)
...
<property name="hibernate.search.default.directory_provider"
value="filesystem"/>
<property name="hibernate.search.default.indexBase"
value="D:\dojupload\index>
...
First you have to tell Hibernate Search which DirectoryProvider to use. This can be achieved by setting the hibernate.search.default.directory_provider property. Apache Lucene has the notion of a Directory to store the index files. Hibernate Search handles the initialization and configuration of a Lucene Directory instance via a DirectoryProvider. In this tutorial we will use a a directory provider storing the index in the file system. This will give us the ability to physically inspect the Lucene indexes created by Hibernate Search.

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
          "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
          "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<!-- Generated by MyEclipse Hibernate Tools.                   -->
<hibernate-configuration>

    <session-factory>
        <!-- Database connection settings -->
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/test</property>
        <property name="connection.username">root</property>
        <property name="connection.password">root</property>
 
        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size">1</property>
 
        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
 
        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>
        <property name="hbm2ddl.auto">update</property>
        <property name="hibernate.search.default.directory_provider">filesystem</property>
        <property name="hibernate.search.default.indexBase">D:\dojupload\index</property>
        <!-- Mapping files -->
       <mapping class="com.dineshonjava.h4java.Book"/>
    
    </session-factory>

</hibernate-configuration>
Now next to the directory provider you also have to specify the default base directory for all indexes via hibernate.search.default.indexBase. Lets assume that your application contains the Hibernate managed classes com.dineshonjava.h4java.Book and you want to add free text search capabilities to your application in order to search the books contained in your database.

Example entities Book before adding Hibernate Search specific annotations:
package com.dineshonjava.h4java;
...
@Entity
public class Book {
@Id
@GeneratedValue
private Integer id;

@Column
private String title;
@Column
private String subtitle;
@Column
private Date publicationDate;

public Book() {}
public Integer getId() {
  return id;
 }

 public void setId(Integer id) {
  this.id = id;
 }

 public String getTitle() {
  return title;
 }

 public void setTitle(String title) {
  this.title = title;
 }

 public String getSubtitle() {
  return subtitle;
 }

 public void setSubtitle(String subtitle) {
  this.subtitle = subtitle;
 }

 public Date getPublicationDate() {
  return publicationDate;
 }

 public void setPublicationDate(Date publicationDate) {
  this.publicationDate = publicationDate;
 }
}
@Indexed-
To achieve this you have to add a few annotations to the Book class. The first annotation @Indexed marks Book as indexable. By design Hibernate Search needs to store an untokenized id in the index to ensure index unicity for a given entity. 

@DocumentId-
@DocumentId marks the property to use for this purpose and is in most cases the same as the database primary key. The @DocumentId annotation is optional in the case where an @Id annotation exists.

@Field-
 Next you have to mark the fields you want to make searchable. Let's start with title and subtitle and annotate both with @Field. The parameter index=Index.YES will ensure that the text will be indexed, while analyze=Analyze.YES ensures that the text will be analyzed using the default Lucene analyzer. Usually, analyzing means chunking a sentence into individual words and potentially excluding common words like 'a' or 'the'. The third parameter we specify within @Field, store=Store.NO, ensures that the actual
data will not be stored in the index. Whether this data is stored in the index or not has nothing to do with the ability to search for it. From Lucene's perspective it is not necessary to keep the data once the index is created.

Note that index=Index.YES, analyze=Analyze.YES and store=Store.NO are the default values for these parameters and could be omitted.

@DateBridge-  
Another annotation we have not yet discussed is @DateBridge. This annotation is one of the built-in field bridges in Hibernate Search. The Lucene index is purely string based. For this reason Hibernate Search must convert the data types of the indexed fields to strings and vice versa. A range of predefined bridges are provided, including the DateBridge which will convert a java.util.Date into a String with the specified resolution.


Example Book entities after adding Hibernate Search annotations:
package com.dineshonjava.h4java;

import java.util.Date;

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

import org.apache.solr.analysis.LowerCaseFilterFactory;
import org.apache.solr.analysis.SnowballPorterFilterFactory;
import org.apache.solr.analysis.StandardTokenizerFactory;
import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.AnalyzerDef;
import org.hibernate.search.annotations.DateBridge;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Parameter;
import org.hibernate.search.annotations.Resolution;
import org.hibernate.search.annotations.Store;
import org.hibernate.search.annotations.TokenFilterDef;
import org.hibernate.search.annotations.TokenizerDef;


@Entity
@Indexed
public class Book {
 @Id
 @GeneratedValue
 private Integer id;
 @Field(index = Index.YES, analyze = Analyze.YES, store = Store.NO)
 private String title;
 @Field(index = Index.YES, analyze = Analyze.YES, store = Store.NO)
 private String subtitle;
 @Field(index = Index.YES, analyze = Analyze.NO, store = Store.YES)
 @DateBridge(resolution = Resolution.DAY)
 private Date publicationDate;

 public Book() {
 }

 public Integer getId() {
  return id;
 }

 public void setId(Integer id) {
  this.id = id;
 }

 public String getTitle() {
  return title;
 }

 public void setTitle(String title) {
  this.title = title;
 }

 public String getSubtitle() {
  return subtitle;
 }

 public void setSubtitle(String subtitle) {
  this.subtitle = subtitle;
 }

 public Date getPublicationDate() {
  return publicationDate;
 }

 public void setPublicationDate(Date publicationDate) {
  this.publicationDate = publicationDate;
 }

 @Override
 public String toString() {
  return "Book [id=" + id + ", title=" + title + ", subtitle=" + subtitle
    + ", publicationDate=" + publicationDate + "]";
 }

}

Indexing-
Hibernate Search will transparently index every entity persisted, updated or removed through Hibernate Core. However, you have to create an initial Lucene index for the data already present in your database. Once you have added the above properties and annotations it is time to trigger an initial batch index of your books.
FullTextSession fullTextSession = Search.getFullTextSession(session);
fullTextSession.createIndexer().startAndWait();
After executing the above code, you should be able to see a Lucene index under "D:\dojupload\index\com.dineshonjava.h4java.Book"

Searching-
Now it is time to execute a first search. The general approach is to create a Lucene query and then wrap this query into a org.hibernate.Query in order to get all the functionality one is used to from the Hibernate API. The following code will prepare a query against the indexed fields, execute it and return a list of Books.
package com.dineshonjava.h4java;

import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.hibernate.Transaction;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.query.dsl.QueryBuilder;


public class App {
public static void main(String[] args) {
 org.hibernate.Session session=HibernateSessionFactory.getSession();
 try{
  Book book=new Book();
  book.setPublicationDate(new Date());
  book.setSubtitle("jsp");
  book.setTitle("tech");
  //session.saveOrUpdate(book);
  FullTextSession fullTextSession = org.hibernate.search.Search.getFullTextSession(session);
  Transaction tx = fullTextSession.beginTransaction();
  QueryBuilder qb = fullTextSession.getSearchFactory().buildQueryBuilder().forEntity( Book.class ).get();
  org.apache.lucene.search.Query query = qb.keyword().onFields("title", "subtitle").matching("jsp").createQuery();
    // wrap Lucene query in a org.hibernate.Query
  org.hibernate.Query hibQuery = fullTextSession.createFullTextQuery(query, Book.class);
    // execute search
  List result = hibQuery.list();
  Iterator<Book> it = result.iterator();
  while (it.hasNext()) {
   Book book1 = (Book) it.next();
   System.out.println(book1);
  }
  tx.commit();
  
 }
 catch (Exception e) {
  // TODO: handle exception
 }finally{
  session.close();
  }
 
  }
}
HibernateSessionFactory.java
package com.dineshonjava.h4java;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.cfg.Configuration;

/**
 * Configures and provides access to Hibernate sessions, tied to the
 * current thread of execution.  Follows the Thread Local Session
 * pattern, see {@link http://hibernate.org/42.html }.
 */
public class HibernateSessionFactory {

    /** 
     * Location of hibernate.cfg.xml file.
     * Location should be on the classpath as Hibernate uses  
     * #resourceAsStream style lookup for its configuration file. 
     * The default classpath location of the hibernate config file is 
     * in the default package. Use #setConfigFile() to update 
     * the location of the configuration file for the current session.   
     */
    private static String CONFIG_FILE_LOCATION = "/hibernate.cfg.xml";
 private static final ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
    private  static Configuration configuration = new Configuration();    
    private static org.hibernate.SessionFactory sessionFactory;
    private static String configFile = CONFIG_FILE_LOCATION;

 static {
     try {
   configuration.configure(configFile);
   sessionFactory = configuration.buildSessionFactory();
  } catch (Exception e) {
   System.err
     .println("%%%% Error Creating SessionFactory %%%%");
   e.printStackTrace();
  }
    }
    private HibernateSessionFactory() {
    }
 
 /**
     * Returns the ThreadLocal Session instance.  Lazy initialize
     * the <code>SessionFactory</code> if needed.
     *
     *  @return Session
     *  @throws HibernateException
     */
    public static Session getSession() throws HibernateException {
        Session session = (Session) threadLocal.get();

  if (session == null || !session.isOpen()) {
   if (sessionFactory == null) {
    rebuildSessionFactory();
   }
   session = (sessionFactory != null) ? sessionFactory.openSession()
     : null;
   threadLocal.set(session);
  }

        return session;
    }

 /**
     *  Rebuild hibernate session factory
     *
     */
 public static void rebuildSessionFactory() {
  try {
   configuration.configure(configFile);
   sessionFactory = configuration.buildSessionFactory();
  } catch (Exception e) {
   System.err
     .println("%%%% Error Creating SessionFactory %%%%");
   e.printStackTrace();
  }
 }

 /**
     *  Close the single hibernate session instance.
     *
     *  @throws HibernateException
     */
    public static void closeSession() throws HibernateException {
        Session session = (Session) threadLocal.get();
        threadLocal.set(null);

        if (session != null) {
            session.close();
        }
    }

 /**
     *  return session factory
     *
     */
 public static org.hibernate.SessionFactory getSessionFactory() {
  return sessionFactory;
 }

 /**
     *  return session factory
     *
     * session factory will be rebuilded in the next call
     */
 public static void setConfigFile(String configFile) {
  HibernateSessionFactory.configFile = configFile;
  sessionFactory = null;
 }

 /**
     *  return hibernate configuration
     *
     */
 public static Configuration getConfiguration() {
  return configuration;
 }

}

After running the above class file App.java we get data on the database test and table name book.

 And now checking the indexing file in the "D:\dojupload\index\com.dineshonjava.h4java.Book".


Application Directory Structure and Jars files.


Output on Console--
INFO: HHH000037: Columns: [id, title, subtitle, publicationdate]
Jul 10, 2013 11:51:30 PM org.hibernate.tool.hbm2ddl.TableMetadata
INFO: HHH000108: Foreign keys: []
Jul 10, 2013 11:51:30 PM org.hibernate.tool.hbm2ddl.TableMetadata

INFO: HHH000126: Indexes: [primary]
Jul 10, 2013 11:51:30 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000232: Schema update complete

Hibernate: select this_.id as id0_0_, this_.publicationDate as publicat2_0_0_, this_.subtitle as subtitle0_0_, this_.title as title0_0_ from Book this_ where (this_.id in (?))
Book [id=1, title=tech, subtitle=jsp, publicationDate=2013-07-10 21:35:33.0]


Contents for Hibernate Search-


Download Source Code + Libs
Hibernate4Demo.zip





References



3 comments:

  1. Hi,
    i would like to store indexes in NosqlDB(MongoDB), Can you please share the required configuration.

    My mail id:sravankumar4rmhyd@gmail.com

    ReplyDelete
  2. hi sir...is thr any tutorial for hibernate mappings?here only hibernate search example is available.pls guide me

    ReplyDelete
    Replies
    1. http://www.dineshonjava.com/p/hibernate-one-to-one-mapping-tutorial.html

      http://www.dineshonjava.com/p/hibernate-one-to-many-mapping-tutorial.html

      http://www.dineshonjava.com/p/hibernate-many-to-one-mapping-tutorial.html

      http://www.dineshonjava.com/p/hibernate-many-to-many-mapping-tutorial.html

      Delete