Von Göttern und Graphen

Die Grundlagen – Chaos formt sich

Dieses Tutorial zeigt, wie mit eXtended Objects (XO) Daten in der Graphdatenbank Neo4j ablegt werden können.

XO, Java und Neo4j – Gottheiten, die sich verstehen

Neo4j ist eine Graphdatenbank. Anders als bei relationalen Datenbanken werden hier die Daten als Knoten und Kanten abgelegt. Knoten besitzen Labels, weiterhin haben sie Eigenschaften als Name-Wert-Paar und stehen mit anderen Knoten über Kanten in Beziehung. Labels markieren und kategorisieren einen Knoten. Beziehungen zwischen Knoten besitzen eine Richtung. Beziehungen können ebenfalls Eigenschaften besitzen.

Grafik_TutorialXO_Neo4jSymbole
Abbildung 1 Bezeichnung Neo4j Bestandteile

eXtended Objects (XO) ist ein Framework, um Daten von Java in Neo4j zu speichern. XO bietet ein Mapping zwischen Konzepten der Programmiersprache Java und Konzepten der Graphdatenbank. Knoten in der Datenbank werden auf XO-Entitäten abgebildet. Eine solche Entität ist kein Java-Bean im herkömmlichen Sinn, sondern eine von eXtendend Objects verwaltete Instanz eines Datentyps. Die Datentypen werden als Interfaces modelliert und tragen XO-spezifische Annotationen. Kanten werden auf ein- oder mehrwertige Properties abgebildet. Eigenschaften von Knoten werden auch als Properties abgebildet, allerdings haben diese einen primitiven Datentyp oder Sting als Typ. Properties werden durch Getter und Setter definiert.

Grafik_TutorialXO_Mapping
Abbildung 2 Mapping Java – XO – Neo4j

Beispieleinführung – Olymp trifft Erde

Zur Veranschaulichung der Funktionsweise von XO soll ein Ausschnitt aus der griechischen Mythologie dienen. Hier gibt es Götter und Menschen. Wenn Götter und Menschen eine Verbindung eingehen, entstehen Mischwesen, so genannte Halbgötter. Halbgötter besitzen sowohl Eigenschaften von Menschen als auch von Göttern. In diesem Beipiel werden Knoten erzeugt, die Menschen, Götter und Halbgötter repräsentieren. Diese Knoten werden mit „Mensch“, „Gott“ und „Halbgott“ gelabelt.

Zwischen den vorhandenen Personen gibt es verschiedene Beziehungen. Zwischen einem Gott und Mensch ist dies „HAT_AFFÄRE_MIT“. Aus so einer Verbindung kann ein Kind entstehen. Dieses hat zwei Eltern, mit den Beziehungen „KIND_VON“ zum irdischen und „SPROSS_VON“ zum göttlichen Elternteil. Die unterschiedlichen Bezeichnungen sind nötig, da es nicht möglich ist, zwei Beziehungen mit demselben Namen von einem Knoten ausgehen zu lassen. Als minimalistischer Stammbaum für das Beispiel sei Abbildung 3 gegeben.

Grafik_TutorialXO_Stammbaum
Abbildung 3 Stammbaum von Hercules

Interface-Erstellung – Hephaistos baut Paläste

Das Interface „Gott“, wird genutzt um Knoten mit dem Label „Gott“ zu erzeugen. Hierzu ist es notwendig, dass das Interface mit der Annotation „@Label“ versehen wird. Eigenschaften der Götter werden über Getter und Setter definiert.

@Label("Gott")
public interface Gott {
	@Indexed
	String getName();
	void setName(String name);
	String getTitel();
	void setTitel(String titel);
}

Die Beziehung zwischen Menschen und Göttern wird auch mittels Getter und Setter abgebildet. Da die Mythologie von mehreren solcher Vorkommnisse berichtet, bietet sich die Modellierung als Liste an.

Zunächst das Interface „Mensch“:

@Label("Mensch")
public interface Mensch {
	@Indexed
	String getName();
	void setName(String name);
}

Für die Beziehung zwischen Menschen und Göttern wird das Interface „Gott“ wie folgt erweitert:

@Relation("HAT_AFFÄRE_MIT")
List<Mensch> getAffaere();
void setAffaere(List<Mensch> partner);

Die Property „affären“ trägt die Annotation „@Relation(„HAT_AFFÄRE_MIT“)“ um die gewünschte Benennung in der Datenbank zu erzielen. Die Richtung der Beziehung ist standardmäßig „Outgoing“.

Das letzte Interface nennt sich „Halbgott“. Jetzt zeigt sich der Vorteil, die Entitäten als Interface abgebildet zu haben, denn so ist selbst in Java Mehrfachvererbung möglich. Ein Halbgott erweitert die Interfaces „Mensch“ und „Gott“. Aus diesem Grund wird später ein Knoten, welcher mit Hilfe dieses Interfaces erzeugt wurde, sowohl das Label „Gott“ als auch „Mensch“ zusätzlich tragen. Dazu erbt es die Property „Name“. Am Getter befinden sich Annotationen, welche die Beziehungen verändern können. @Outgoing bestimmt die Richtung der Relation.

@Label(value = "Halbgott", usingIndexedPropertyOf = Gott.class)
public interface Halbgott extends Gott, Mensch {
	String getWaffe();

	void setWaffe(String waffe);

	@Relation("SPROSS_VON")
	@Outgoing
	Gott getGottVorfahre();

	void setGottVorfahre(Gott elternGott);

	@Relation("KIND_VON")
	@Outgoing
	Mensch getMenschVorfahre();

	void setMenschVorfahre(Mensch elternMensch);
}

Übermittlung an die Datenbank – Hermes der XO-Bote

