Hibernate Search is a full-text search based on Lucene which integrates near transparently with Hibernate ORM. Creating Lucene index on entities is really simple when you can use annotations in your code. First you need to mark your class as @Indexed, next assign @DocumentId to the entity primary key, last thing would be mark instance fields as indexable with @Field annotation - see the example below.
@Entity
@Indexed
public class Advertisement implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@DocumentId
private Integer id;
@Field(index=Index.TOKENIZED, store=Store.NO)
private String title;
private String body;
@Field(index=Index.TOKENIZED, store=Store.YES)
public String getRawBody() {
Pattern pattern = Pattern.compile("<[^>]*>");
Matcher matcher = pattern.matcher(this.body);
return matcher.replaceAll("");
}
//getters / setters
}
You need to add some properties to Hibernate SessionFactory - e.g. in Spring Framework it would be something like this:
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="hibernateProperties">
<props>
<prop key="hibernate.search.default.directory_provider">org.hibernate.search.store.FSDirectoryProvider</prop>
<prop key="hibernate.search.default.indexBase">/usr/home/ghost/workspace/AdvertisementSystem/target/search</prop>
</props>
</property>
<bean>
All of above is well described in Hibernate Search documentation, but some troubles could be with Wicket integration, in which we use DataView to show paginated data. This view needs to get DataProvider object which have to implements iterator() and size() methods, and it's quite obvious when using databases, but not when using Hibernate Search - why? Because it returns number of all results when querying even for part of them, so why you would ask search twice - my solution for this problem is:
class SearchDataProvider implements IDataProvider {
private String query;
private int resultsPerPage;
private SearchDAO.SearchResults searchedAdverts;
private Integer resultsCount;
public SearchDataProvider(String query, int resultsPerPage) {
this.query = query;
this.resultsPerPage = resultsPerPage;
}
public Iterator iterator(int first, int count) {
if(searchedAdverts == null) {
searchedAdverts = searchDAO.getSearchedAdverts(this.query, first, count);
}
return searchedAdverts.getResults().iterator();
}
public IModel model(Object object) {
return new Model((Advertisement) object);
}
public int size() {
if(resultsCount == null) {
searchedAdverts = searchDAO.getSearchedAdverts(this.query, 0, this.resultsPerPage);
resultsCount = searchedAdverts.getCount();
}
return resultsCount;
}
public void detach() {
searchedAdverts = null;
}
}
In DAO:
public SearchResults getSearchedAdverts(final String queryString, int first, int count) {
Session session = getSession(true);
FullTextSession fullTextSession = Search.createFullTextSession(session);
SearchResults results = null;
Transaction tx = fullTextSession.beginTransaction();
MultiFieldQueryParser parser = new MultiFieldQueryParser( new String[]{"title", "rawBody"}, new StandardAnalyzer());
Query query;
try {
query = parser.parse(queryString);
FullTextQuery hibQuery = fullTextSession.createFullTextQuery(query, Advertisement.class)
.setFirstResult(first)
.setMaxResults(count);
results = new SearchResults(hibQuery.list(), hibQuery.getResultSize());
} catch (ParseException e) {
e.printStackTrace();
} finally {
tx.commit();
session.close();
}
}
In my case SearchResults is private class which wraps list of results and ALL result counter, there is some problem in Spring with using HibernateTemplate() so I manually get new session. In DataProvider size() is asked only once when new DataView is instanced, and it takes first 20 results, store them as detached object, and set up number of results for a later use (when processing to next pages).