Pengenalan Morphia - ODM Java untuk MongoDB

1. Gambaran keseluruhan

Dalam tutorial ini, kita akan memahami cara menggunakan Morphia, Object Document Mapper (ODM) untuk MongoDB di Java.

Dalam prosesnya, kami juga akan memahami apa itu ODM dan bagaimana ia memudahkan bekerja dengan MongoDB.

2. Apa itu ODM ?

Bagi mereka yang belum berpengalaman di kawasan ini, MongoDB adalah pangkalan data berorientasikan dokumen yang dibina untuk diedarkan secara semula jadi . Pangkalan data berorientasikan dokumen, dalam istilah mudah, menguruskan dokumen, yang tidak lain hanyalah cara tanpa skema untuk mengatur data separa berstruktur . Mereka berada di bawah payung pangkalan data NoSQL yang lebih luas dan didefinisikan secara longgar, yang dinamakan sempena pemergian mereka yang jelas dari organisasi tradisional pangkalan data SQL.

MongoDB menyediakan pemacu untuk hampir semua bahasa pengaturcaraan yang popular seperti Java . Pemacu ini menawarkan lapisan abstraksi untuk bekerja dengan MongoDB sehingga kami tidak bekerja dengan Wire Protocol secara langsung. Anggap ini sebagai Oracle yang menyediakan pelaksanaan pemacu JDBC untuk pangkalan data hubungan mereka.

Namun, jika kita mengingati hari-hari kita bekerja dengan JDBC secara langsung, kita dapat menghargai betapa berantakannya - terutama dalam paradigma berorientasikan objek. Nasib baik, kami mempunyai kerangka Object Relational Mapping (ORM) seperti Hibernate untuk menyelamatkan kami. Tidak begitu berbeza untuk MongoDB.

Walaupun kita pasti dapat bekerjasama dengan pemandu tahap rendah, ia memerlukan plat pemanas yang lebih banyak untuk menyelesaikan tugas tersebut. Di sini, kami mempunyai konsep yang serupa dengan ORM yang disebut Object Document Mapper (ODM) . Morphia benar-benar mengisi ruang itu untuk bahasa pengaturcaraan Java dan berfungsi di atas pemacu Java untuk MongoDB.

3. Menetapkan Tanggungan

Kami telah melihat teori yang cukup untuk memasukkan kami ke dalam beberapa kod. Sebagai contoh, kami akan memodelkan perpustakaan buku dan melihat bagaimana kami dapat menguruskannya di MongoDB menggunakan Morphia.

Tetapi sebelum kita memulakan, kita perlu mengatur beberapa pergantungan.

3.1. MongoDB

Kita perlu mempunyai contoh MongoDB yang sedang berjalan. Terdapat beberapa cara untuk mendapatkannya, dan yang paling mudah ialah memuat turun dan memasang edisi komuniti di mesin tempatan kami.

Kita harus meninggalkan semua konfigurasi lalai sebagaimana adanya, termasuk port tempat MongoDB dijalankan.

3.2. Morphia

Kami boleh memuat turun JAR pra-binaan untuk Morphia dari Maven Central dan menggunakannya dalam projek Java kami.

Walau bagaimanapun, cara paling mudah adalah menggunakan alat pengurusan pergantungan seperti Maven:

 dev.morphia.morphia core 1.5.3 

4. Bagaimana Menghubungkan Menggunakan Morphia?

Sekarang kita telah memasang dan menjalankan MongoDB dan menyiapkan Morphia dalam projek Java kita, kita siap untuk menyambung ke MongoDB menggunakan Morphia.

Mari lihat bagaimana kita dapat mencapainya:

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

