Friday 15 February 2008

Hibernate Search and Spring 2.5

Hi there,

After doing my first blog I have been excited to post something new.  So this blog entry is about Hibernate Search and integrating with Spring.  Hibernate Search (www.search.hibernate.org)is a cool technology that uses Apache Lucene under the hood to allow free text search across your domain model.  

I have been playing with Hibernate Search for some months now and I thought I'd share my code example with you.  By the way I am using Hibernate annotations, I haven't looked at doing it via XML.

Book.java

@Indexed
@Table(name="BOOK")
@Analyzer(impl=com.amin.spring.app.util.EnglishAnaylzer.class)
public class Book implements Serializable {
@Id @GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="BOOK_ID")
@Type(type="java.lang.Long")
@DocumentId
private Long id;

@Column(name="BOOK_TITLE")
@Field(index=Index.TOKENIZED, store=Store.YES)
private String title;

@Column(name="BOOK_DESCRPTION")
@Field(index=Index.TOKENIZED, store=Store.YES)
private String description;

@Column(name="BOOK_CATEGORY")
@Field(index=Index.TOKENIZED, store=Store.YES)
@Enumerated(EnumType.STRING)
private CategoryEnum category;

//getters and setters
}

BookRepositoryImpl.java

@Repository
public class BookRepositoryImpl implements BookRepository {
   private HibernateTemplate hibernateTemplate;
   
   private static final String[] FIELD_NAMES = new String[]{"title", "description", "category"};

   @Autowired
   public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
     this.hibernateTemplate = hibernateTemplate;
   }
   
   public List search ( final String searchTerm) {
    List searchResult = (List) hibernateTemplate.execute( 
new HibernateCallback() {
public Object doInHibernate(org.hibernate.Session session) {
FullTextSession fullTextSession = Search.createFullTextSession(session);
QueryParser parser = new MultiFieldQueryParser( FIELD_NAMES, new StandardAnalyzer());
org.apache.lucene.search.Query query = parser.parse(searchString);
org.hibernate.Query hibernateQuery = fullTextSession.createFullTextQuery(query, Book.class);
List results = hibernateQuery.list();
return results;
}
}, true);
return searchResults
}
   }

Now I need to configure the hibernate session factory in Spring to enable Hibernate Search. The following provides a snippet what you will need to add to the session factory bean definition in the spring context file. 

spring-context.xml

<property name="eventListeners">
<map>
<entry>
<key>
<value>post-update</value>
<bean class="org.hibernate.search.event.FullTextIndexEventListener" />
</property>

Add the same FullTextIndexEventListener for post-insert and post-delete

This will enable automatic indexing of your domain object upon insert, update or delete.  You will also need to specify the location of where  the indexes should be created.  This can be added to the hibernateProperties section of the sessionFactory setup.  There are additional properties that you can add, for example you can enable asynchronous indexing. Please refer to the Hibernate Search documentation on the full properties that can be used.

Once you have got this example working you can use Luke (an Swing application located at www.getopt.org/like/) that allows you to view your indexes and also perform searches.  I found that using Luke was very handy when needing to view my indexes and build Lucene queries.

Now finally a testcase.  The test case is very similar to the one I posted last time but I've added a transactional context. 


BookRepositoryTest.java

Set up a testcase that saves a book to the database.  [One thing to note that the event listeners will only execute after the completion of a Hibernate transaction so Spring's transactional tests won't work unless the object is saved to the database.  You could create an object commit to the database, perform a search, do some assertions and then delete the object from the database.]

@Test
@Rollback(false)
public void testCanSearchWithResult() {
List books = bookRepository.search("xyz");
assertNotNull(books);
}


In the above example the search is performed on all fields (so the query looks something like 'title:xyz description:xyz' and so on), if however you wanted to search on a particular field (say 'title') then you could pass the string "title:XYZ" and search would only be done on only the title field.  There are various ways to construct a search query in Lucene and I recommend Lucene In Action as a good resource for getting to know more about Lucene.

Hibernate Search documentations provides good information about embedded objects and how to set up the indexing processes on these objects.  I also found the Hibernate Search forum very helpful when trying to find solutions to problems.

At the end of the day you can still get to the Lucene api from Hibernate Search.  With Hibernate Search you get the added advantage that when you perform a query you get domain objects back without having to do any conversions from Lucene documents.

That's it.  Hope this provides enough information to get you started.   As I mentioned last time your comments are welcomed.

6 comments:

aminmc said...

Sorry folks Luke is located at www.getopt.org/luke/...not www.getopt.org/like!

Simona said...

Sorry Amin but your post is very hard to read, you forgot < /key>< /entry>< /map>
for the spring-context.xml
and I really didn't understand almost anything...

Simona said...

Hey Amin, are you sure about this code:?
public List search ( final String searchTerm) {
List searchResult = (List) hibernateTemplate.execute(
new HibernateCallback() {
public Object doInHibernate(org.hibernate.Session session) {
FullTextSession fullTextSession = Search.createFullTextSession(session);
QueryParser parser = new MultiFieldQueryParser( FIELD_NAMES, new StandardAnalyzer());
org.apache.lucene.search.Query query = parser.parse(searchString);
org.hibernate.Query hibernateQuery = fullTextSession.createFullTextQuery(query, Book.class);
List results = hibernateQuery.list();
return results;
}
}, true);
return searchResults
}
thanks for the effort by the way...

Luis-José said...

Hi Amin, just to ask you how about composite Key? I known we have to make a Bridge to index a composite key but I did it and when I try my application I get a real strange problem like Query.List().Size() is different from Query.getResultSize().., do you known something about this bug?
Luis josé

aminmc said...

Hi Simona

Yes, looking back the formatting isn't great. I'd be happy to explain more or even provide you with a sample POC of Hibernate Search.

Thanks

Unknown said...

Hi dude.

the parse method would throws a ParseException. how and where would you catch it? in DAO layer? or service layer?