Indexing an entity automatically is nice, but we can argue that it would have been reasonably simple to do it without Hibernate Search, simply by converting our entity to JSON and sending it to Elasticsearch manually, every time we create/update/delete a client. This will involve additional boilerplate code, but it can be an option.

However, most of the time, we will not want to index data from just one entity, but from an entity graph. For example, let’s assume we want to index the business manager’s name as part of the client, so that we can search for "lapin" to easily get a list of all the clients managed by the business manager Phyllis Lapin.

This is where things start getting complex:

When the name of a business manager changes, we will need to load and reindex the assigned clients. When other properties of the business manager change (for example the phone number), we do not need to reindex the assigned clients, since these other properties are not indexed.

These two requirements would make manually reindexing entities significantly harder to implement efficiently: the code would need to be aware of which of the business manager’s properties are used when indexing a client, it would need to keep track of which properties of the business manager’s are actually changed, and based on that would need to decide whether to load clients for reindexing or not.

Add a couple more associations like this to the Client entity or (worse) add a few levels of nesting, and the simple boilerplate code will soon turn into a time sink.

Fortunately, Hibernate Search handles all this transparently. In order to index the business manager’s name as part of the client, only two steps are necessary.

First, we will declare a field in the business manager:

@Entity public class BusinessManager extends PanacheEntity { @OneToMany (mappedBy = " assignedManager " ) public List <Client> assignedClients = new ArrayList <>(); @FullTextField (analyzer = " english " ) (1) public String name; public String email; public String phone; }

1 Define a full-text field whose content will be extracted from the name property.

@Entity @Indexed public class Client extends PanacheEntity { @FullTextField (analyzer = " english " ) public String name; @ManyToOne @IndexedEmbedded (1) public BusinessManager assignedManager; }

1 Define the assigned manager as "indexed-embedded" into the client, meaning all the indexed fields defined in the business manager will be embedded into the client upon indexing. Simply put, a new field will appear in index documents generated for clients: assignedManager.name .

That’s all for the mapping: Hibernate Search will know that whenever a business manager’s name changes, it must reindex the assigned clients.

To take advantage of this new assignedManager.name field, let’s change our search method:

// ... public class ClientResource { // ... @GET @Path ( " /client/search " ) public List <ClientRetrieveDto> search( @QueryParam ( " terms " ) String terms) { List <Client> result = Search.session( Panache.getEntityManager() ) .search( Client.class ) .predicate( f -> f.simpleQueryString() .fields( " name " , " assignedManager.name " ) (1) .matching( terms ) .defaultOperator( BooleanOperator.AND ) ) .fetchHits( 20 ); return result.stream().map( mapper::toDto ).collect( Collectors.toList() ); } // ... }

1 Look for matches not only in the name field, but also in the assignedManager.name field.

We’re now ready to test the changes. Reindexing is necessary because of the mapping change, but once again Quarkus' hot reload should take care of it, so we can send a request to our service immediately:

$ curl -X GET 'http://localhost:8080/client/search/?terms=lapin' [ { "assignedManager": { "email": "plapin@dundermifflin.net", "id": 6, "name": "Phyllis Lapin", "phone": "+1-202-555-0153" }, "id": 7, "name": "Stark Industries" }, { "assignedManager": { "email": "plapin@dundermifflin.net", "id": 6, "name": "Phyllis Lapin", "phone": "+1-202-555-0153" }, "id": 8, "name": "Parker Industries" } ]

Upon Phyllis Lapin’s wedding with Bob Vance, we can now update her name and email:

$ curl -X POST http://localhost:8080/manager/6 -H "Content-Type: application/json" -d '{"name": "Phyllis Vance", "email": "pvance@dundermifflin.net"}'

Since Hibernate Search updates the index, "lapin" will no longer match:

$ curl -X GET 'http://localhost:8080/client/search/?terms=lapin' [ ]

... but "vance" will match: