Einführung in Morphia - Java ODM für MongoDB

1. Übersicht

In diesem Tutorial erfahren Sie, wie Sie Morphia, einen Object Document Mapper (ODM) für MongoDB in Java, verwenden.

Dabei werden wir auch verstehen, was ein ODM ist und wie es die Arbeit mit MongoDB erleichtert.

2. Was ist ein ODM ?

Für diejenigen, die in diesem Bereich noch keine Erfahrung haben, ist MongoDB eine dokumentenorientierte Datenbank, die von der Natur verteilt werden soll . Dokumentorientierte Datenbanken verwalten in einfachen Worten Dokumente, die nichts anderes als eine schemalose Methode zum Organisieren von halbstrukturierten Daten sind . Sie fallen unter ein breiteres und lose definiertes Dach von NoSQL-Datenbanken, benannt nach ihrer offensichtlichen Abkehr von der traditionellen Organisation von SQL-Datenbanken.

MongoDB bietet Treiber für fast alle gängigen Programmiersprachen wie Java . Diese Treiber bieten eine Abstraktionsebene für die Arbeit mit MongoDB, sodass wir nicht direkt mit dem Wire Protocol arbeiten. Stellen Sie sich dies als Oracle vor, das eine Implementierung des JDBC-Treibers für die relationale Datenbank bereitstellt.

Wenn wir uns jedoch an unsere Tage erinnern, in denen wir direkt mit JDBC gearbeitet haben, können wir erkennen, wie chaotisch es werden kann - insbesondere in einem objektorientierten Paradigma. Glücklicherweise haben wir ORM-Frameworks (Object Relational Mapping) wie Hibernate zu unserer Rettung. Für MongoDB ist das nicht sehr unterschiedlich.

Während wir sicherlich mit dem Low-Level-Treiber arbeiten können, erfordert es viel mehr Boilerplate, um die Aufgabe zu erfüllen. Hier haben wir ein ähnliches Konzept wie ORM namens Object Document Mapper (ODM) . Morphia füllt genau diesen Platz für die Java-Programmiersprache aus und arbeitet über dem Java-Treiber für MongoDB.

3. Einrichten von Abhängigkeiten

Wir haben genug Theorie gesehen, um uns in einen Code zu bringen. In unseren Beispielen werden wir eine Bibliothek mit Büchern modellieren und sehen, wie wir sie in MongoDB mit Morphia verwalten können.

Bevor wir beginnen, müssen wir einige der Abhängigkeiten einrichten.

3.1. MongoDB

Wir benötigen eine laufende Instanz von MongoDB, um damit arbeiten zu können. Es gibt verschiedene Möglichkeiten, dies zu erreichen. Am einfachsten ist es, die Community Edition auf unseren lokalen Computer herunterzuladen und zu installieren.

Wir sollten alle Standardkonfigurationen unverändert lassen, einschließlich des Ports, auf dem MongoDB ausgeführt wird.

3.2. Morphium

Wir können die vorgefertigten JARs für Morphia von Maven Central herunterladen und in unserem Java-Projekt verwenden.

Am einfachsten ist es jedoch, ein Abhängigkeitsmanagement-Tool wie Maven zu verwenden:

 dev.morphia.morphia core 1.5.3 

4. Wie verbinde ich mich mit Morphia?

Nachdem wir MongoDB installiert und ausgeführt und Morphia in unserem Java-Projekt eingerichtet haben, können wir über Morphia eine Verbindung zu MongoDB herstellen.

Mal sehen, wie wir das erreichen können:

Morphia morphia = new Morphia(); morphia.mapPackage("com.baeldung.morphia"); Datastore datastore = morphia.createDatastore(new MongoClient(), "library"); datastore.ensureIndexes();

Das wars so ziemlich! Lassen Sie uns das besser verstehen. Wir brauchen zwei Dinge, damit unsere Mapping-Operationen funktionieren:

  1. Ein Mapper: Dies ist für die Zuordnung unserer Java-POJOs zu MongoDB-Sammlungen verantwortlich . In unserem obigen Code-Snippet ist Morphia die dafür verantwortliche Klasse. Beachten Sie, wie wir das Paket dort konfigurieren, wo es nach unseren POJOs suchen soll.
  2. Eine Verbindung: Dies ist die Verbindung zu einer MongoDB-Datenbank, auf der der Mapper verschiedene Vorgänge ausführen kann. Die Klasse Datastore verwendet als Parameter eine Instanz von MongoClient (vom Java MongoDB-Treiber) und den Namen der MongoDB-Datenbank und gibt eine aktive Verbindung zurück, mit der gearbeitet werden kann .

