Introduction to Hibernate Search

Bring the power of Lucene to your database-backed applications

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

In the sample application, when search results are displayed as a list on a Web screen, leaving the resume Word documents unfetched can significantly save memory. An applicant profile plus a summary of the Resume should provide enough information for users to make a selection. Once a user selects a row in the result list, the chosen Word document will then be fetched from the BLOB database column and returned through a separate request/response.

The seFindResumeProjectionsWithoutDatabaseAccess() method, shown in Listing 9, demonstrates how to employ projections in Hibernate Search to make a reporting query without database access. The method also shows how to get the relevance scores for Lucene search results. Keep in mind that the Resume objects in the results are not managed by the persistence context of JPA; therefore, they are detached JPA entities, with all the fields populated from the Lucene search results. Note that you lose a crucial benefit when employing these detached entities -- automatic dirty checking, a key factor that makes Hibernate backed applications perform great for data update operations.

Listing 9. Projections in a reporting query

@SuppressWarnings("unchecked")
   public Map<Resume, Float> seFindResumeProjectionsWithoutDatabaseAccess(
         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.setProjection(FullTextQuery.SCORE, "id",
               "summary",
               "applicant.id", "applicant.firstName",
               "applicant.lastName", "applicant.middleName",
               "applicant.emailAddress");

            Map<Resume, Float> resumes = new HashMap<Resume, Float>();

            for (Object[] result : (List<Object[]>) fq.getResultList()) {
               Resume resume = new Resume();
               User applicant = new User();
               resume.setApplicant(applicant);
               resume.setId((Long) result[1]);
               resume.setSummary((String) result[2]);
               /** WordDoc content is left blank. */
               applicant.setId((Long) result[3]);
               applicant.setFirstName((String) result[4]);
               applicant.setLastName((String) result[5]);
               applicant.setMiddleName((String) result[6]);
               applicant.setEmailAddress((String) result[7]);
               resumes.put(resume, (Float) result[0]);
            }
            return resumes;
          }
      });
      return (Map<Resume, Float>) results;
   }

Spring service layer

Service classes annotated by @service in Spring 2.5 are always a great place to specify the transaction characteristics of your Web applications. This is especially true when managing XA transactions across multiple data sources. Different DAO classes may collaborate to complete an XA transaction defined in a single method of a service class. In the ResumeManagerImpl class, shown in Listing 10, each business method is marked with the Spring @Transactional annotation, with propagation, readyOnly, and isolation as attributes. Don't confuse this with the EJB 3 @TransactionAttribute annotation, although both are designed for a similar goal. Hibernate Search by default encapsulates Lucene indexing processes with database transactions; hence, indexes are only updated when database operations are committed.

Listing 10. ResumeManagerImpl.java

package demo.hibernatesearch.service.impl;

@Service("resumeManager")
public class ResumeManagerImpl implements ResumeManager {

   protected final Log log = LogFactory.getLog(getClass());

   @Autowired
   private ResumeDao resumeDao;

   @Transactional(propagation = Propagation.REQUIRED, readOnly = false, isolation = Isolation.READ_COMMITTED)
   public void saveApplicant(User applicant) {
      resumeDao.saveApplicant(applicant);
   }

   //...
}

Spring 2.5 POJO test cases

Another advantage of the newly released Spring 2.5 annotations: they enable POJO test cases, as illustrated in Listing 11. By default, each test method in a test class runs under a single transaction context, and the transaction rolls back at the end of the method. In the sample application, this default behavior has been suppressed with a @Rollback(false) annotation on each test method, so that the results are committed to the database and Lucene indexes. Spring's JUnit/TestNG extensions allow you to run your test cases out of the container. All you need to do is to execute the Maven 2 built-in lifecycle phase -- mvn test.

Listing 11. POJO test cases

package demo.hibernatesearch.dao;

import static junit.framework.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class,
   TransactionalTestExecutionListener.class })
@ContextConfiguration(locations = "/WEB-INF/applicationContext*.xml")
@Transactional
public class ResumeDaoTest {

   @Autowired
   private ResumeDao resumeDao;

   //...

   @Test
   @Rollback(false)
   public void testSeFindResumesWithDocHandler() throws Exception {
      List<Resume> resumes = resumeDao.seFindResumesWithDocHandler(
         new GregorianCalendar(2006, 1, 1).getTime(),
         new GregorianCalendar().getTime(), "java", "web");

      assertTrue(resumes.size() == 5);
   }

   //...
}
1 2 3 4 5 6 7 Page 6
Page 6 of 7