Статьи

NoSQL с Hibernate OGM — Часть первая: сохранение ваших первых сущностей

Выпущена первая финальная версия Hibernate OGM, и команда немного оправилась от безумного релиза. Поэтому они подумали о том, чтобы начать серию блогов в стиле учебников, которые дают вам возможность легко начать заново с Hibernate OGM. Спасибо Гуннару Морлингу ( @gunnarmorling ) за создание этого урока.

Вступление

Не знаете, что такое Hibernate OGM? Hibernate OGM является новейшим проектом под эгидой Hibernate и позволяет вам сохранять модели сущностей в разных хранилищах NoSQL через известный JPA .

Мы рассмотрим эти темы в следующие недели:

  • Сохранение ваших первых сущностей (этот взнос)
  • Запрашивая ваши данные
  • Работает на WildFly
  • Запуск с CDI на Java SE
  • Хранить данные в двух разных хранилищах в одном приложении

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

В этой первой части серии мы собираемся настроить Java-проект с необходимыми зависимостями, создать несколько простых сущностей и записать / прочитать их в и из хранилища. Мы начнем с графической базы данных Neo4j, а затем переключимся на хранилище документов MongoDB с небольшим изменением конфигурации.

Настройка проекта

Давайте сначала создадим новый проект Java с необходимыми зависимостями. Далее мы будем использовать Maven в качестве инструмента для сборки, но, конечно, Gradle или другие будут работать одинаково хорошо.

Добавьте это в блок dependencyManagement вашего pom.xml:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
...
<dependencyManagement>
    <dependencies>
        ...
        <dependency>
            <groupId>org.hibernate.ogm</groupId>
            <artifactId>hibernate-ogm-bom</artifactId>
            <type>pom</type>
            <version>4.1.1.Final</version>
            <scope>import</scope>
        </dependency>
            ...
    </dependencies>
</dependencyManagement>
...

Это обеспечит использование соответствующих версий модулей Hibernate OGM и их зависимостей. Затем добавьте следующее в блок dependencies :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
...
<dependencies>
    ...
    <dependency>
        <groupId>org.hibernate.ogm</groupId>
        <artifactId>hibernate-ogm-neo4j</artifactId>
    </dependency>
    <dependency>
        <groupId>org.jboss.jbossts</groupId>
        <artifactId>jbossjta</artifactId>
    </dependency>
    ...
</dependencies>
...

Зависимости:

  • Модуль Hibernate OGM для работы со встроенной базой данных Neo4j; Это включит все другие необходимые модули, такие как ядро ​​Hibernate OGM и драйвер Neo4j. При использовании MongoDB вы должны поменять это с hibernate-ogm-mongodb .
  • Реализация JBoss API транзакций Java (JTA), который необходим, когда он не работает в контейнере Java EE, таком как WildFly

Модель предметной области

Наша HikeSection модель предметной области состоит из трех классов: Hike , HikeSection и Person .

027c5fc5

Между Hike и HikeSection существует композиционная связь, т.е. поход состоит из нескольких разделов, жизненный цикл которых полностью зависит от Hike. Список разделов похода упорядочен; Этот порядок необходимо поддерживать при сохранении похода и его разделов.

Ассоциация между походом и Person (выступающей в качестве организатора похода) — это двунаправленные отношения «многие к одному / один ко многим»: один человек может организовать ноль или больше походов, тогда как в одном походе его организатором выступает только один человек ,

Картирование сущностей

Теперь давайте сопоставим модель предметной области, создав классы сущностей и пометив их необходимыми метаданными. Давайте начнем с класса Person :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@Entity
public class Person {
 
    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private long id;
 
    private String firstName;
    private String lastName;
 
    @OneToMany(mappedBy = "organizer", cascade = CascadeType.PERSIST)
    private Set<Hike> organizedHikes = new HashSet<>();
 
    // constructors, getters and setters...
}

Тип сущности помечается как таковой с @Entity аннотации @Entity , а свойство, представляющее идентификатор, @Id .

Вместо назначения идентификаторов вручную Hibernate OGM может позаботиться об этом, предлагая несколько стратегий генерации идентификаторов, таких как (эмулируемые) последовательности, UUID и многое другое. Использование генератора UUID обычно является хорошим выбором, поскольку он обеспечивает переносимость между различными хранилищами данных NoSQL и делает генерацию идентификаторов быстрой и масштабируемой. Но в зависимости от магазина, с которым вы работаете, вы также можете использовать определенные типы идентификаторов, такие как идентификаторы объектов в случае MongoDB (подробности см. В справочном руководстве ).

