Перейти к основному содержимому

Введение в Morphia — Java ODM для MongoDB

· 10 мин. чтения

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:

<dependency>
<groupId>dev.morphia.morphia</groupId>
<artifactId>core</artifactId>
<version>1.5.3</version>
</dependency>

4. Как подключиться с помощью Morphia?

Теперь, когда мы установили и запустили MongoDB и настроили Morphia в нашем проекте Java, мы готовы подключиться к MongoDB с помощью Morphia.

Давайте посмотрим, как мы можем это сделать:

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

Вот и все! Давайте лучше разберемся в этом. Нам нужны две вещи, чтобы наши операции отображения работали:

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

Итак, мы готовы использовать это хранилище данных и работать с нашими сущностями.

5. Как работать с сущностями?

Прежде чем мы сможем использовать наше только что созданное хранилище данных , нам нужно определить некоторые объекты предметной области, с которыми мы будем работать.

5.1. Простой объект

Начнем с определения простой сущности Book с некоторыми атрибутами:

@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<Book> companionBooks;

Вот и все — Morphia предоставляет удобные аннотации для моделей отношений, поддерживаемых MongoDB. Однако выбор ссылки или встраивания должен основываться на сложности модели данных, избыточности и согласованности среди других соображений.

Упражнение похоже на нормализацию в реляционных базах данных.

Теперь мы готовы выполнить некоторые операции с Book с помощью Datastore .

6. Некоторые основные операции

Давайте посмотрим, как работать с некоторыми основными операциями с помощью Morphia.

6.1. Сохранять

Начнем с самой простой из операций, создав экземпляр Book в нашей библиотеке базы данных MongoDB :

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);

Этого достаточно, чтобы позволить Morphia создать коллекцию в нашей базе данных MongoDB, если она не существует, и выполнить операцию upsert.

6.2. Запрос

Давайте посмотрим, сможем ли мы запросить книгу, которую мы только что создали в MongoDB:

List<Book> books = datastore.createQuery(Book.class)
.field("title")
.contains("Learning Java")
.find()
.toList();

assertEquals(1, books.size());

assertEquals(book, books.get(0));

Запрос документа в Morphia начинается с создания запроса с использованием хранилища данных , а затем декларативного добавления фильтров, к радости тех, кто любит функциональное программирование!

Morphia поддерживает гораздо более сложные построения запросов с фильтрами и операторами . Более того, Morphia позволяет ограничивать, пропускать и упорядочивать результаты в запросе.

Более того, Morphia позволяет нам использовать необработанные запросы, написанные с помощью драйвера Java для MongoDB, для большего контроля, если это необходимо.

6.3. Обновлять

Хотя операция сохранения может обрабатывать обновления, если первичный ключ совпадает, Morphia предоставляет способы выборочного обновления документов:

Query<Book> query = datastore.createQuery(Book.class)
.field("title")
.contains("Learning Java");

UpdateOperations<Book> updates = datastore.createUpdateOperations(Book.class)
.inc("price", 1);

datastore.update(query, updates);

List<Book> books = datastore.createQuery(Book.class)
.field("title")
.contains("Learning Java")
.find()
.toList();

assertEquals(4.95, books.get(0).getCost());

Здесь мы создаем запрос и операцию обновления для увеличения на единицу цены всех книг, возвращенных запросом.

6.4. Удалить

Наконец, то, что было создано, должно быть удалено! Опять же, с Morphia все интуитивно понятно:

Query<Book> query = datastore.createQuery(Book.class)
.field("title")
.contains("Learning Java");

datastore.delete(query);

List<Book> books = datastore.createQuery(Book.class)
.field("title")
.contains("Learning Java")
.find()
.toList();

assertEquals(0, books.size());

Мы создаем запрос точно так же, как и раньше, и запускаем операцию удаления в хранилище данных .

7. Расширенное использование

