Hibernate Search: Volltextsuche in Spring-Boot-Projekten mit Apache Lucene

Seite 2: Spring-Boot-Anwendung für Gesetze und Paragrafen

Inhaltsverzeichnis

Die Beispielanwendung ist eine Spring-Boot-Anwendung, die Gesetze und einzelne Paragrafen speichert und als REST-API anbietet. Das Datenmodell dazu besteht aus der Entität LegalDocument, die die allgemeinen Daten des Gesetzes umfasst, und darin enthalten ist eine Liste von Articles mit dem eigentlichen Gesetzestext des Paragrafen. Der Datenzugriff erfolgt über ein Spring-Data-Repository, das für jede Entität erstellt wird. Zusätzlich ist Spring-Data-REST in das Projekt eingebunden und exponiert das Datenmodell automatisch als REST-Endpunkt. Über diese Schnittstelle sind die Daten bereits abrufbar. Der vollständige Quelltext zum Projekt ist auf GitHub verfügbar.

Damit ist das Datenmodell fertig und die Einträge lassen sich lesen, erstellen und bearbeiten. Als Nächstes kommt der Suchindex für die Volltextsuche in den Paragrafen hinzu. Die Abhängigkeiten lassen sich über Maven einbinden, wie das folgende Listing zeigt:

<dependency>
  <groupId>org.hibernate.search</groupId>
  <artifactId>hibernate-search-engine</artifactId>
  <version>${hibernate.search.version}</version>
</dependency>
<dependency>
  <groupId>org.hibernate.search</groupId>
  <artifactId>hibernate-search-mapper-orm</artifactId>
  <version>${hibernate.search.version}</version>
</dependency>
<dependency>
  <groupId>org.hibernate.search</groupId>
  <artifactId>hibernate-search-backend-lucene</artifactId>
  <version>${hibernate.search.version}</version>
</dependency>

Auf jeden Fall nötig sind die Such-Engine und die Mapper-Bibliothek, die das Datenmodell mit dem Suchindex verbindet. Zur direkten Speicherung des Suchindex mit Apache Lucene ist dieses als Backend noch dabei.Die Konfiguration für Hibernate Search befindet sich in der Datei hibernate.properties im Ressourcen-Ordner src/main/resources/. Die Einstellung für die Eigenschaft directory.type ist auf ein lokales Dateisystem gesetzt und zusätzlich ist noch der Pfad angegeben, wo das Programm den Suchindex ablegt. Folgende Codzeilen zeigen, wie man die Konfiguration des Suchindex einfügt:

hibernate.search.backend.directory.type = local-filesystem
hibernate.search.backend.directory.root = C://Tools//Search//Playground
hibernate.search.backend.analysis.configurer = LegalDocumentAnalysisConfigurer

Die Java-Klassen für das Datenmodell sind mit der Annotation @Entity und die Attribute mit @Column erweitert, damit Hibernate ORM sie erkennt. Zur Aufnahme der Daten in den Suchindex kommt die Annotation @Indexed zur Klasse hinzu. Damit das Programm die einzelnen Felder in den Index übernimmt, erhalten sie eine der @*Field-Annotationen. Das Feld title ist als @FullTextField annotiert. Das bedeutet, dass für dieses Feld eine Textanalyse ausgeführt wird. Der Text wird in einzelne Tokens gesplittet und anschließend normalisiert.

Die Details der Textanalyse folgen weiter unten in diesem Artikel. Für die Attribute Kurztitel (titleShort) und Abkürzung (abbreviation) wird die @KeywordField-Annotation verwendet. Dabei wird der Inhalt des Feldes nicht analysiert, sondern nur normalisiert. Zusätzlich ist beim Attribut abbreviation die Eigenschaft aggregable aktiviert, damit lässt sich eine Aggregierung auf dieses Attribut ausführen. Wie sich LegalDocument.java einfügen lässt, demonstriert folgendes Listing.

@Entity
@Indexed
public class LegalDocument extends AbstractEntity {

    @FullTextField
    @Column(length = 1024)
    private String title;
    
    @KeywordField(name = "titleShort")
    private String titleShort;

    @KeywordField(aggregable = Aggregable.YES)
    private String abbreviation;

    @OneToMany(mappedBy = "document", cascade = CascadeType.ALL)
    @JsonBackReference
    private List<Article> articles;

// getter and setter
}

Die Article-Klasse ist ebenfalls mit der @Indexed-Annotation versehen. Der Titel des Artikels (title) und der eigentliche Gesetzestext (text) sind wiederum als @FullTextField annotiert und werden somit für den Suchindex ausgewertet. Jeder Paragraf verfügt über ein Datum des Inkrafttretens und Außerkrafttretens. Diese Datumsfelder sind als @GenericField annotiert. Das @GenericField unterstützt verschiedene Typen in Java, und bei der Suche wird ein exakter Vergleich gezogen.

Die Suchabfragen laufen im Datenmodell auf der Ebene der Artikel. Für den Zugriff auf den Suchindex des übergeordneten Gesetzes wird das LegalDocument mit @IndexedEmbedded annotiert. Zur Suche nach allen Artikeln eines Gesetzes anhand der Abkürzung können Entwickler den Punktoperator document.abbreviation verwenden. Das nächste Listing zeigt, wie man Article.java korrekt einfügt:

@Entity
@Indexed
public class Article extends AbstractEntity {

    @GenericField(name = "number", sortable = Sortable.YES)
    private String number;

    private String eli;

    @FullTextField(name = "title", analyzer = "german")
    @GenericField(name = "title_sort", sortable = Sortable.YES)
    private String title;
    
    @FullTextField(name = "text", analyzer = "german")
    @Column(length = 4096)
    private String text; 

    @GenericField
    @Column(columnDefinition = "DATE")
    private LocalDate effectiveDate;

    @GenericField
    @Column(columnDefinition = "DATE", nullable = true)
    private LocalDate expireDate;

    @IndexedEmbedded
    @ManyToOne
    @JoinColumn(name = "document_id", nullable = false)
    @JsonManagedReference
    private LegalDocument document;
    // getter and setter
}