How to use Facets to categorize your FullTextQuery results


Take your skills to the next level!

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.


Facetting is another interesting feature provided by Hibernate Search. It allows you to group your FullTextQuery results in categories. You often see this in online shops which present the search results in different product categories or on websites which categorize their articles by date.

This is the 3rd post in my series about Hibernate Search, and it requires some basic knowledge about the full-text search framework. You should have a look at the 2 previous posts if you have no experience with Hibernate Search:

Prepare your entities for a faceted search

Before you can define a faceted search query, you need to prepare your search index for it. You can do that by annotating the entity attribute you want to use for faceting with a @Facet annotation. I did that with the userName attribute of my Tweet entity.

@Indexed
@Entity
public class Tweet {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = "id", updatable = false, nullable = false)
	private Long id;

	@Column
	@Field(name = "postedAt", analyze = Analyze.NO)
	@DateBridge(resolution = Resolution.MONTH)
	private Date postedAt;

	@Column
	@Field(analyze = Analyze.NO)
	@Facet
	private String userName;

	@Column
	private String message;

	@Column
	private String url;

	@Version
	private Long version;
  
	...
}

As you can see in the code snippet, I also annotated the userName attribute with @Field(analyze = Analyze.NO)). That is also required if you want to use an entity attribute for faceting. It’s obvious that you need to index an attribute that you want to use in a full-text query. But it’s not that obvious that you’re not allowed to apply any Analyzer to that field. If you want to analyze an attribute to use advanced full-text search features, like the ones I showed you in the previous post, you need to index it twice. You can simply do that by adding 2 @Field annotations to the entity attribute. One with the Analyzer and another one without it. You can then use the analyzed index for your full-text query and the not-analyzed index for the faceting.

Get faceted results

After you annotated an entity attribute with @Facet, you can use it in a FacetingRequest.

In the first step, you need to create a FullTextQuery for which you to get faceted results. I explained this part in more details in the first post of this series. The FullTextQuery in this example selects all Tweet entities from the Lucene index.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

FullTextEntityManager fullTextEm = Search.getFullTextEntityManager(em);
QueryBuilder tweetQb = fullTextEm.getSearchFactory().buildQueryBuilder().forEntity(Tweet.class).get();
Query tweetQuery = tweetQb.all().createQuery();
FullTextQuery fullTextQuery = fullTextEm.createFullTextQuery(tweetQuery, Tweet.class);

You can then use this query to with a FacetingRequest to get the different facets and their number of elements.

FacetingRequest postedAtFR = tweetQb.facet()
      .name("userNameFR")
      .onField(Tweet_.userName.getName())
      .discrete()
      .orderedBy(FacetSortOrder.COUNT_DESC)
      .includeZeroCounts(false)
      .maxFacetCount(3)
      .createFacetingRequest();	

FacetManager facetMgr = fullTextQuery.getFacetManager();
facetMgr.enableFaceting(postedAtFR);
List<Facet> facets = facetMgr.getFacets("userNameFR");

I first use the QueryBuilder to create a new FacetingRequest with the name “userNameFR” that uses the index field that maps the userName attribute. In this example, I want to get a maximum of 3 discrete facets which have at least 1 element that matches the full-text query.

By default, Hibernate Search returns the facets in the descending order of their search results.

06:44:05,467  INFO TestSearchTweets:179 - thjanssen123 3
06:44:05,467  INFO TestSearchTweets:179 - baeldung 1
06:44:05,467  INFO TestSearchTweets:179 - nipafx 1

Use a Facet in your query

Getting the facets of a query result and showing them in the UI is good first step. But what happens if you a user selects one of the facets and wants to see the matching query results?

You obviously need to use the selected facet in your query. You can do that based on the facets you selected in the previous example.

// create a FullTextQuery and select Facets as shown in previous code snippets

FacetSelection facetSelection = facetMgr.getFacetGroup( "userNameFR" );
facetSelection.selectFacets( facets.get( 0 ) );

List<Tweet> tweets = fullTextQuery.getResultList();
for (Tweet t : tweets) {
  log.info(t);
}

As you can see, you just need to get a FacetSelection from the FacetManager, select the Facet you want to use in your query and execute the FullTextQuery again.

Summar

As you’ve seen, Hibernate Search allows you to categorize the results of a FullTextQuery with Facets. That is a concept you often see in online shops which present the search results in different product categories or on websites which categorize their articles by date.

Facetting your query results with Hibernate Search requires 3 step:

  1. Add @Facet annotation to the entity attribute you want to use for facetting.
  2. Create a FullTextQuery.
  3. Create a FacetingRequest and retrieve the Facets.

After you retrieved all Facets, you can use one of them in your FullTextQuery.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.