Наконец, @OneToMany помечает свойство @OneToMany как ассоциацию между сущностями. Поскольку это двунаправленный объект, атрибут mappedBy необходим для указания стороны ассоциации, которая отвечает за ее управление. Указание каскадного типа PERSIST гарантирует, что сохранение персоны автоматически приведет к сохранению связанных с ней походов.

Следующий класс Hike :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
@Entity
public class Hike {
 
    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private String id;
 
    private String description;
    private Date date;
    private BigDecimal difficulty;
 
    @ManyToOne
    private Person organizer;
 
    @ElementCollection
    @OrderColumn(name = "sectionNo")
    private List<HikeSection> sections;
 
    // constructors, getters and setters...
}

Здесь аннотация @ManyToOne отмечает другую сторону двунаправленной связи между Hike и Organizer . Поскольку предполагается, что HikeSection зависит от Hike, список разделов отображается через @ElementCollection . Чтобы обеспечить порядок секций в хранилище данных, используется @OrderColumn . Это добавит один дополнительный «столбец» к постоянным записям, который содержит порядковый номер каждого раздела.

Наконец, класс HikeSection :

1
2
3
4
5
6
7
8
@Embeddable
public class HikeSection {
 
    private String start;
    private String end;
 
    // constructors, getters and setters...
}

В отличие от Person и Hike , он отображается не через @Entity а через @Embeddable . Это означает, что он всегда является частью другого объекта (в данном случае Hike ) и, как таковой, также не имеет собственной идентичности. Поэтому он не объявляет никакого свойства @Id .

Обратите внимание, что эти отображения выглядели точно так же, если бы вы использовали Hibernate ORM с реляционным хранилищем данных. И действительно, это одно из обещаний Hibernate OGM: сделать миграцию между реляционной и NoSQL-парадигмой как можно проще!

Создание файла persistence.xml

С существующими классами сущностей отсутствует еще одна вещь, дескриптор persistence.xml JPA. Создайте его в src / main / resources / META-INF / persistence.xml :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
 
    version="2.0">
 
    <persistence-unit name="hikePu" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
 
        <properties>
            <property name="hibernate.ogm.datastore.provider" value="neo4j_embedded" />
            <property name="hibernate.ogm.datastore.database" value="HikeDB" />
            <property name="hibernate.ogm.neo4j.database_path" value="target/test_data_dir" />
        </properties>
    </persistence-unit>
</persistence>

Если вы работали с JPA раньше, это определение единицы сохраняемости должно показаться вам очень знакомым. Основным отличием от использования классического Hibernate ORM поверх реляционной базы данных является определенный класс провайдера, который мы должны указать для Hibernate OGM: org.hibernate.ogm.jpa.HibernateOgmPersistence .

Кроме того, некоторые свойства, специфичные для Hibernate OGM и выбранного внутреннего интерфейса, определены для установки:

  • используемый конец (встроенная графическая база данных Neo4j в данном случае)
  • имя базы данных Neo4j
  • каталог для хранения файлов базы данных Neo4j

В зависимости от вашего использования и серверной части, могут потребоваться другие свойства, например, для установки хоста, имени пользователя, пароля и т. Д. Вы можете найти все доступные свойства в классе с именем <BACK END>Properties , например, Neo4jProperties , MongoDBProperties и т. Д. ,

Сохранение и загрузка объекта

Со всеми этими битами пришло время сохранять (и загружать) некоторые объекты. Создайте простую тестовую оболочку JUnit для этого:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
public class HikeTest {
 
    private static EntityManagerFactory entityManagerFactory;
 
    @BeforeClass
    public static void setUpEntityManagerFactory() {
        entityManagerFactory = Persistence.createEntityManagerFactory( "hikePu" );
    }
 
    @AfterClass
    public static void closeEntityManagerFactory() {
        entityManagerFactory.close();
    }
}

Эти два метода управляют фабрикой диспетчера сущностей для модуля постоянства, определенного в файле persistence.xml. Он хранится в поле, поэтому его можно использовать для нескольких методов тестирования (помните, что фабрики диспетчера сущностей довольно дороги в создании, поэтому их следует инициализировать один раз и хранить для повторного использования).