Wir sind also alle bereit, diesen Datenspeicher zu verwenden und mit unseren Entitäten zu arbeiten.

5. Wie arbeite ich mit Entitäten?

Bevor wir unseren frisch geprägten Datenspeicher verwenden können , müssen wir einige Domänenentitäten definieren, mit denen gearbeitet werden soll.

5.1. Einfache Entität

Beginnen wir mit einer einfachen Definition von Buch Entität mit einigen Attributen:

@Entity("Books") public class Book { @Id private String isbn; private String title; private String author; @Property("price") private double cost; // constructors, getters, setters and hashCode, equals, toString implementations }

Hier sind einige interessante Dinge zu beachten:

  • Beachten Sie die Annotation @ Entity , die dieses POJO für die ODM-Zuordnung durch Morphia qualifiziert
  • Morphium, standardmäßig ordnet eine Einheit zu einer Sammlung in MongoDB mit dem Namen seiner Klasse, aber wir können dies ausdrücklich außer Kraft setzen (wie wir für das Unternehmen getan haben Buch hier)
  • Standardmäßig ordnet Morphia die Variablen in einer Entität den Schlüsseln in einer MongoDB-Sammlung mit dem Namen der Variablen zu, aber wir können dies wieder überschreiben (wie wir es hier für die variablen Kosten getan haben).
  • Zuletzt müssen wir eine Variable in der Entität markieren, um als Primärschlüssel durch die Annotation @ Id zu fungieren (so wie wir hier die ISBN für unser Buch verwenden).

5.2. Entitäten mit Beziehungen

In der realen Welt sind Entitäten jedoch kaum so einfach wie sie aussehen und haben komplexe Beziehungen zueinander. Zum Beispiel unserer einfachen Einheit Buch kann einen haben Publisher und andere Begleiter Bücher verweisen kann. Wie modellieren wir sie?

MongoDB bietet zwei Mechanismen zum Aufbau von Beziehungen: Referenzieren und Einbetten . Wie der Name schon sagt, speichert MongoDB beim Verweisen verwandte Daten als separates Dokument in derselben oder einer anderen Sammlung und verweist sie nur anhand ihrer ID.

Im Gegenteil, beim Einbetten speichert MongoDB die Beziehung im übergeordneten Dokument selbst.

Mal sehen, wie wir sie nutzen können. Beginnen wir mit der Einbettung von Publisher in unser Buch :

@Embedded private Publisher publisher;

Einfach genug. Lassen Sie uns nun Verweise auf andere Bücher hinzufügen:

@Reference private List companionBooks;

That's it — Morphia provides convenient annotations to model relationships as supported by MongoDB. The choice of referencing vs embedding, however, should draw from data model complexity, redundancy, and consistency amongst other considerations.

The exercise is similar to normalization in relational databases.

Now, we're ready to perform some operations on Book using Datastore.

6. Some Basic Operations

Let's see how to work with some of the basic operations using Morphia.

6.1. Save

Let's begin with the simplest of the operations, creating an instance of Book in our MongoDB database library:

Publisher publisher = new Publisher(new ObjectId(), "Awsome Publisher"); Book book = new Book("9781565927186", "Learning Java", "Tom Kirkman", 3.95, publisher); Book companionBook = new Book("9789332575103", "Java Performance Companion", "Tom Kirkman", 1.95, publisher); book.addCompanionBooks(companionBook); datastore.save(companionBook); datastore.save(book);

This is enough to let Morphia create a collection in our MongoDB database, if it does not exist, and perform an upsert operation.

6.2. Query

Let's see if we're able to query the book we just created in MongoDB:

List books = datastore.createQuery(Book.class) .field("title") .contains("Learning Java") .find() .toList(); assertEquals(1, books.size()); assertEquals(book, books.get(0));

Querying a document in Morphia begins with creating a query using Datastore and then declaratively adding filters, to the delight of those in love with functional programming!

Morphia supports much more complex query construction with filters and operators. Moreover, Morphia allows for limiting, skipping, and ordering of results in the query.

What's more, Morphia allows us to use raw queries written with the Java driver for MongoDB for more control, should that be needed.

6.3. Update

Although a save operation can handle updates if the primary key matches, Morphia provides ways to selectively update documents:

Query query = datastore.createQuery(Book.class) .field("title") .contains("Learning Java"); UpdateOperations updates = datastore.createUpdateOperations(Book.class) .inc("price", 1); datastore.update(query, updates); List books = datastore.createQuery(Book.class) .field("title") .contains("Learning Java") .find() .toList(); assertEquals(4.95, books.get(0).getCost());

Here, we're building a query and an update operation to increase by one the price of all books returned by the query.

6.4. Delete

Finally, that which has been created must be deleted! Again, with Morphia, it's quite intuitive:

Query query = datastore.createQuery(Book.class) .field("title") .contains("Learning Java"); datastore.delete(query); List books = datastore.createQuery(Book.class) .field("title") .contains("Learning Java") .find() .toList(); assertEquals(0, books.size());

We create the query quite similarly as before and run the delete operation on the Datastore.

7. Advanced Usage

MongoDB has some advanced operations like Aggregation, Indexing, and many others. While it isn't possible to perform all of that using Morphia, it's certainly possible to achieve some of that. For others, sadly, we'll have to fall back to the Java driver for MongoDB.

Let's focus on some of these advanced operations that we can perform through Morphia.

7.1. Aggregation

Aggregation in MongoDB allows us to define a series of operations in a pipeline that can operate on a set of documents and produce aggregated output.

Morphia has an API to support such an aggregation pipeline.

Let's assume we wish to aggregate our library data in such a manner that we have all the books grouped by their author:

Iterator iterator = datastore.createAggregation(Book.class) .group("author", grouping("books", push("title"))) .out(Author.class);

So, how does this work? We begin by creating an aggregation pipeline using the same old Datastore. We have to provide the entity on which we wish to perform aggregation operations, for instance, Book here.

Next, we want to group documents by “author” and aggregate their “title” under a key called “books”. Finally, we're working with an ODM here. So, we have to define an entity to collect our aggregated data — in our case, it's Author.

Of course, we have to define an entity called Author with a variable called books:

@Entity public class Author { @Id private String name; private List books; // other necessary getters and setters }

This, of course, just scratches the surface of a very powerful construct provided by MongoDB and can be explored further for details.

7.2. Projection

Projection in MongoDB allows us to select only the fields we want to fetch from documents in our queries. In case document structure is complex and heavy, this can be really useful when we need only a few fields.

Let's suppose we only need to fetch books with their title in our query:

List books = datastore.createQuery(Book.class) .field("title") .contains("Learning Java") .project("title", true) .find() .toList(); assertEquals("Learning Java", books.get(0).getTitle()); assertNull(books.get(0).getAuthor());

Here, as we can see, we only get back the title in our result and not the author and other fields. We should, however, be careful in using the projected output in saving back to MongoDB. This may result in data loss!

7.3. Indexing

Indexes play a very important role in query optimization with databases — relational as well as many non-relational ones.

MongoDB defines indexes at the level of the collection with a unique index created on the primary key by default. Moreover, MongoDB allows indexes to be created on any field or sub-field within a document. We should choose to create an index on a key depending on the query we wish to create.

For instance, in our example, we may wish to create an index on the field “title” of Book as we often end up querying on it:

@Indexes({ @Index( fields = @Field("title"), options = @IndexOptions(name = "book_title") ) }) public class Book { // ... @Property private String title; // ... }

Of course, we can pass additional indexing options to tailor the nuances of the index that gets created. Note that the field should be annotated by @Property to be used in an index.

Moreover, apart from the class-level index, Morphia has an annotation to define a field-level index as well.

7.4. Schema Validation

We've got an option to provide data validation rules for a collection that MongoDB can use while performing an update or insert operation. Morphia supports this through their APIs.

Let's say that we don't want to insert a book without a valid price. We can leverage schema validation to achieve this:

@Validation("{ price : { $gt : 0 } }") public class Book { // ... @Property("price") private double cost; // ... }

There is a rich set of validations provided by MongoDB that can be employed here.

8. Alternative MongoDB ODMs

Morphia ist nicht das einzige verfügbare MongoDB ODM für Java. Es gibt mehrere andere, die wir in unseren Anwendungen verwenden können. Eine Diskussion über den Vergleich mit Morphia ist hier nicht möglich, aber es ist immer nützlich, unsere Optionen zu kennen:

  • Spring-Daten: Bietet ein Spring-basiertes Programmiermodell für die Arbeit mit MongoDB
  • MongoJack: Bietet eine direkte Zuordnung von JSON zu MongoDB-Objekten

Dies ist keine vollständige Liste der MongoDB-ODMs für Java, aber es gibt einige interessante Alternativen!

9. Fazit

In diesem Artikel haben wir die grundlegenden Details von MongoDB und die Verwendung eines ODM zum Verbinden und Bearbeiten von MongoDB über eine Programmiersprache wie Java verstanden. Wir haben Morphia als MongoDB-ODM für Java und die verschiedenen Funktionen weiter untersucht.

Wie immer ist der Code auf GitHub zu finden.