Поиск в режиме гибернации : Сортировка с фильтром по вложенному объекту, как это сделать?

#elasticsearch #hibernate-search

Вопрос:

Я должен закодировать поисковый запрос в режиме гибернации (для бэкенда базы данных эластичного поиска), который включает в себя условный вид такого рода :

 Date dateOfBirth = new Date('01/01/2000');
Integer age = 10;
if (dateOfBirth == null) {
   //then sort by age
}
else {
   //sort by date of birth
}
 

Я нашел пример для кодирования этой условной сортировки в справочнике поиска Hibernate, это можно сделать следующим образом (приведенный пример) :

 List<Author> hits = searchSession.search( Author.class )
.where( f -> f.matchAll() )
.sort( f -> f.field( "books.pageCount" )
.mode( SortMode.AVG )
.filter( pf -> pf.match().field( "books.genre" )
.matching( Genre.CRIME_FICTION ) ) )
.fetchHits( 20 );
 

Моя проблема в том, что поиск в режиме гибернации вызывает исключение во время выполнения. Мой код фильтра сортировки :

  case DATE_SIGNATURE:
                FieldSortOptionsStep bivSortFirst = f.field(Depot_.VENTE   "."   Vente_.DATE_SIGNATURE)
                        .filter(fa ->
                                {
                                    PredicateFinalStep a = fa.bool(bo -> bo.must(fa.exists().field(Depot_.VENTE   "."   Vente_.DATE_SIGNATURE)));
                                    return fa.bool(b0 -> b0.must(a));
                                }
                        );
                FieldSortOptionsStep bivSortSecond = f.field(Depot_.VENTE   "."   Vente_.ACTE   "."   Acte_.SIGNATURE)
                        .filter(fa ->
                                {
                                    PredicateFinalStep a = fa.bool(bo -> bo.mustNot(fa.exists().field(Depot_.VENTE   "."   Vente_.DATE_SIGNATURE)));
                                    PredicateFinalStep b = fa.bool(bo -> bo.must(fa.exists().field(Depot_.VENTE   "."   Vente_.ACTE   "."   Acte_.SIGNATURE)));
                                    return fa.bool(b0 -> b0.must(a).must(b));
                                }
                        );
                sortFieldOrderedList.add(bivSortFirst);
                sortFieldOrderedList.add(bivSortSecond);
                break;
 

В приведенном выше примере я сортирую по двум полям по приоритету. Первое сопоставимо с «датой рождения», а второе-с «возрастом». Во время выполнения фильтр не принимается поиском в режиме гибернации, а затем выдает исключение, подобное следующему :

Сообщение об ошибке :

HSEARCH400604: Недопустимый фильтр сортировки: поле «vente.acte.подпись» не содержится во вложенном объекте. Фильтры сортировки доступны только в том случае, если поле для сортировки содержится во вложенном объекте. Контекст: поле «vente.acte.подпись»

Я читал, что для этого мне нужно перейти к запросу «inner_hits» для эластичного поиска. Но как мне это сделать с помощью hibernate search API ?

Спасибо.

ИЗМЕНИТЬ : Отображение классов в режиме гибернации :

 @Entity
@Indexed
public class Depot {
    ...
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "vente_fk")
    protected Vente vente;
                
    @IndexedEmbedded(includePaths = {
    Vente_.ID,
    Vente_.DATE_SIGNATURE,
    Vente_.DATE_SIGNATURE_ACTE,
    Vente_.ACTE   "."   Acte_.SIGNATURE,
        and much more
    }
    public Vente getVente() {
            return this.vente;
        }
    ...
}

@Entity
public class Vente {

    @OneToMany(mappedBy = Depot_.VENTE, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    protected Set<Depot> depot = new HashSet<>();

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "acte_fk")
    protected Acte acte;
...
    @AssociationInverseSide(inversePath = @ObjectPath(@PropertyValue(propertyName = Acte_.VENTE)))
    @IndexedEmbedded
    public Acte getActe() {
        return this.acte;
    }
...
}

@Entity
public class Acte {
...
    @GenericField(projectable = Projectable.YES, sortable = Sortable.YES, aggregable = Aggregable.YES)
    protected Date signature;
    
    @OneToMany(mappedBy = Vente_.ACTE)
    protected Set<Vente> vente = new HashSet<>();
    