MongoDB имеет некоторые расширенные операции, такие как агрегирование, индексирование и многие другие . Хотя невозможно выполнить все это с помощью Morphia, кое-что из этого, безусловно, возможно. Для других, к сожалению, нам придется вернуться к драйверу Java для MongoDB.

Давайте сосредоточимся на некоторых из этих сложных операций, которые мы можем выполнять с помощью Morphia.

7.1. Агрегация

Агрегация в MongoDB позволяет нам определить серию операций в конвейере, которые могут работать с набором документов и производить агрегированные выходные данные .

У Morphia есть API для поддержки такого конвейера агрегации.

Предположим, мы хотим агрегировать данные нашей библиотеки таким образом, чтобы все книги были сгруппированы по их авторам:

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

Так как же это работает? Начнем с создания конвейера агрегации с использованием того же старого хранилища данных . Мы должны предоставить объект, для которого мы хотим выполнять операции агрегирования, например, Забронировать здесь.

Затем мы хотим сгруппировать документы по «автору» и объединить их «название» под ключом «книги». Наконец, здесь мы работаем с ODM. Итак, мы должны определить объект для сбора наших агрегированных данных — в нашем случае это Author .

Конечно, мы должны определить сущность с именем Author с переменной с именем books:

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

Это, конечно, лишь малая часть очень мощной конструкции, предоставляемой MongoDB, и ее можно изучить подробнее .

7.2. Проекция

Проекция в MongoDB позволяет нам выбирать только те поля, которые мы хотим получить из документов в наших запросах . Если структура документа сложная и тяжелая, это может быть очень полезно, когда нам нужно всего несколько полей.

Предположим, нам нужно получить только книги с их названием в нашем запросе:

List<Book> 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());

Здесь, как мы видим, мы получаем только заголовок в нашем результате, а не автора и другие поля. Однако мы должны быть осторожны при использовании прогнозируемого вывода при сохранении обратно в MongoDB. Это может привести к потере данных!

7.3. Индексация

Индексы играют очень важную роль в оптимизации запросов к базам данных — как реляционным, так и многим нереляционным.

MongoDB определяет индексы на уровне коллекции с уникальным индексом, созданным по первичному ключу по умолчанию . Более того, MongoDB позволяет создавать индексы для любого поля или подполя в документе. Мы должны выбрать создание индекса по ключу в зависимости от запроса, который мы хотим создать.

Например, в нашем примере мы можем захотеть создать индекс для поля «название» книги , так как мы часто заканчиваем запрос к нему:

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

Конечно, мы можем передать дополнительные параметры индексации, чтобы адаптировать нюансы создаваемого индекса. Обратите внимание, что поле должно быть аннотировано @Property для использования в индексе.

Более того, помимо индекса на уровне класса, Morphia также имеет аннотацию для определения индекса на уровне поля.

7.4. Проверка схемы

У нас есть возможность предоставить правила проверки данных для коллекции, которые MongoDB может использовать при выполнении операции обновления или вставки . Morphia поддерживает это через свои API.

Допустим, мы не хотим вставлять книгу без действительной цены. Для этого мы можем использовать проверку схемы:

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

MongoDB предоставляет широкий набор проверок , которые можно использовать здесь.

8. Альтернативные ODM MongoDB

Morphia — не единственный доступный ODM MongoDB для Java. Есть несколько других, которые мы можем рассмотреть для использования в наших приложениях. Обсуждение сравнения с Morphia здесь невозможно, но всегда полезно знать наши варианты:

  • Spring Data : предоставляет модель программирования на основе Spring для работы с MongoDB.
  • MongoJack : обеспечивает прямое сопоставление JSON с объектами MongoDB.

Это не полный список ODM MongoDB для Java, но есть несколько интересных альтернатив!

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

В этой статье мы узнали об основных деталях MongoDB и использовании ODM для подключения и работы с MongoDB с такого языка программирования, как Java. Далее мы изучили Morphia как ODM MongoDB для Java и различные его возможности.

Как всегда, код можно найти на GitHub.