Projection

In the first couple of chapters, our example application fetched all the matching entities in one big database call. We introduced pagination in Chapter 3, Performing Queries, to at least limit the database calls to a fixed number of rows. However, since we're already searching data in a Lucene index to begin with, is it really necessary to go to the database at all?

Hibernate Search offers projections as a technique for eliminating, or at least reducing, database access. A projection-based search returns only specific fields pulled from Lucene, rather than returning a full entity object from the database. You can then go to the database and fetch full objects if necessary, but the fields available in Lucene may be sufficient by themselves.

This chapter's version of the VAPORware Marketplace application modifies the search results page so that it now uses a projection-based query. The previous versions of the page received App entities all at once, and hid each app's pop-up window until its Full Detail button was clicked. Now, the page receives only enough fields to build the summary view. Each Full Detail button triggers an AJAX call for that app. Only then is the database called, and only to fetch data for that one app.

Note

Exhaustive descriptions of how to make AJAX calls from JavaScript and how to write RESTful web services to respond to those calls, ventures pretty far beyond the scope of this Hibernate Search book.

That being said, all of the JavaScript is contained on the search results JSP, within the showAppDetails function. All of the corresponding server-side Java code resides in the com.packtpub.hibernatesearch.rest package, and is heavily commented. There are endless online primers and tutorials for writing RESTful services, and the documentation for the particular framework used here is at http://jersey.java.net/nonav/documentation/latest.

Making a query projection-based

To change FullTextQuery to be projection-based, invoke the setProjection method on that object. Our search servlet class now contains the following line:

...
hibernateQuery.setProjection("id", "name", "description", "image");
...

The method accepts the names of one or more fields to pull from the Lucene indexes associated with this query.

Converting projection results to an object form

If we stopped right here, then the query object's list() method would no longer return a list of App objects! By default, projection-based queries return a list of object arrays (that is, Object[]) instead of entity objects. These arrays are often referred to as tuples.

The elements in each tuple contain values for the projected fields, in the order they were declared. For example, here listItem[0] would contain the value of a result's ID, field.listItem[1] would contain the name, value.listItem[2] would contain the description, and so on.

In some cases, it's easy enough to work with the tuple as-is. However, you can automatically convert tuples into an object form by attaching a Hibernate ORM result transformer to the query. Doing so changes the query's return type yet again, from List<Object[]> to a list of the desired object type:

...
hibernateQuery.setResultTransformer(
   newAliasToBeanResultTransformer(App.class) );
...

You can create your own custom transformer class inheriting from ResultTransformer, implementing whatever complex logic you need. However, in most cases, the subclasses provided by Hibernate ORM out of the box are more than enough.

Here, we are using the AliasToBeanResultTransformer subclass, and initializing it with our App entity class. This matches up the projected fields with the entity class properties having the same names, and sets each property with the corresponding field value.

Only a subset of properties of App are available. It is okay to leave the other properties uninitialized, since the search results JSP doesn't need them when building its summary list. Also, the resulting App objects won't actually be attached to a Hibernate session. However, we've been detaching our results before sending them to the JSP anyway.

Making Lucene fields available for projection

By default, Lucene indexes are optimized with the assumption that they will not be used for projection-based queries. Therefore, projection requires that you make some small mapping changes and bear a couple of caveats in mind.

First and foremost, the field data must be stored by Lucene in a manner that can be easily retrieved. The normal indexing process optimizes data for complex queries, not for retrieval in its original form. To store a field's value in a form that can be restored by a projection, you add a store element to the @Field annotation:

...
@Field(store=Store.COMPRESS)
private String description;
...

This element takes an enum with three possible values:

  • Store.NO is the default. It causes the field to be indexed for searching, but not retrievable in its original form through projection.
  • Store.YES causes the field to be included as-is in the Lucene index. This increases the size of the index, but makes projections possible.
  • Store.COMPRESS is an attempt at compromise. It also stores the field as-is, but applies compression to reduce the overall index size. Be aware that this is more processor-intensive, and is not available for a field that also uses the @NumericField annotation.

Secondly, a field must use a bi-directional field bridge. All of the default bridges built-in to Hibernate Search already support this. However, if you create your own custom bridge type (see Chapter 4, Advanced Mapping), it must be based on TwoWayStringBridge or TwoWayFieldBridge.

Last but not least, projection is only effective for basic properties on the entity class itself. It is not meant for fetching associated entities or embedded objects. If you do try to reference an association, then you will only get one instance rather than the full collection that you were probably expecting.

Tip

If you need to work with the associated or embedded objects, then you might take the approach used by our example application. Lucene projection fetches the basic properties for all search results, including the entity object's primary key. When we later need to work with an entity object's associations, we use that primary key to retrieve only the necessary rows through a database call.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset