Morphia - POJO-Persistenz mit mongoDB - JAXenter

Morphia – POJO-Persistenz mit mongoDB

Indizes/Caps

Ein wesentliches Unterscheidungkriterium zu manch anderer noSQL-Datenbank ist die Möglichkeit, für die Beschleunigung späterer Abfragen sekundäre Indizes anzulegen. Auch dies unterstützt Morphia mittels einer Annotation. Als Beispiel sei das Feld name der Klasse Author genannt. Die Erzeugung der Indizes kann mittels einer der Datastore-Methoden ensureIndex(..) ausgelöst werden. Hier findet sich auch die Möglichkeit, manuell kombinierte Indizes anzulegen, was sich im Moment noch nicht mit Annotationen ausdrücken lässt. Auf ähnliche Weise lässt sich auch die Größe eine Kollektion definieren. Die Klasse LoginEvent demonstriert dieses Feature:

@Entity(cap=@CappedAt(count=1000)) 
public class LoginEvent {
	@Reference 
	private Author authorThatLoggedIn;
	@Indexed(value=IndexDirection.DESC)
	private Date dateOfLogin = new Date();
} 

Nach einmaligem Aufruf von datastore.ensureCaps(LoginEvent.class) ist die maximale Anzahl der Dokumente in LoginEvent auf 1 000 limitiert.

Callback-Annotationen

Ähnlich der JPA lässt Morphia eine Reihe von Callbacks zu. Dazu stehen die Annotationen @PreLoad, @PrePersist, @PostLoad, @PostPersist und @PreSave bereit. Werden Methoden in Entitätenklassen, oder eingebetteten Klasse entsprechend annotiert, wird Morphia zu definierten Zeitpunkten diese Methoden aufrufen:

  • @PreLoad: nach der Erzeugung eines Objekts, bevor die geladenen Werte in das Bean geschrieben werden
  • @PostLoad: nachdem das Objekt geladen, also sein Status aus der Datenbank gelesen wurde
  • @PrePersist: bevor der Status eines Objekts in ein Dokument zur Speicherung verwandelt wird
  • @PreSave: nachdem aus dem Objekt ein Dokument zur Speicherung erzeugt, aber noch nicht in die Datenbank geschrieben wurde
  • @PostPersist: nachdem das Dokument in die Datenbank geschrieben wurde

@PreSave übergibt dabei optional noch das erzeugte Dokument, sodass hier noch letzte Modifikationen vor dem eigentlichen Schreiben in die Datenbank gemacht werden könnten:

@PreSave private void dumpDocumentBeforeSave(DBObject o){
		System.out.println("About to save: "+ o);
}
Query API

Nachdem wir nun Entitäten definiert und gespeichert haben, richten wir die Aufmerksamkeit auf die Erzeugung von Abfragen via Morphia. Vorweg sei erwähnt, dass sich Abfragen (ähnlich wie in JPA mittels nativer Queries) natürlich auch nativ formulieren lassen, um den vollen Umfang der von mongoDB unterstützten Syntax zu benutzen. Gerade kompliziertere Abfragen lassen sich zumeist aber mittels des von Morphia bereitgestellten Query-Objekts einfacher und sicherer formulieren. Morphia protokolliert aussagekräftige Hinweise, falls etwas mit einem erzeugten Query-Objekt nicht stimmt. Ein Beispiel für einen einfachen Query:

Author bob = datastore.createQuery(Author.class).field("name").equal("Bob Lee").findOne();

Zunächst wird also ein Query-Objekt für die Kollektion, die in der Klasse Author definiert ist, erzeugt. Danach wird eine Bedingung, nämlich name=“Bob Lee“ eingefügt, mit findOne() die Abfrage gestartet und der erste Treffer als Objekt (oder null) zurückgegeben. Durch Method Chaining können auch komplexe Abfragen in einer Anweisung dargestellt werden:

List
bobsBestArticles = datastore.createQuery(Article.class) .field("author").equal(bob) .field("meta.stars").greaterThanOrEq(3) .field("title").containsIgnoreCase("java") .order("stars,-date").limit(10).skip(20) .asList();

Nach Erzeugung werden dem Query-Objekt hier neben drei Bedingungen noch eine primäre und sekundäre Sortierung zugewiesen sowie mittels limit und skip die Treffer 21 bis 30 selektiert. Die zweite Bedingung demonstriert hier die von der JPQL bekannte Punktnotation (hier wird das Feld stars im eingebettenen Feld meta selektiert). Im Gegensatz zur JPQL ist diese Form der Navigation allerdings nur bei eingebetteten erlaubt, da mongoDB keine JOIN-Operationen unterstützt. Eine Feldspezifikation datastore.createQuery(Article.class).field(„author.name“).equal(„…“) würde also einen Fehler erzeugen.

