1. Обзор
В этом уроке мы рассмотрим основы Hibernate Object/Grid Mapper (OGM) .
Hibernate OGM обеспечивает поддержку Java Persistence API (JPA) для хранилищ данных NoSQL. NoSQL — это общий термин, охватывающий широкий спектр хранилищ данных. Например, сюда входят хранилища данных типа "ключ-значение", "документ", "столбец" и "график".
2. Архитектура Hibernate OGM
Hibernate традиционно предлагает механизм объектно-реляционного сопоставления (ORM) для реляционных баз данных. Механизм Hibernate OGM расширяет свои функциональные возможности для поддержки хранилищ данных NoSQL. Основным преимуществом его использования является согласованность интерфейса JPA в реляционных хранилищах и хранилищах данных NoSQL.
Hibernate OGM может обеспечить абстракцию над рядом хранилищ данных NoSQL благодаря двум ключевым интерфейсам, DatastoreProvider
и GridDialect
. Поэтому каждое новое хранилище данных NoSQL, которое оно поддерживает, поставляется с реализацией этих интерфейсов.
На сегодняшний день он не поддерживает все хранилища данных NoSQL, но способен работать со многими из них, такими как Infinispan и Ehcache (ключ-значение), MongoDB и CouchDB (документ) и Neo4j (граф).
Он также полностью поддерживает транзакции и может работать со стандартными провайдерами JTA. Во-первых, это можно сделать через контейнер Jakarta EE без какой-либо явной настройки. Более того, мы можем использовать автономный менеджер транзакций JTA, такой как Narayana, в среде Java SE.
3. Настройка
В этом руководстве мы будем использовать Maven для получения необходимых зависимостей для работы с Hibernate OGM. Мы также будем использовать MongoDB .
Чтобы уточнить, давайте посмотрим, как настроить их для учебника.
3.1. Зависимости Maven
Давайте посмотрим на зависимости, необходимые для работы с Hibernate OGM и MongoDB:
<dependency>
<groupId>org.hibernate.ogm</groupId>
<artifactId>hibernate-ogm-mongodb</artifactId>
<version>5.4.0.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.narayana.jta</groupId>
<artifactId>narayana-jta</artifactId>
<version>5.9.2.Final</version>
</dependency>
Здесь мы извлекаем необходимые зависимости через Maven:
- Диалект Hibernate OGM для MongoDB
- Narayana Transaction Manager (фактический поставщик JTA)
3.2. Блок сохранения
Нам также нужно будет определить детали хранилища данных в файле Hibernate persistance.xml
:
<persistence-unit name="ogm-mongodb" transaction-type="JTA">
<provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
<properties>
<property name="hibernate.ogm.datastore.provider" value="MONGODB" />
<property name="hibernate.ogm.datastore.database" value="TestDB" />
<property name="hibernate.ogm.datastore.create_database" value="true" />
</properties>
</persistence-unit>
Обратите внимание на приведенные здесь определения:
- значение атрибута типа транзакции как «JTA» (это означает, что нам нужен менеджер сущностей JTA из
EntityManagerFactory)
- поставщик, которым является
HibernateOgmPersistence
для Hibernate OGM - несколько дополнительных деталей, связанных с БД (обычно они различаются в разных источниках данных)
Конфигурация предполагает, что MongoDB работает и доступна по умолчанию. Если это не так, мы всегда можем предоставить подробную информацию по мере необходимости . В одной из наших предыдущих статей также подробно рассматривается настройка MongoDB .
4. Определение объекта
Теперь, когда мы рассмотрели основы, давайте определим некоторые объекты. Если раньше мы работали с Hibernate ORM или JPA, то в этом больше добавить нечего . Это основная предпосылка Hibernate OGM. Он обещает позволить нам работать с различными хранилищами данных NoSQL, зная только JPA .
Для этого руководства мы определим простую объектную модель:
Он определяет классы Article
, Author
и Editor вместе с их отношениями.
Давайте также определим их в Java:
@Entity
public class Article {
@Id
@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy = "uuid2")
private String articleId;
private String articleTitle;
@ManyToOne
private Author author;
// constructors, getters and setters...
}
@Entity
public class Author {
@Id
@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy = "uuid2")
private String authorId;
private String authorName;
@ManyToOne
private Editor editor;
@OneToMany(mappedBy = "author", cascade = CascadeType.PERSIST)
private Set<Article> authoredArticles = new HashSet<>();
// constructors, getters and setters...
}
@Entity
public class Editor {
@Id
@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy = "uuid2")
private String editorId;
private String editorName;
@OneToMany(mappedBy = "editor", cascade = CascadeType.PERSIST)
private Set<Author> assignedAuthors = new HashSet<>();
// constructors, getters and setters...
}
Теперь мы определили классы сущностей и аннотировали их стандартными аннотациями JPA:
@Entity
, чтобы установить их как объекты JPA@Id
для генерации первичных ключей для сущностей с UUID@OneToMany
и@ManyToOne
для установления двунаправленных отношений между объектами.
5. Операции
Теперь, когда мы создали наши объекты, давайте посмотрим, сможем ли мы выполнить над ними некоторые операции. В качестве первого шага нам нужно сгенерировать некоторые тестовые данные. Здесь мы создадим Editor
, несколько Author
и немного Article.
Мы также установим их отношения.
После этого, прежде чем мы сможем выполнить какую-либо операцию, нам понадобится экземпляр EntityManagerFactory.
Мы можем использовать это для создания EntityManager
. Наряду с этим нам нужно создать TransactionManager
для обработки границ транзакций.
Давайте посмотрим, как мы можем использовать их для сохранения и извлечения объектов, которые мы создали ранее:
private void persistTestData(EntityManagerFactory entityManagerFactory, Editor editor)
throws Exception {
TransactionManager transactionManager =
com.arjuna.ats.jta.TransactionManager.transactionManager();
transactionManager.begin();
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.persist(editor);
entityManager.close();
transactionManager.commit();
}
Здесь мы используем EntityManager
для сохранения корневого объекта, который распространяется на все его отношения. Мы также выполняем эту операцию в рамках определенной границы транзакции.
Теперь мы готовы загрузить объект, который мы только что сохранили, и проверить его содержимое. Мы можем запустить тест, чтобы убедиться в этом:
@Test
public void givenMongoDB_WhenEntitiesCreated_thenCanBeRetrieved() throws Exception {
EntityManagerFactory entityManagerFactory =
Persistence.createEntityManagerFactory("ogm-mongodb");
Editor editor = generateTestData();
persistTestData(entityManagerFactory, editor);
TransactionManager transactionManager =
com.arjuna.ats.jta.TransactionManager.transactionManager();
transactionManager.begin();
EntityManager entityManager = entityManagerFactory.createEntityManager();
Editor loadedEditor = entityManager.find(Editor.class, editor.getEditorId());
assertThat(loadedEditor).isNotNull();
// Other assertions to verify the entities and relations
}
Здесь мы снова используем EntityManager
для поиска данных и выполнения над ними стандартных утверждений. Когда мы запускаем этот тест, он создает хранилище данных, сохраняет объекты, извлекает их обратно и проверяет.
Опять же, мы только что использовали JPA для сохранения объектов вместе с их отношениями . Точно так же мы используем JPA для обратной загрузки сущностей, и все работает нормально, даже если мы выбираем базу данных MongoDB вместо традиционной реляционной базы данных.
6. Переключение бэкенда
Мы также можем переключить наш бэкэнд. Давайте теперь узнаем, насколько сложно это будет сделать.
Мы изменим наш бэкэнд на Neo4j, который оказался популярным графоориентированным хранилищем данных .
Во-первых, давайте добавим зависимость Maven для Neo4j :
<dependency>
<groupId>org.hibernate.ogm</groupId>
<artifactId>hibernate-ogm-neo4j</artifactId>
<version>5.4.0.Final</version>
</dependency>
Затем нам нужно будет добавить соответствующий блок сохранения в наш файл persistence.xml
:
<persistence-unit name="ogm-neo4j" transaction-type="JTA">
<provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
<properties>
<property name="hibernate.ogm.datastore.provider" value="NEO4J_EMBEDDED" />
<property name="hibernate.ogm.datastore.database" value="TestDB" />
<property name="hibernate.ogm.neo4j.database_path" value="target/test_data_dir" />
</properties>
</persistence-unit>
Короче говоря, это самые основные конфигурации, необходимые для Neo4j. При необходимости это можно детализировать .
Ну, это примерно то, что нужно сделать. Когда мы запускаем тот же тест с Neo4j в качестве внутреннего хранилища данных, он работает без проблем.
Обратите внимание, что мы переключили наш сервер с MongoDB, который является хранилищем данных, ориентированным на документы, на Neo4j, который является хранилищем данных, ориентированным на графы. И все это мы сделали с минимальными изменениями и без каких-либо изменений ни в одной из наших операций .
7. Заключение
В этой статье мы рассмотрели основы Hibernate OGM, включая его архитектуру. Впоследствии мы реализовали базовую модель предметной области и выполнили различные операции с использованием различных БД.
Как всегда, код примеров доступен на GitHub .