Hibernate Search: Volltextsuche in Spring-Boot-Projekten mit Apache Lucene
Seite 4: Facettensuche bei Hibernate Search
In vielen Online-Shops sind nach einer Suche passende Kategorien und Preisbereiche aufgelistet. Die Bezeichnung dafür ist Facettensuche und Hibernate Search bietet sie ebenfalls an. Durch die Facettensuche lässt sich das Suchergebnis einfach einschränken und verfeinern, beispielsweise auf eine bestimmte Marke oder einen Preisbereich. Bis Version 5 von Hibernate Search hieß diese Funktion auch noch Faceting, mit Version 6 wurde sie umbenannt in eine allgemeine Aggregation-DSL.
Im Beispiel basiert die Aggregierung auf der Abkürzung eines Gesetzes. Dadurch bekommt man einen schnellen Überblick, in wie vielen Paragrafen eines Gesetzes sich der Suchbegriff auffinden lässt. Die Voraussetzung ist ähnlich wie bei der Sortierung: Das Feld muss in der Entität als aggregierbar definiert sein, und ein FullTextField
lässt sich nicht aggregieren.
FĂĽr eine Suche gilt es, zuerst einen AggregationKey
mit einem Namen zu definieren. Die Suche selbst ist aufgebaut wie im vorigen Beispiel, allerdings ist die Aggregierung zum Aufruf nach der matching
-Methode hinzuzufügen. Nach der Ausführung der Suche lässt sich die Aggregierung über das Ergebnis auslesen.
Die Aggregierung ist ein Map-Objekt, wobei der SchlĂĽssel (Key
) das Objekt ist, nach dem aggregiert ist. Im nächsten Code-Beispiel handelt es sich um einen String, und der Wert (Value
) enthält die Anzahl der Treffer. Die Anwendung der Aggregierung kann sich wahlweise auf diskrete Begriffe oder auf kontinuierliche Bereiche beziehen, wie es für Preisbereiche erforderlich ist. Die Codezeilen für eine Suche mit Aggregierung sind wie folgt:
AggregationKey<Map<String, Long>> countByAbbreviation = AggregationKey.of("countByAbbreviation");
SearchResult<Article> result = searchSession.search(Article.class)
.where(f -> f.match().fields("title", "document.title", "document.abbreviation")
.matching(request.keyword))
.aggregation(countByAbbreviation, f -> f.terms().field("document.abbreviation", String.class))
.fetch(100);
List<SearchAggregation> facetAbbreviation = result.aggregation(countByAbbreviation)
.entrySet()
.stream()
.map(e -> new SearchAggregation(e.getKey(), e.getValue()))
.collect(Collectors.toList());
return new SearchResponse<>(
result.hits(),
new PageMetadata(100, 0, result.total().hitCount()),
facetAbbreviation);
Textanalyse fĂĽr die Volltextsuche
Eine Textanalyse ermöglicht eine effizientere Suche. Beim Indizieren erfolgt sie auf die Daten und bei der Suche entsprechend auf die Suchbegriffe, und zwar in drei Schritten. Der erste Schritt ist dabei die Anwendung von Zeichenfiltern (character filters
), die den Eingabetext von nicht benötigten Zeichen bereinigen. Beispielsweise lässt sich mit dem HTMLStripCharFilter
eine HTML-Formatierung des Textes entfernen.
Liegt der Text der Paragrafen im HTML-Format vor, ist es möglich, die HTML-Formatierung zu bereinigen und dann gezielt nur den tatsächlichen Inhalt in den Suchindex zu übernehmen. Das Aufsplitten des Textes in einzelne Wörter (tokens
) erfolgt im zweiten Schritt mit einem Tokenizer.
Im dritten Schritt kommen Token-Filter zur Anwendung, um einzelne Tokens zu normalisieren, transformieren oder zu entfernen. Ein Beispiel dafür ist die Normalisierung der deutschen Umlaute oder die Stammformreduktion: Das Stemming führt Wörter auf einen gemeinsamen Wortstamm zurück und ermöglicht somit auch verschiedene Formen eines Wortes als Suchtreffer. Die Tabelle zeigt ein Beispiel für die Transformation des Textes aus der Datenbank mit dem Analyzer für den Suchindex.
Vom Datenbank- zum Sucheintrag: Drei Schritte der Textanalyse |
|
Eingabe fĂĽr HTMLStripCharFilter |
Ergebnis |
<h1>Text</h1><h2>Maßnahmen</h2><div><h3 class="GldSymbol AlignJustify">§ 2.</h3><p> Maßnahmen im Sinne dieses Bundesgesetzes sind solche, … <p></div> |
Maßnahmen§ 2. Maßnahmen im Sinne des Bundesgesetzes sind solche, … |
Eingabe fĂĽr StandardTokenizer |
Ergebnis |
Maßnahmen § 2. Maßnahmen im Sinne des Bundesgesetzes sind solche, … | [Maßnahmen, §,2., Maßnahmen, im, Sinne, des, Bundesgesetzes, sind, solche ] |
Eingabe fĂĽr LowerCaseFilter |
Ergebnis |
[Maßnahmen, §,2., Maßnahmen, im, Sinne, des, Bundesgesetzes, sind, solche ] | [maßnahmen, §,2., maßnahmen, im, sinne, des, bundesgesetzes, sind, solche ] |
In der Standardeinstellung von Hibernate Search mit Apache Lucene erledigt ein Tokenizer die Aufteilung in einzelne Wörter und ein Token-Filter kümmert sich um die Kleinschreibung. Diese Analyse funktioniert für viele Sprachen, ist aber auch nicht optimiert für eine bestimmte. Die Erstellung einer individuellen Konfiguration für Apache Lucene im Backend wird mit einem LuceneAnalysisConfigurer
umgesetzt.
Bei den deutschsprachigen Gesetzestexten kommt ein Standard Tokenizer zum Einsatz, auĂźerdem die Token-Filter LowerCaseFilter
, ASCIIFoldingFilter
und Snowball-PorterFilter
. Letzterer implementiert die Stammformreduktion, die sich mit einem Parameter für Deutsch konfigurieren lässt. Die Konfiguration erhält die Bezeichnung "german"
und ist in der Article
-Klasse beim @FullTextField
zu platzieren. Das folgende Listing zeigt eine Textanalyse fĂĽr deutschsprachige Inhalte:
@Component("LegalDocumentAnalysisConfigurer")
public class MyLegalDocumentAnalysisConfigurer implements LuceneAnalysisConfigurer {
@Override
public void configure(LuceneAnalysisConfigurationContext context) {
context.analyzer("german").custom()
.tokenizer(
StandardTokenizerFactory.class)
.tokenFilter(
LowerCaseFilterFactory.class)
.tokenFilter(
SnowballPorterFilterFactory.class)
.param("language", "German")
.tokenFilter(ASCIIFoldingFilterFactory.class);
}
}