Search in json field using hibernate search

Viewed 638

I have a trouble with search by the entities using hibernate search. In my case i need to search:

  • by all fields;
  • by key-value in json field

This is my entity:

@Entity
@Indexed(index = "test")
@Table(name = "test")
@TypeDef(name = "jsonb", typeClass = Jsonb.class)
public class TestEntity {

    @Id
    @Column(name = "id")
    private UUID Id;

   @FullTextField
    @Column(name = "name", nullable = false, length = 50)
    private String name;

    @FullTextField
    @Column(name = "desc")
    private String desc;

    @Column(name = "data")
    @Type(type = "jsonb")
    @ElementCollection
    @PropertyBinding(binder = @PropertyBinderRef(type = JsonPropertyBinderNew.class))
    private Map<String, Object> data;
//constructor
//getters 
//setters
}

Entity have a column in JSONB format. Using PropertyBinder i save it in elastic:

public class JsonPropertyBinder implements PropertyBinder {

    @Override
    public void bind(PropertyBindingContext context) {
        context.dependencies().useRootOnly();

        IndexSchemaElement schemaElement = context.indexSchemaElement();

        IndexFieldType<String> amountFieldType = context.typeFactory()
                .asString().analyzer("english").toIndexFieldType();

        context.bridge(Map.class, new Bridge(
                schemaElement.field("data", amountFieldType).toReference()));
    }

    private static class Bridge implements PropertyBridge<Map> {
        private IndexFieldReference<String> data;

        public Bridge(IndexFieldReference<String> data) {
            this.data = data;
        }

        @Override
        public void write(DocumentElement target, Map bridgedElement, PropertyBridgeWriteContext context) {
            Gson gson = new Gson();
            target.addValue(data, gson.toJson(bridgedElement));
        }
    }

In elastic it looks like:

data: {"SomeKey":"SomeKey","StringKey":"Stroka","ObjectKey":"ObjectData"}
desc:Fourth
name: Fourth Test Name

When i try to search by field

List<TestEntity> result = searchSession.search(TestEntity.class)
                .where(f -> f.exists().field("data.SomeKey"))
                .fetchHits(20);

I got exeption: "org.hibernate.search.util.common.SearchException: HSEARCH400504: Unknown field 'data.SomeKey'"

If i try to use simpleQueryString it works but i coud not use fuzzing:

SearchResult<TestEntity> search = searchSession.search(TestEntity.class)
            .where(f -> f.simpleQueryString()
                    .fields("data")
                    .matching("ObjectKey + ObjectData")
            ).fetch(20);

If i try to use Predicate DSL i coud not use boolean operators like "AND using +"

List<TestEntity> hits = searchSession.search( TestEntity.class )
                .where( f -> f.match().field( "data" )
                        .matching( "ObjectKey ObjectData" ).fuzzy())
                .fetchHits( 20 );

How coud i search in inner json using key-value?

1 Answers

If you want to use the Hibernate Search DSL to search, you need to make Hibernate Search aware of your schema: the fields, their name, their type, ...

By declaring just a "data" field and pushing JSON directly into it, you are effectively hiding the schema from Hibernate Search. You are also relying on Elasticsearch's ability to auto-detect field types when they are first populated in the index, which can lead to bad surprises.

When you do that, your only solution for searching is to bypass Hibernate Search and to write down the JSON of your query yourself. See here for the JSON syntax. Personally I would avoid this, but to each his own.

Really, a better solution would be to cleanly declare all your fields using field templates. That way Hibernate Search would be aware of the type of each field and you would be able to take advantage of the DSL.


As to targeting all fields in a single predicate, Hibernate Search does not support that yet (HSEARCH-3926). You must either:

  • List all the fields in your predicate, e.g. f.match().fields( "data.SomeKey", "data.SomeOtherKey", "data.SomeThirdKey" )
  • OR put the data of each single value in your JSON in another field next to data, say data_all, and then search on that field.
  • OR change your binder to declare field templates, and use native field types for those fields to leverage Elasticsearch's copy_to feature. You will also have to declare the field that you will copy to, of course. Then you will target this field when searching.
Related