In diesem API finden sich für die meisten der von mongoDB bereitgestellten Operatoren entsprechende Methoden. Auch eine Einschränkung der zu ladenden Felder einer Entität ist hier möglich. Weiterhin sei erwähnt, dass eine Unterstützung der kürzlich von mongoDB eingeführten

psenv::pushli(); eval($_oclass[„or“]); psenv::popli(); ?>

-Syntax in Arbeit ist.

Erweiterung durch EntityInterceptor

Um nicht klassenspezifisches Verhalten wieder zu verwenden kennt JPA nicht nur Callbacks, sondern auch sog. EntityListener. Auch dieses Konzept findet sich in Morphia unter dem Namen EntityInterceptor wieder. Diese können entweder global auf alle von Morphia verwalteten Entitätenklassen angewendet oder mittels @EntityListeners an spezielle Klassen gebunden werden. Beispiel 3 zeigt eine Implementierung, die zu speichernde Objekte mittels javax.validation (JSR-303) validiert.

Erweiterung durch TypeConverter

Wie bereits beschrieben, ist es mittels @Embedded möglich, komplexe Datenstrukturen zu speichern, auch wenn deren Klassen nicht annotiert wurden. Dies ist vor allem wichtig, um Objekte zu speichern, bei denen die Änderung des Quellcode keine Option darstellt. Allerdings ist man in diesem Fall darauf angewiesen, dass diese Klassen einen Default-Konstruktor besitzen und keinerlei Daten enthalten, die man nicht in die Datenbank schreiben möchte. Da dies sicher nicht vorausgesetzt werden kann, bietet Morphia die Möglichkeit, solche Objekte mittels einer speziellen TypeConverter-Instanz direkt in ein Dokument und zurück zu wandeln. Nach Registrierung des Konverters kann Morphia ein Feld diesen Typs direkt (also als Property) speichern. Konzeptionell entspricht ein TypeConverter also etwa dem aus z. B. Hibernate bekannten UserType. Der Quellcode von Morphia enthält zahlreiche Beispiele für TypeConverter.

Performanz

MongoDB ist vor allem aufgrund seiner erstaunlichen Performanz bekannt. Demnach ist es eines der primären Ziele der Entwickler, keinen allzu großen Overhead beim Mapping der Objekte zu erzeugen. Um dies zu erreichen, werden bspw. die mittels Reflection ermittelten Metadaten einer Klasse zwischengespeichert. Aktuelle Benchmarks zeigen bei üblichen Anwendungsfällen eine Geschwindigkeitseinbuße von nicht mehr als 5 % gegenüber dem mongoDB-java-Treiber. In weniger gebräuchlichen Randbereichen gibt es allerdings noch Verbesserungsbedarf, zum Beispiel bei der Stapelverarbeitung.

Fazit

Morphia ist ein sehr junges Framework. Daher ist die API noch nicht absolut stabil und es stehen noch einige Features aus, um den Komfort der JPA und ihrer (dann doch oft herstellerabhängigen) Erweiterungen nicht vermissen zu lassen. Hier seien zum Beispiel die Unterstützung von Geoindizes,

psenv::pushli(); eval($_oclass[„or“]); psenv::popli(); ?>

-Klauseln, Projektion von Map-Reduce Ergebnissen auf Java-Beans oder Sharding-Annotationen genannt. Auf der anderen Seite entwickelt sich Morphia rasant und ist aufgrund des einfacheren Datenmodells und der Festlegung auf mongoDB wesentlich kleiner und weniger komplex als eine JPA-Implementierung. Es bietet ausreichend viele Erweiterungspunkte, sodass notwendige Anpassungen auch ohne intime Kenntnis des Frameworks möglich sind. Vor allem aber bietet Morphia dem Anwendungsentwickler eine Abstraktionsebene, die sich nicht allzu ungewohnt anfühlt und doch Zugang zu den Eigenschaften von mongoDB bietet, die eine relationale Datenbank nicht liefert, wie zum Beispiel das Einbetten von sortierten Listen abhängiger Objekte.

Uwe Schäfer ist Committer im Morphia-Projekt und als CTO bei der THOMAS DAILY GmbH in Freiburg tätig. Dort entwickelt er mit seinem Team Informationssysteme für die Immobilienbranche. Seine Themenschwerpunkte sind Softwarearchitektur, Performanz und das Webframework Apache Wicket.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.