Volltextsuche mit der Open-Source-Lösung Compass Search im Cluster

von Manuel Breitfeld

Wer sich mit Suchmaschinen für eigene Projekte beschäftigt, stößt zweifellos früher oder später auf Apache Lucene. Lucene ist eine Suchmaschinen-Bibliothek, die mit einer beeindruckenden Funktionsvielfalt ausgestattet ist. Sie ist in Java geschrieben, mittlerweile aber auch für viele andere Programmiersprachen verfügbar.

In diesem Eintrag geht es um die Einbindung einer Suche in ein WebLogic Portal Projekt, welches in einem Cluster auf WebLogic Servern läuft. Als Datenbank-Persistenzschicht wird Hibernate eingesetzt. Die Suche soll dabei Entitäten und deren Relationen erfassen und Funktionen wie unter anderem unscharfe Suche und boolesche Operatoren unterstützen und dabei einen gutes Ranking der Ergebnisse liefern.

Durchsuchen und Indizieren von Entitäten mit Relationen

In diesem Beispiel geht es um eine Applikation, mit der Projekte verwaltet werden können. Im vereinfachten Beispiel soll davon ausgegangen werden, dass es lediglich die Entität Project mit einigen Attributen sowie die Entität User mit einigen Attributen gibt. Dabei besteht zwischen Project und User folgende Relation: Ein Projekt ist immer einem User zugeordnet, ein User kann jedoch auch mehreren Projekten zugeordnet sein.

Bei der Suche soll dabei der Titel des Projekts, die Beschreibung des Projekts und der Vor- und Zuname des zugeordneten Autors (Entität User) durchsucht werden können.

Die folgende Abbildung zeigt die Entitäten und die vorherrschende Beziehung:

Das Framework Compass

Compass Search ist ein Framework, welches auf Lucene setzt und eine einfache (Java) API für Suche und Indizierung anbietet. Darüber hinaus lässt sich mit Compass Search die Suche direkt mit Hibernate und entsprechenden Annotationen in ein Projekt einbinden. Mit Compass Search besteht außerdem die Möglichkeit, den Suchindex über JDBC in einer relationalen Datenbank abzuspeichern. Letzteres ist mit Hibernate Search, einem Projekt, das einen ähnlichen Ansatz wie Compass Search verfolgt, nicht möglich, was den Einsatz bei Applikationen, die in einem Cluster laufen, ausschließt.

Damit Compass Search verwendet werden kann, muss es in das Projekt eingebunden werden. In erster Linie geht es dabei um das Hinzufügen der entsprechenden Jar-Dateien in den Classpath. Der Download mit Installationsanleitung findet sich auf der offiziellen Webseite von Compass Search.

Für unser Beispiel sieht die Konfiguration in der compass.cfg.xml, die im Classpath der Applikation liegt, wie folgt aus:

<compass-core-config xmlns="http://www.compass-project.org/schema/core-config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.compass-project.org/schema/core-config
http://www.compass-project.org/schema/compass-core-config-2.2.xsd">

<compass name="default">
<connection>
<jdbc dialect="org.apache.lucene.store.jdbc.dialect.OracleDialect" managed="true">
<dataSourceProvider>
<jndi lookup="exensioExampleJndiDataSource" />
</dataSourceProvider>
</jdbc>
</connection>

<transaction factory="org.compass.core.transaction.JTASyncTransactionFactory" commitBeforeCompletion="true" lockPollInterval="200" lockTimeout="30">
</transaction>

<searchEngine>
<optimizer schedule="false" />
</searchEngine>

<mappings>
<class name="com.exensio.examples.persistence.entity.Project" />
<class name="com.exensio.examples.persistence.entity.User" />
</mappings>
</compass>

</compass-core-config>

Im ersten Abschnitt wird die Verbindung zur Datenbank definiert. In diesem Beispiel greifen wir auf eine im WebLogic Server definierte JNDI Datenquelle zurück. An dieser Stelle könnten auch direkt JDBC-Daten mit Benutzer und Passwort angegeben werden, was jedoch aus Performance-Gründen nicht zu empfehlen ist.

Eine weitere interessante Konfiguration ist das Mapping am Ende, bei welchem die Entitäten angegeben werden müssen, die Compass-Annotationen enthalten und bei der Suche betrachtet werden sollen.

Die Annotationen in den Java-Dateien sehen wie folgt aus und sind selbstklärend. Eine Übersicht über die vorhandenen Annotationen findet sich in der Dokumentation unter Kapitel 6 "OSEM - Object/Search Engine Mapping".