Затем создайте метод тестирования, сохраняющий и загружающий некоторые данные:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Test
public void canPersistAndLoadPersonAndHikes() {
    EntityManager entityManager = entityManagerFactory.createEntityManager();
 
    entityManager.getTransaction().begin();
 
    // create a Person
    Person bob = new Person( "Bob", "McRobb" );
 
    // and two hikes
    Hike cornwall = new Hike(
            "Visiting Land's End", new Date(), new BigDecimal( "5.5" ),
            new HikeSection( "Penzance", "Mousehole" ),
            new HikeSection( "Mousehole", "St. Levan" ),
            new HikeSection( "St. Levan", "Land's End" )
    );
    Hike isleOfWight = new Hike(
            "Exploring Carisbrooke Castle", new Date(), new BigDecimal( "7.5" ),
            new HikeSection( "Freshwater", "Calbourne" ),
            new HikeSection( "Calbourne", "Carisbrooke Castle" )
    );
 
    // let Bob organize the two hikes
    cornwall.setOrganizer( bob );
    bob.getOrganizedHikes().add( cornwall );
 
    isleOfWight.setOrganizer( bob );
    bob.getOrganizedHikes().add( isleOfWight );
 
    // persist organizer (will be cascaded to hikes)
    entityManager.persist( bob );
 
    entityManager.getTransaction().commit();
 
    // get a new EM to make sure data is actually retrieved from the store and not Hibernate's internal cache
    entityManager.close();
    entityManager = entityManagerFactory.createEntityManager();
 
    // load it back
    entityManager.getTransaction().begin();
 
    Person loadedPerson = entityManager.find( Person.class, bob.getId() );
    assertThat( loadedPerson ).isNotNull();
    assertThat( loadedPerson.getFirstName() ).isEqualTo( "Bob" );
    assertThat( loadedPerson.getOrganizedHikes() ).onProperty( "description" ).containsOnly( "Visiting Land's End", "Exploring Carisbrooke Castle" );
 
    entityManager.getTransaction().commit();
 
    entityManager.close();
}

Обратите внимание, как оба действия происходят в транзакции. Neo4j — это полностью транзакционное хранилище данных, которым можно легко управлять с помощью API транзакций JPA. В реальном приложении можно было бы работать с менее подробным подходом к управлению транзакциями. В зависимости от выбранной серверной части и типа среды, в которой работает ваше приложение (например, контейнер Java EE, такой как WildFly ), вы можете воспользоваться декларативным управлением транзакциями через CDI или EJB. Но давайте сохраним это в другой раз.

Сохраняя некоторые данные, вы можете проверить их, используя красивую веб-консоль, поставляемую с Neo4j. Ниже показаны объекты, сохраненные в ходе теста:

aDXbhi6

Hibernate OGM стремится к максимально естественному составлению карт для хранилища данных, на которое вы ориентируетесь. В случае Neo4j как хранилища данных графа это означает, что любая сущность будет отображена на соответствующий узел.

Свойства объекта отображаются как свойства узла (см. Черный ящик с описанием одного из узлов похода). Любые неподдерживаемые типы свойств будут преобразованы по мере необходимости. Например, это относится к свойству date которое сохраняется в виде строки в формате ISO. Кроме того, каждый узел сущности имеет метку ENTITY (чтобы отличить его от узлов других типов) и метку, указывающую его тип сущности (в данном случае Hike).

Ассоциации отображаются как отношения между узлами, а роль ассоциации отображается на тип отношения .

Обратите внимание, что Neo4j не имеет понятия встроенных объектов. Поэтому объекты HikeSection отображаются как узлы с меткой EMBEDDED, связанной с владельцами узлов Hike. Порядок разделов сохраняется через свойство в отношении.

Переключение на MongoDB

Одно из обещаний Hibernate OGM — разрешить использование одного и того же API, а именно JPA, для работы с различными хранилищами NoSQL. Итак, давайте посмотрим, как это работает и использует MongoDB, который, в отличие от Neo4j, является хранилищем данных документа и сохраняет данные в JSON-подобном представлении. Для этого сначала замените серверную часть Neo4j на следующую:

1
2
3
4
5
6
...
<dependency>
    <groupId>org.hibernate.ogm</groupId>
    <artifactId>hibernate-ogm-mongodb</artifactId>
</dependency>
...

Затем обновите конфигурацию в файле persistence.xml для работы с MongoDB в качестве внутреннего сервера, используя свойства, доступные через
MongoDBProperties для предоставления имени хоста и учетных данных, соответствующих вашей среде (если у вас еще не установлен MongoDB, вы можете скачать его здесь ):

