Introduction to Hibernate Search

Bring the power of Lucene to your database-backed applications

1 2 3 4 5 6 7 Page 5
Page 5 of 7

The next two methods, shown in Listing 5, return the match count of the Resume entities for a keyword search on the summary property. A Lucene filter is used to specify a query date range for the lastUpdated field, and so is a Hibernate filter. I had to wrap the Lucene built-in RangeFilter, because Hibernate Search requires a no-arg constructor for all the Lucene filters declared in the entity classes through annotations. Like Hibernate filters, the Lucene filter must be turned on programmatically in the search method.

Listing 5. Find match count for keyword search

public int dbFindMatchCount(final Date beginDate, final Date endDate,
         final String... keywordsInSummary) {
      Object results = getJpaTemplate().execute(new JpaCallback() {
         public Object doInJpa(EntityManager em) throws PersistenceException {

            StringBuilder jpaql = new StringBuilder(
               "select count(resume) from Resume resume join resume.applicant where ");

            for (int i = 0; i < keywordsInSummary.length; i++) {
               jpaql.append("resume.summary like '%"
                  + keywordsInSummary[i] + "%' ");
               if (i < keywordsInSummary.length - 1)
                  jpaql.append(" and ");
            }

            Session session = ((Session) em.getDelegate());

            session.enableFilter("rangeFilter").setParameter("beginDate",
               beginDate).setParameter("endDate", endDate); // Hibernate Filter

            return ((Long) getJpaTemplate().find(jpaql.toString())
               .iterator().next()).intValue();
         }
      });
      return (Integer) results;
   }

   public int seFindMatchCount(final Date beginDate, final Date endDate,
         final String... keywordsInSummary) {
      Object results = getJpaTemplate().execute(new JpaCallback() {
         public Object doInJpa(EntityManager em) throws PersistenceException {

            FullTextEntityManager fullTextEntityManager = createFullTextEntityManager(em);

            BooleanQuery bq = new BooleanQuery();

            for (String q : keywordsInSummary) {
               TermQuery tq = new TermQuery(new Term("summary", q));
               bq.add(new BooleanClause(tq, BooleanClause.Occur.MUST));
            }

            FullTextQuery fq = fullTextEntityManager.createFullTextQuery(
               bq, Resume.class);

            FullTextFilter ff = fq.enableFullTextFilter("rangeFilter");
            ff.setParameter("fieldName", "lastUpdated");
            ff.setParameter("lowerTerm", DateTools.dateToString(beginDate,
               DateTools.Resolution.DAY));
            ff.setParameter("upperTerm", DateTools.dateToString(endDate,
               DateTools.Resolution.DAY));
            ff.setParameter("includeLower", true);
            ff.setParameter("includeUpper", true);

            return fq.getResultSize();
         }
      });
      return (Integer) results;
   }

If you expect a large set of results returned from a query, you'll need to paginate those results -- that is, break them up over several Web pages. Pagination makes your Web pages look nice when you put the complete results on the screen, and displays Google-like search results that your users can page through. You might end up with an out-of-memory error from your JVM or out-of-cursor error from your database without pagination enabled. The two methods in Listing 6, dbFindResumesWithPagination() and seFindResumesWithPagination(), show you how easy it is to enable pagination in JPA and Hibernate Search.

Listing 6. Pagination in JPA and Hibernate Search