Aus Übersichtsgründen sind nur Teile der Entitäten angegeben.

Project.java

@Entity
@Searchable
public class Project implements IProject {
@Id
@SearchableId
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@SearchableComponent(prefix = "user_")
private User user;

@SearchableProperty(boost = 2.0f)
private String title;

@SearchableProperty
private String description;

// Getter and Setter will stay unmodified
}

User.java

@Searchable(root=false)
public class User {
@Id
@SearchableId
private Long id;

@SearchableProperty
private String firstname;

@SearchableProperty
private String lastname;

// Getter and Setter will stay unmodified
}

Wichtig ist die Annotation Searchable, bei der über die Eigenschaft root angegeben wird, welche Klasse die Wurzelentität darstellt.

 

Indizieren

Damit später auch etwas gefunden werden kann, müssen die Daten noch indiziert werden. Compass Search bietet die Möglichkeit über Hibernate Events bei jedem Schreibzugriff automatisch die neuen Werte zu indizieren bzw. alte Werte zu löschen. Die entsprechenden Listener müssen in der Compass Konfiguration angegeben werden.

Da in diesem Beispiel jedoch das programmatische Indizieren gezeigt werden soll, wird auf die automatische Indizierung nun nicht weiter eingegangen. Die nötige Konfiguration ist in dieser Dokumentation gut beschrieben.

// Load compass configuration
CompassConfiguration conf = new CompassConfiguration();
URL cfg = getClass().getClassLoader().getResource("compass.cfg.xml");
conf.configure(cfg);
compass = conf.buildCompass();

// Get a compass session
CompassSession compassSession = compass.openSession();

// Start with getting all projects
List<project> projects = exensioCompassExampleService.getAllProjects();

// Begin the compass transaction block
CompassTransaction compassTransaction = null;
try {
compassTransaction = compassSession.beginTransaction();

// Iterate through the list of projects
Project p = null;
for (int i = 0; i < projects.size(); i++) {
p = projects.get(i);

// Save annotated project to the index
compassSession.save(p);

// Commit with the specified commit interval or if at
// the end of the project list
if (((i % transactionCommitInterval) == 0) || (i == (projects.size() - 1))) {
compassTransaction.commit();
}
}

// Start optimizing
compass.getSearchEngineOptimizer().optimize();
} catch (CompassException ex) {
if (compassTransaction != null) {
compassTransaction.rollback();
logger.error("Compass Indexer: Had to rollback the Compass transaction because of a Compass exception.",ex);
} else {
logger.error("Compass Indexer: Compass exception (compass transaction is null)",ex);
}
} finally {
compassSession.close();
}

Suchen

Ähnlich einfach wie das Indizieren ist das Suchen. Der Aufruf dazu lautet:

CompassHits hits = session.find(searchString);

Anwendungsbeispiele

Mit Compass und Lucene können komplexe sowie gezielte Suchanfragen abgeschickt werden. Eine Suche nach description:"exensio GmbH integriert Compass Search" würde all jene Projekte als Resultat liefern, die in der Beschreibung den Text "exensio GmbH integriert Compass Search" haben. Nach dem Nachnamen eines Autors kann über das in der Annotation angegebene Präfix gesucht werden: user_lastname:Schmidt

Per Standardeinstellung durchsucht Compass alle Felder, so dass eine Suche nach exensio auf alle indizierten Attribute geht. In unserem Fall würde das Vorkommen von exensio im Titel eines Projekts jedoch höher bewertet werden (boost-Eigenschaft in der Annotation, siehe oben) und dadurch jene Projekte zuerst in der Ergebnismenge zurückliefern.

Fazit

Mit dem Compass Search Framework kann auf einfache Weise eine funktionsreiche und performante Suche in kleine sowie große Applikationen integriert werden. Beim Speichern des Indexes kann entweder das Dateisystem oder eine Datenbank benutzt werden, was somit den Betrieb im Cluster ermöglicht. Auch die Integration in JVM-nahe Cluster-Software wie Terracotta ist möglich.

Wenn gewünscht kann das Indizieren im Hintergrund über Hibernate Listener integriert werden, so dass es keine Verzögerung zwischen Suchresultaten und den tatsächlich vorhandenen Daten in der Applikation gibt.

Über die Einbindung von Apache Tika können nicht nur reine Texte sondern auch viele gängige Dateiformate indiziert werden. Dazu zählen beispielsweise diverse Office-Formate wie Microsoft Word, Excel und Powerpoint sowie Adobe PDF.

Kategorien: Apache Lucene

Zurück