    public Date getSignature() {
        return this.signature;
    }
...
}
 

Комментарии:

1. Вам нужно показать свою модель, чтобы люди поняли, что вы пытаетесь сделать. В частности: Vente , Acte , и любой суперкласс или суперинтерфейс.

2. Также Depot , конечно.

3. Depot-это корневой документ (@ Indexed). Венте @ indexEmbedded. Acte-это @ IndexEmbedded. Индексированный документ выглядит как json (_source-это хранилище): _source { vente: { acte: { подпись: { } } } Достаточно ли этого ?

4. Боюсь, что это не так. Мне нужно увидеть фактические аннотированные свойства, такие как Depot#vente Vente#acte , и т. Д.

5. ок добавлено в часть редактирования.

Ответ №1:

Из того, что я вижу , для каждого Depot есть не более одного Acte и одного Vente . Поэтому то, что вы пытаетесь сделать, немного экзотично, поскольку фильтрация в сортировках обычно используется для многозначных вложенных объектов.

Причина, по которой это не работает, заключается в том, что вы не пометили @IndexedEmbedded объекты (vente, acte) как «вложенные»; как объясняется в документации, фильтрация работает только для вложенных объектов. И «вложенный» имеет очень точное значение, оно не синонимично «индексированно-встроенному».

Однако я думаю, что в данном случае весь подход неверен: вы не должны использовать фильтрацию. Я совершенно уверен, что даже если вы пометите @IndexedEmbedded объекты как «вложенные», вы столкнетесь с другими проблемами, потому что то, что вы пытаетесь сделать, не является целью фильтрации. Одной из таких проблем может быть производительность; вложенные документы означают соединения во время выполнения, а соединения во время выполнения стоят недешево.

Вместо этого рассмотрите возможность решения этой проблемы во время индексации. Вместо того чтобы пытаться выяснить, какую дату использовать для каждого документа при поиске, сделайте это при индексировании:

 @Entity
@Indexed
public class Depot {
    //...
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "vente_fk")
    protected Vente vente;
                
    @IndexedEmbedded(includePaths = {
    Vente_.ID,
    Vente_.DATE_FOR_SORT, // <================= ADD THIS
    Vente_.DATE_SIGNATURE,
    Vente_.DATE_SIGNATURE_ACTE,
    Vente_.ACTE   "."   Acte_.SIGNATURE,
        //and much more
    })
    public Vente getVente() {
            return this.vente;
        }

}

@Entity
public class Vente {

    @OneToMany(mappedBy = Depot_.VENTE, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    protected Set<Depot> depot = new HashSet<>();

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "acte_fk")
    protected Acte acte;
//...
    @AssociationInverseSide(inversePath = @ObjectPath(@PropertyValue(propertyName = Acte_.VENTE)))
    @IndexedEmbedded
    public Acte getActe() {
        return this.acte;
    }

    // v================= ADD THIS
    @Transient
    @IndexingDependency(derivedFrom = {
        @ObjectPath(@PropertyValue(propertyName = Vente_.DATE_SIGNATURE)),
        @ObjectPath(@PropertyValue(propertyName = Vente_.ACTE), @PropertyValue(propertyName = Acte_.SIGNATURE)),
    })
    public Date getDateForSort() {
        if ( getDateSignature() != null ) {
            return getDateSignature();
        }
        else {
            return getActe().getSignature();
        }
    }
    // ^================= ADD THIS
//...
}
 

Комментарии:

1. Хороший ответ. Я уже сделал то, что вы сказали : удалите этот фильтр, который не работает, и предварительно вычислите это значение динамической сортировки (но за счет поддержания этого значения индекса в актуальном состоянии). Спасибо вам за ваш совет о вложенном объекте, я не знал этого понятия. Но то, как elastic search хранит свои документы, говорит мне о том, что я проиндексировал свой контент как вложенный, а не сглаженный : документ-это дерево json, а не только свойства в _source {}. Мои поля упоминаются как вложенные только с @IndexEmbedded : Поле 1 «.» Поле 2 «.» …. и т.д.

2. Даже в Elasticsearch по умолчанию содержимое индекса сглажено. Не имеет значения, отображается ли ваш JSON как вложенный: индекс структурирован по-другому, и по умолчанию он сглажен. См. nested Тип данных, который можно выбрать, и object тип данных, который используется по умолчанию.