Въведение в Morphia - Java ODM за MongoDB

1. Общ преглед

В този урок ще разберем как да използваме Morphia, картограф на обектни документи (ODM) за MongoDB в Java.

В процеса ще разберем и какво представлява ODM и как улеснява работата с MongoDB.

2. Какво е ODM ?

За непосветените в тази област MongoDB е ориентирана към документи база данни, създадена да се разпространява от природата . Базите данни, ориентирани към документи, с прости думи управляват документи, които не са нищо друго освен схемен начин за организиране на полуструктурирани данни . Те попадат под по-широк и слабо дефиниран чадър на базите данни NoSQL, наречени след очевидното им отклонение от традиционната организация на SQL бази данни.

MongoDB предоставя драйвери за почти всички популярни езици за програмиране като Java . Тези драйвери предлагат слой абстракция за работа с MongoDB, така че да не работим директно с Wire Protocol. Помислете за това като за Oracle, предоставящ реализация на JDBC драйвер за тяхната релационна база данни.

Ако обаче си припомним дните си, работещи директно с JDBC, можем да оценим колко объркано може да стане - особено в обектно-ориентирана парадигма. За щастие имаме рамки за обектно релационно картографиране (ORM) като Hibernate за наше спасяване. Не е много различно за MongoDB.

Въпреки че със сигурност можем да работим с драйвера за ниско ниво, той изисква много повече образци за изпълнение на задачата. Тук имаме подобна концепция на ORM, наречена Object Document Mapper (ODM) . Morphia точно запълва това пространство за езика за програмиране Java и работи върху Java драйвера за MongoDB.

3. Настройване на зависимости

Видяхме достатъчно теория, за да влезем в някакъв код. За нашите примери ще моделираме библиотека от книги и ще видим как можем да я управляваме в MongoDB, използвайки Morphia.

Но преди да започнем, ще трябва да настроим някои от зависимостите.

3.1. MongoDB

Трябва да имаме работещ екземпляр на MongoDB, с който да работим. Има няколко начина да получите това, а най-простият е да изтеглите и инсталирате издание на общността на нашата локална машина.

Трябва да оставим всички конфигурации по подразбиране такива, каквито са, включително порта, на който работи MongoDB.

3.2. Морфия

Можем да изтеглим предварително изградените JAR за Morphia от Maven Central и да ги използваме в нашия Java проект.

Най-простият начин обаче е да се използва инструмент за управление на зависимости като Maven:

 dev.morphia.morphia core 1.5.3 

4. Как да се свържете с помощта на Morphia?

Сега, след като инсталирахме и стартирахме MongoDB и настроихме Morphia в нашия Java проект, ние сме готови да се свържем с MongoDB с помощта на Morphia.

Нека да видим как можем да постигнем това:

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

Това е почти всичко! Нека разберем това по-добре. Имаме нужда от две неща, за да работят нашите картографски операции:

  1. Картограф : Това е отговорно за картографирането на нашите Java POJO към колекциите MongoDB . В нашия кодов фрагмент по-горе Morphia е класът, отговорен за това. Обърнете внимание как конфигурираме пакета там, където той трябва да търси нашите POJO.
  2. Връзка: Това е връзката с база данни MongoDB, върху която картографът може да изпълнява различни операции. Класът Datastore приема като параметър екземпляр на MongoClient (от драйвера Java MongoDB) и името на базата данни MongoDB, връщайки активна връзка за работа .

И така, всички сме готови да използваме това хранилище на данни и да работим с нашите обекти.

5. Как да работя с обекти?

Преди да можем да използваме нашия прясно кован Datastore , трябва да дефинираме някои обекти на домейна, с които да работим.

5.1. Прост субект

Нека започнем с дефиниране на прост обект на Книга с някои атрибути:

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

Тук има няколко интересни неща, които трябва да се отбележат:

  • Забележете анотацията @ Entity, която отговаря на това POJO за ODM картографиране от Morphia
  • Morphia по подразбиране картографира обект в колекция в MongoDB от името на неговия клас, но можем изрично да заменим това (както направихме за обекта Book тук)
  • Morphia по подразбиране съпоставя променливите в обект с ключовете в колекция MongoDB по името на променливата, но отново можем да заменим това (както направихме за цената на променливата тук)
  • И накрая, трябва да маркираме променлива в обекта, която да действа като първичен ключ от анотацията @ Id (като тук използваме ISBN за нашата книга)

5.2. Субекти с връзки

В реалния свят обаче съществата едва ли са толкова прости, колкото изглеждат и имат сложни взаимоотношения помежду си. Например, нашата проста книга „Книга“ може да има издател и може да препраща към други придружаващи книги. Как ги моделираме?

MongoDB предлага два механизма за изграждане на взаимоотношения - препращане и вграждане . Както подсказва името, при позоваване MongoDB съхранява свързани данни като отделен документ в същата или различна колекция и просто ги препраща, използвайки своя идентификатор.

Напротив, с вграждането, MongoDB съхранява или по-скоро вгражда връзката в самия родителски документ.

Нека да видим как можем да ги използваме. Нека започнем с вграждането на Publisher в нашата книга :

@Embedded private Publisher publisher;

Достатъчно просто. Сега нека да добавим препратки към други книги:

@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 не е единственият наличен MongoDB ODM за Java. Има няколко други, които можем да обмислим да използваме в нашите приложения. Тук не е възможна дискусия за сравнение с Morphia, но винаги е полезно да знаете нашите възможности:

  • Spring Data: Осигурява базиран на Spring модел за програмиране за работа с MongoDB
  • MongoJack: Осигурява директно картографиране от JSON към MongoDB обекти

Това не е пълен списък на MongoDB ODM за Java, но има няколко интересни алтернативи!

9. Заключение

В тази статия разбрахме основните подробности за MongoDB и използването на ODM за свързване и работа с MongoDB от програмен език като Java. По-нататък изследвахме Morphia като MongoDB ODM за Java и различните възможности, които има.

Както винаги, кодът може да бъде намерен в GitHub.