1
2
3
4
5
6
7
8
9
...
<properties>
    <property name="hibernate.ogm.datastore.provider" value="mongodb" />
    <property name="hibernate.ogm.datastore.database" value="HikeDB" />
    <property name="hibernate.ogm.datastore.host" value="mongodb.mycompany.com" />
    <property name="hibernate.ogm.datastore.username" value="db_user" />
    <property name="hibernate.ogm.datastore.password" value="top_secret!" />
</properties>
...

И это все, что вам нужно сделать, чтобы сохранить ваши сущности в MongoDB, а не в Neo4j. Если вы снова запустите тестирование, вы найдете следующие документы BSON в вашем хранилище данных:

01
02
03
04
05
06
07
08
09
10
# Collection "Person"
{
    "_id" : "50b62f9b-874f-4513-85aa-c2f59015a9d0",
    "firstName" : "Bob",
    "lastName" : "McRobb",
    "organizedHikes" : [
        "a78d731f-eff0-41f5-88d6-951f0206ee67",
        "32384eb4-717a-43dc-8c58-9aa4c4e505d1"
    ]
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# Collection Hike
{
    "_id" : "a78d731f-eff0-41f5-88d6-951f0206ee67",
    "date" : ISODate("2015-01-16T11:59:48.928Z"),
    "description" : "Visiting Land's End",
    "difficulty" : "5.5",
    "organizer_id" : "50b62f9b-874f-4513-85aa-c2f59015a9d0",
    "sections" : [
        {
            "sectionNo" : 0,
            "start" : "Penzance",
            "end" : "Mousehole"
        },
        {
            "sectionNo" : 1,
            "start" : "Mousehole",
            "end" : "St. Levan"
        },
        {
            "sectionNo" : 2,
            "start" : "St. Levan",
            "end" : "Land's End"
        }
    ]
}
{
    "_id" : "32384eb4-717a-43dc-8c58-9aa4c4e505d1",
    "date" : ISODate("2015-01-16T11:59:48.928Z"),
    "description" : "Exploring Carisbrooke Castle",
    "difficulty" : "7.5",
    "organizer_id" : "50b62f9b-874f-4513-85aa-c2f59015a9d0",
    "sections" : [
        {
            "sectionNo" : 1,
            "start" : "Calbourne",
            "end" : "Carisbrooke Castle"
        },
        {
            "sectionNo" : 0,
            "start" : "Freshwater",
            "end" : "Calbourne"
        }
    ]
}

Опять же, отображение очень естественное и такое, как вы ожидаете, работая с хранилищем документов, таким как MongoDB. Двунаправленная связь один-ко-многим / многие-к-одному между Person и Hike отображается путем сохранения ссылочных идентификаторов с обеих сторон. При загрузке данных обратно Hibernate OGM разрешит идентификаторы и позволит перемещаться по ассоциации от одного объекта к другому.

Коллекции элементов отображаются с использованием возможностей MongoDB для хранения иерархических структур. Здесь разделы похода сопоставляются с массивом в документе владельца похода с дополнительным полем sectionNo для поддержания порядка сбора. Это позволяет очень эффективно загружать сущность и ее встроенные элементы за один прием в хранилище данных.

Заворачивать

В этой первой части NoSQL с Hibernate OGM 101 вы узнали, как настроить проект с необходимыми зависимостями, отобразить некоторые сущности и ассоциации и сохранить их в Neo4j и MongoDB. Все это происходит через известный API JPA. Так что, если раньше вы работали с Hibernate ORM и JPA поверх реляционных баз данных, вам никогда не было так легко погрузиться в мир NoSQL.

В то же время каждый магазин ориентирован на определенные варианты использования и, таким образом, предоставляет определенные функции и параметры конфигурации. Естественно, они не могут быть представлены через общий API, такой как JPA. Поэтому Hibernate OGM позволяет вам использовать собственные запросы NoSQL и позволяет настраивать специфичные для магазина настройки с помощью своей гибкой системы опций.

Вы можете найти полный пример кода этого поста на GitHub. Просто раскошелитесь и играйте как хотите.

Конечно, хранение сущностей и получение их по идентификатору — это только начало. В любом реальном приложении вы хотели бы выполнять запросы к вашим данным, и вы, вероятно, также захотите воспользоваться некоторыми специфическими функциями и настройками выбранного вами магазина NoSQL. Мы придем к этому в следующих частях этой серии, так что следите за обновлениями!