2020-03-21

Hibernate Search - Projection DSL

Projections do just that: they allow the query to return something more precise than just "the matching entities". Projections can be configured when building the search query:


Using projections to extract data from the index

SearchSession searchSession = Search.session( entityManager );

List<String> result = searchSession.search( Book.class )
        .select( f -> f.field( "title", String.class ) )
        .where( f -> f.matchAll() )
        .fetchHits( 20 );
Start building the query as usual.
Mention that the expected result of the query is a projection on field "title", of type String. If that type is not appropriate or if the field does not exist, an exception will be thrown.
Fetch the results, which will have the expected type.

Using projections to extract data from the index — object-based syntax

SearchSession searchSession = Search.session( entityManager );

SearchScope<Book> scope = searchSession.scope( Book.class );

List<String> result = searchSession.search( scope )
        .select( scope.projection().field( "title", String.class )
                .toProjection() )
        .where( scope.predicate().matchAll().toPredicate() )
        .fetchHits( 20 );

documentReference: return references to matched documents
Returning references to matched documents
List<DocumentReference> hits = searchSession.search( Book.class )
        .select( f -> f.documentReference() )
        .where( f -> f.matchAll() )
        .fetchHits( 20 );

entityReference: return references to matched entities
Returning references to matched entities
List<EntityReference> hits = searchSession.search( Book.class )
        .select( f -> f.entityReference() )
        .where( f -> f.matchAll() )
        .fetchHits( 20 );

entity: return matched entities loaded from the database
Returning matched entities loaded from the database
List<Book> hits = searchSession.search( Book.class )
        .select( f -> f.entity() )
        .where( f -> f.matchAll() )
        .fetchHits( 20 );

field: return field values from matched documents

Returning field values from matched documents

List<Genre> hits = searchSession.search( Book.class )
        .select( f -> f.field( "genre", Genre.class ) )
        .where( f -> f.matchAll() )
        .fetchHits( 20 );
Returning field values from matched documents, without specifying the field type
List<Object> hits = searchSession.search( Book.class )
        .select( f -> f.field( "genre" ) )
        .where( f -> f.matchAll() )
        .fetchHits( 20 );
Returning field values from matched documents, without converting the field value
List<String> hits = searchSession.search( Book.class )
        .select( f -> f.field(
                "genre", String.class, ValueConvert.NO
        ) )
        .where( f -> f.matchAll() )
        .fetchHits( 20 );

score: return the score of matched documents
 Returning the score of matched documents
List<Float> hits = searchSession.search( Book.class )
        .select( f -> f.score() )
        .where( f -> f.match().field( "title" )
                .matching( "robot dawn" ) )
        .fetchHits( 20 );

distance: return the distance to a point
Returning the distance to a point
GeoPoint center = GeoPoint.of( 47.506060, 2.473916 );
SearchResult<Double> result = searchSession.search( Author.class )
        .select( f -> f.distance( "placeOfBirth", center ) )
        .where( f -> f.matchAll() )
        .fetch( 20 );
Returning the distance to a point with a given distance unit
GeoPoint center = GeoPoint.of( 47.506060, 2.473916 );
SearchResult<Double> result = searchSession.search( Author.class )
        .select( f -> f.distance( "placeOfBirth", center )
                .unit( DistanceUnit.KILOMETERS ) )
        .where( f -> f.matchAll() )
        .fetch( 20 );

composite: combine projections
Returning custom objects created from multiple projected values

List<MyPair<String, Genre>> hits = searchSession.search( Book.class )
        .select( f -> f.composite(
                MyPair::new,
                f.field( "title", String.class ),
                f.field( "genre", Genre.class )
        ) )
        .where( f -> f.matchAll() )
        .fetchHits( 20 );
Call .composite(…​).
Use the constructor of a custom object, MyPair, as the combining function. The combining function can be a Function, a BiFunction, or a org.hibernate.search.util.common.function.TriFunction. It will combine values returned by other projections and create an object returned by the composite projection. Depending on the type of function, either one, two, or three additional arguments are expected.
Define the first projection to combine as a projection on the title field, meaning the constructor of MyPair will be called for each matched document with the value of the title field as its first argument.
Define the second projection to combine as a projection on the genre field, meaning the constructor of MyPair will be called for each matched document with the value of the genre field as its second argument.
The hits will be the result of calling the combining function for each matched document, in this case MyPair instances.


Returning a List of projected values

List<List<?>> hits = searchSession.search( Book.class )
        .select( f -> f.composite(
                f.field( "title", String.class ),
                f.field( "genre", Genre.class )
        ) )
        .where( f -> f.matchAll() )
        .fetchHits( 20 );
Call .composite(…​).
Define the first projection to combine as a projection on the title field, meaning the hits will be List instances with the value of the title field of the matched document at index 0.
Define the second projection to combine as a projection on the genre field, meaning the hits will be List instances with the value of the genre field of the matched document at index 1.
The hits will be List instances holding the result of the given projections, in order for each matched document.


Lucene: document

Returning the matched document as a native org.apache.lucene.document.Document
List<Document> hits = searchSession.search( Book.class )
        .extension( LuceneExtension.get() )
        .select( f -> f.document() )
        .where( f -> f.matchAll() )
        .fetchHits( 20 );

Lucene: explanation

Explanations are rather costly performance-wise: only use them for debugging purposes.

Returning the score explanation as a native org.apache.lucene.search.Explanation
List<Explanation> hits = searchSession.search( Book.class )
        .extension( LuceneExtension.get() )
        .select( f -> f.explanation() )
        .where( f -> f.matchAll() )
        .fetchHits( 20 );

Elasticsearch: source

Returning the matched document source as a JsonObject
List<JsonObject> hits = searchSession.search( Book.class )
        .extension( ElasticsearchExtension.get() )
        .select( f -> f.source() )
        .where( f -> f.matchAll() )
        .fetchHits( 20 );

Elasticsearch: explanation

Explanations are rather costly performance-wise: only use them for debugging purposes.

Returning the score explanation as a JsonObject
List<JsonObject> hits = searchSession.search( Book.class )
        .extension( ElasticsearchExtension.get() )
        .select( f -> f.explanation() )
        .where( f -> f.matchAll() )
        .fetchHits( 20 );

Elasticsearch: jsonHit

This is particularly useful when customizing the request’s JSON to ask for additional data within each hit.

Direct changes to the HTTP request may conflict with Hibernate Search features and be supported differently by different versions of Elasticsearch.

Similarly, the content of the HTTP response may change depending on the version of Elasticsearch, depending on which Hibernate Search features are used, and even depending on how Hibernate Search features are implemented.

Thus, features relying on direct access to HTTP requests or responses cannot be guaranteed to continue to work when upgrading Hibernate Search, even for micro upgrades (x.y.z to x.y.(z+1)).

Use this at your own risk.

Returning the Elasticsearch hit as a JsonObject
List<JsonObject> hits = searchSession.search( Book.class )
        .extension( ElasticsearchExtension.get() )
        .select( f -> f.jsonHit() )
        .where( f -> f.matchAll() )
        .fetchHits( 20 );


No comments:

Post a Comment