@SuppressWarnings("unchecked")
   public List<Resume> dbFindResumesWithPagination(final int fetchCursor,
         final int fetchSize, final Date beginDate, final Date endDate,
         final String... keywordsInSummary) {
      Object results = getJpaTemplate().execute(new JpaCallback() {
         public Object doInJpa(EntityManager em) throws PersistenceException {

            StringBuilder jpaql = new StringBuilder(
               "from Resume resume join fetch resume.applicant where ");

            for (int i = 0; i < keywordsInSummary.length; i++) {
               jpaql.append("resume.summary like '%"
                  + keywordsInSummary[i] + "%' ");
               if (i < keywordsInSummary.length - 1)
                  jpaql.append(" and ");
            }

            Session session = ((Session) em.getDelegate());

            session.enableFilter("rangeFilter").setParameter("beginDate",
               beginDate).setParameter("endDate", endDate);

            Query query = em.createQuery(jpaql.toString());

            query.setFirstResult(fetchCursor);
            query.setMaxResults(fetchSize);

            return (List<Resume>) query.getResultList();
         }
      });
      return (List<Resume>) results;
   }

   @SuppressWarnings("unchecked")
   public List<Resume> seFindResumesWithPagination(final int fetchCursor,
         final int fetchSize, final Date beginDate, final Date endDate,
         final String... keywordsInSummary) {
      Object results = getJpaTemplate().execute(new JpaCallback() {
         public Object doInJpa(EntityManager em) throws PersistenceException {

            FullTextEntityManager fullTextEntityManager = createFullTextEntityManager(em);

            BooleanQuery bq = new BooleanQuery();

            for (String q : keywordsInSummary) {
               TermQuery tq = new TermQuery(new Term("summary", q));
               bq.add(new BooleanClause(tq, BooleanClause.Occur.MUST));
            }

            FullTextQuery fq = fullTextEntityManager.createFullTextQuery(
               bq, Resume.class);

            FullTextFilter ff = fq.enableFullTextFilter("rangeFilter");
            ff.setParameter("fieldName", "lastUpdated");
            ff.setParameter("lowerTerm", DateTools.dateToString(beginDate,
               DateTools.Resolution.DAY));
            ff.setParameter("upperTerm", DateTools.dateToString(endDate,
               DateTools.Resolution.DAY));
            ff.setParameter("includeLower", true);
            ff.setParameter("includeUpper", true);

            fq.setFirstResult(fetchCursor);
            fq.setMaxResults(fetchSize);

            return (List<Resume>) fq.getResultList();
         }
      });
      return (List<Resume>) results;
   }

More importantly, the seFindResumesWithDocHandler() method in Listing 7 searches technical keywords inside the Word-formatted resume files. As you saw earlier, a custom FieldBridge WordDocHandlerBridge is declared through the @FieldBridge annotation in the Resume entity class. The custom FieldBridge shown in Listing 8 makes use of Apache POI to extract all the text out of the Microsoft Word documents for indexing purposes. Searching is conducted over this extracted text.

Listing 7. Searching through Word-formatted resume files

@SuppressWarnings("unchecked")
   public List<Resume> seFindResumesWithDocHandler(final Date beginDate,
         final Date endDate, final String... keywordsInWordDoc) {
      Object results = getJpaTemplate().execute(new JpaCallback() {
      public Object doInJpa(EntityManager em) throws PersistenceException {
         FullTextEntityManager fullTextEntityManager = createFullTextEntityManager(em);
         BooleanQuery bq = new BooleanQuery();
         for (String q : keywordsInWordDoc) {
            TermQuery tq = new TermQuery(new Term("resume", q)); //Word Doc
            bq.add(new BooleanClause(tq, BooleanClause.Occur.MUST));
         }

         FullTextQuery fq = fullTextEntityManager.createFullTextQuery(
            bq, Resume.class);
         FullTextFilter ff = fq.enableFullTextFilter("rangeFilter");
         ff.setParameter("fieldName", "lastUpdated");
         ff.setParameter("lowerTerm", DateTools.dateToString(beginDate,
         DateTools.Resolution.DAY));
         ff.setParameter("upperTerm", DateTools.dateToString(endDate,
            DateTools.Resolution.DAY));
         ff.setParameter("includeLower", true);
         ff.setParameter("includeUpper", true);
         return (List<Resume>) fq.getResultList();
         }
      });
      return (List<Resume>) results;
   }

Listing 8. A custom FieldBridge example

package demo.hibernatesearch.dao.hibernate.utils;

public class WordDocHandlerBridge implements StringBridge {

   public String objectToString(Object arg0) {

      StringBuilder _result = new StringBuilder();
      try {
         ByteArrayInputStream bais = new ByteArrayInputStream((byte[]) arg0);
         org.apache.poi.hwpf.HWPFDocument doc = new org.apache.poi.hwpf.HWPFDocument(
            bais);
         Range range = doc.getRange();
         int np = range.numParagraphs();
         for (int i = 0; i < np; i++) {
            _result.append(range.getParagraph(i).text());
            _result.append(" ");
         }
      } catch (IOException ex) {
         ex.printStackTrace();
      }
      return _result.toString();
   }

}

A reporting query returns a subset of the entity beans' properties rather than the full entity objects. This is a cost-effective query approach in that it reduces the overhead of unnecessary data fetching and table joins. Both JPA and Hibernate Search provide projection facilities to support reporting queries. Applying projections properly in Hibernate Search may avoid unnecessary database access, reduce memory usage, and improve the performance of an application.

1 2 3 4 5 6 7 Page 5
Page 5 of 7