Itu cukup banyak! Mari kita fahami ini dengan lebih baik. Kami memerlukan dua perkara agar operasi pemetaan kami dapat dijalankan:

  1. A Mapper: Ini bertanggungjawab untuk memetakan POJO Java kami ke Koleksi MongoDB . Dalam coretan kod kami di atas, Morphia adalah kelas yang bertanggungjawab untuk itu. Perhatikan bagaimana kami mengkonfigurasi pakej di mana ia harus mencari POJO kami.
  2. Sambungan: Ini adalah sambungan ke pangkalan data MongoDB di mana pemeta dapat menjalankan operasi yang berbeza. Datastore kelas mengambil sebagai parameter contoh MongoClient (dari pemacu Java MongoDB) dan nama pangkalan data MongoDB, mengembalikan sambungan aktif untuk digunakan .

Oleh itu, kami sudah bersedia untuk menggunakan Datastore ini dan bekerjasama dengan entiti kami.

5. Bagaimana Bekerja dengan Entiti?

Sebelum kita dapat menggunakan Datastore yang baru dicetak , kita perlu menentukan beberapa entiti domain untuk digunakan.

5.1. Entiti Sederhana

Mari mulakan dengan menentukan entiti Buku ringkas dengan beberapa atribut:

@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 }

Terdapat beberapa perkara menarik untuk diperhatikan di sini:

  • Perhatikan anotasi @ Entiti yang memenuhi syarat POJO ini untuk pemetaan ODM oleh Morphia
  • Secara lalai, Morphia memetakan entiti ke koleksi di MongoDB dengan nama kelasnya, tetapi kami secara jelas dapat mengatasinya (seperti yang telah kami lakukan untuk entiti. Pesan di sini)
  • Secara default, Morphia memetakan pemboleh ubah dalam entiti ke kunci dalam koleksi MongoDB dengan nama pemboleh ubah, tetapi sekali lagi kita dapat mengatasi ini (seperti yang telah kita lakukan untuk kos berubah di sini)
  • Terakhir, kita perlu menandakan pemboleh ubah dalam entiti untuk bertindak sebagai kunci utama oleh anotasi @ Id (seperti kita menggunakan ISBN untuk buku kita di sini)

5.2. Entiti dengan Hubungan

Namun, di dunia nyata, entiti hampir tidak sesederhana rupa dan mempunyai hubungan yang kompleks antara satu sama lain. Sebagai contoh, Buku entiti ringkas kita boleh mempunyai Penerbit dan dapat merujuk buku pendamping yang lain. Bagaimana kita memperagakannya?

MongoDB menawarkan dua mekanisme untuk membina hubungan - Merujuk dan Menyematkan . Seperti namanya, dengan merujuk, MongoDB menyimpan data yang berkaitan sebagai dokumen terpisah dalam koleksi yang sama atau berbeza dan hanya merujuknya menggunakan idnya.

Sebaliknya, dengan penyematan, MongoDB menyimpan atau lebih tepatnya menanamkan hubungan dalam dokumen induk itu sendiri.

Mari lihat bagaimana kita boleh menggunakannya. Mari mulakan dengan memasukkan Penerbit dalam Buku kami :

@Embedded private Publisher publisher;

Cukup sederhana. Sekarang mari kita teruskan dan tambahkan rujukan ke buku lain:

@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 bukan satu-satunya ODM MongoDB yang tersedia untuk Java. Terdapat beberapa yang lain yang boleh kita pertimbangkan untuk digunakan dalam aplikasi kita. Perbincangan mengenai perbandingan dengan Morphia tidak mungkin dilakukan di sini, tetapi selalu berguna untuk mengetahui pilihan kami:

  • Spring Data: Menyediakan model pengaturcaraan berasaskan Spring untuk bekerjasama dengan MongoDB
  • MongoJack: Menyediakan pemetaan langsung dari JSON ke objek MongoDB

Ini bukan senarai lengkap ODM MongoDB untuk Java, tetapi ada beberapa alternatif menarik yang tersedia!

9. Kesimpulannya

Dalam artikel ini, kami memahami perincian asas MongoDB dan penggunaan ODM untuk menyambung dan beroperasi pada MongoDB dari bahasa pengaturcaraan seperti Java. Kami seterusnya menerokai Morphia sebagai MongoDB ODM untuk Java dan pelbagai kemampuan yang dimilikinya.

Seperti biasa, kodnya boleh didapati di GitHub.