#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
тип данных, который используется по умолчанию.