Um eine Verbindung zur Datenbank herstellen zu können, wird eine XOManagerFactory gebraucht. Mit dieser kann man XOManager erzeugen. Diese XOManager dienen dazu, CRUD Opertionen auszuführen. Wichtig bei diesen Operation ist, sich in einer Transaktionen zu befinden. Diese wird – wie im Codebeispiel zu sehen ist – gestartet und mit einem Commit beendet.

XOManagerFactory xmf = XO.createXOManagerFactory("Stammbaum");
XOManager xm = xmf.createXOManager();
xm.currentTransaction().begin();

//Einfügen des Transaktionsinhalts

xm.currentTransaction().commit();
xm.close();
xmf.close();

Als Operationen stehen die bekannten CRUD Operationen zur Verfügung. In einer Create-Operation wird ein neuer Knoten erstellt.

Gott gott = xm.create(Gott.class);

Für das Auslesen eines Knotens aus der Datenbank gibt es zwei Wege. Der Erste Weg führt über eine Cypher-Query. Cypher ist die Datenbanksprache von Neo4j, wenn sie nicht bekannt ist gibt es hier eine Einführung.

xm.createQuery("match (god:God) where god.name={name} return god")
  .withParameter("name", "Gottesname")
  .execute();

Als zweite Möglichkeit stellt XO die find() Methode zu Verfügung. Wurde eine Property in einem Interface mit @Indexed annotiert, so ist es möglich danach zu suchen. Achtung: @Indexed bedeutet nicht, dass ein Wert für das annotierte Property nur einmal vorkommen darf. Es definiert keinen Unique-Constraint.

xm.find(Gott.class, "Gottesname");

Für das Update eines Knotens reicht es, die Setter an der Enität aufzurufen. XO überträgt alle Änderungen automatisch zur Datenbank.

updateGott.setTitel("Herrscher des Olymp");

Löschen ist nicht ohne Einschränkungen in XO möglich, da Knoten in Neo4j nur gelöscht werden können, wenn sie keine Beziehung zu anderen Knoten besitzen. Es gibt eine delete-Methode, um Instanzen zu löschen.

xm.delete(instance);

Dieser Vorgang ist mit einer Cypherquery besser, da in dieser Relationen mit gelöscht werden können.

xm.createQuery("MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r")
  .execute();

Praktische Anwendung – Wenn Götter handeln

Um das Arbeiten auf der Datenbank mit XO zu üben wird das bereits vorgestellte Beispiel der grieschichen Mythologie genutzt.

Projekt Setup – Athene plant

Zuerst wird ein MavenProject mit diesen Dependencies erstellt:

* org.neo4j:neo4j:3.2.3
* com.buschmais.xo:xo.neo4j.remote:0.8.1
* com.buschmais.xo:xo.api:0.8.1
* com.buschmais.xo:xo.impl:0.8.1:runtime

Zum Ausführen wird weiterhin mindestens Java 8 und zusätzlich eine lokale Installation von Neo4j benötigt.

Um die oben modellierten Interfaces nutzen zu können, müssen diese im Descriptor „xo.xml“ angegeben werden. Die „xo.xml“ Datei muss sich im Ordner „META-INF“ befinden. Des Weiteren werden Nutzername, Passwort und die URL zur Datenbank angegeben. Der Bezug zwischen der Datenbank und der XOManagerFactory wird via des XO-Unit-Namens hergestellt.

<v1:xo version="1.0" xmlns:v1="http://buschmais.com/xo/schema/v1.0">
	<xo-unit name="Stammbaum">
		<url>bolt://127.0.0.1</url>
		<provider>
			com.buschmais.xo.neo4j.remote.api.RemoteNeo4jXOProvider
		</provider>
		<types>
			<!--alle Interfaces mit Label eintragen -->
			<type>de.buschmais.greekgods.Gott</type>
			<type>de.buschmais.greekgods.Mensch</type>
			<type>de.buschmais.greekgods.Halbgott</type>
		</types>
		<properties>
			<property name="neo4j.remote.username" value="neo4j" />
			<property name="neo4j.remote.password" value="safe" />
		</properties>
	</xo-unit>
</v1:xo>

Implementierung – Ares zieht in die Schlacht

Um in der Datenbank die Knoten anzulegen, werden alle Götter, Menschen und Halbgötter wie beschrieben mit „create“ erzeugt (siehe Abbildung 1). Um Beziehungen zwischen den erstellten Personen zu erzeugen, werden Updates durchgeführt. Soll ein Knoten vom Typ „Halbgott“ mit einer Cypher-Query abgerufen werden, kann auch nach einem Knoten mit den Labels „Halbgott“, „Mensch“ und „Gott“ gesucht werden.

xm.createQuery("match (gott:Gott) where gott.name={name} return gott")
  .withParameter("name", "Hercules")
  .execute();

Der Grund liegt an dem Fakt, dass ein Halbgott beide Labels trägt, da das Interface „Halbgott“ „Mensch“ und „Gott“ erweitert. Die find() Methode verhält sich eben so, jedoch muss der Rückgabetyp angepasst werden.

Wenn das Tutorial erfolgreich umgesetzt wurde, dann sollte die Abbildung in Neo4j der von Abbildung 4 ähneln. Der vollständige Quelltext kann in Kürze auf GitHub nachgeschaut werden.
Grafik_TutorialXO_Halbgott_Neo
Abbildung 4 Beispiel in Neo4j dargestellt

Über den Autor

Jonas Witzgall ist dualer Student der Wirtschaftsinformatik an der BA Dresden. In den Praxisphasen wird er von der buschmais GbR zum Java-Entwickler ausgebildet. In seiner Freizeit sucht er die Balance aus Körper, Geist und Technik.

Kommentare sind abgeschaltet.