Статьи

NoSQL с Hibernate OGM. Часть третья. Создание REST-приложения на WildFly

Добро пожаловать в нашу серию руководств «NoSQL с Hibernate OGM»! Спасибо Гуннару Морлингу ( @gunnarmorling ) за создание этого урока. В этой части вы узнаете, как использовать Hibernate OGM из приложения Java EE, работающего на сервере WildFly. Используя  модель сущностей, которую  вы уже знаете из предыдущих частей этого руководства, мы создадим небольшое приложение на основе REST для управления походами. Если вы не читали первые две части этой серии, вы можете найти их здесь: 

Далее вы узнаете, как подготовить WildFly к использованию его с Hibernate OGM, настроить модуль сохранения JPA, создать классы репозитория для доступа к вашим данным и предоставления ресурсов REST поверх них. В этом посте мы в основном сосредоточимся на аспектах, связанных с постоянством, поэтому может помочь некоторый базовый опыт работы с REST / JAX-RS.  Полный исходный код  этого урока размещается на GitHub. 

Подготовка WildFly
Время   выполнения сервера WildFly основано на  модулях JBoss система. Это обеспечивает модульную среду загрузки классов, в которой каждая библиотека (например, Hibernate OGM) является собственным модулем, объявляя список других модулей, от которых она зависит, и «видя» только классы из этих других зависимостей. Эта изоляция обеспечивает выход из страшного «ада пути к классам». 
ZIP-файлы, содержащие все необходимые модули для Hibernate OGM, предоставляются на SourceForge. Hibernate OGM 4.2 — который  мы выпустили вчера  — поддерживает WildFly 9, поэтому загрузите  hibernate-ogm-modules-wildfly9-4.2.0.Final.zip  для этого. Если вы используете WildFly 8, используйте Hibernate OGM 4.1 и получите   вместо него hibernate-ogm-modules-wildfly8-4.1.3.Final.zip
Распакуйте архив, соответствующий вашей версии WildFly, в  модули каталог сервера приложений. Если вы предпочитаете, чтобы исходные каталоги WildFly оставались без изменений, вы также можете разархивировать архив модулей Hibernate OGM в любую другую папку и настроить его как «путь к модулю», который будет использоваться сервером. Для этого экспортируйте следующие две переменные среды, соответствующие вашей конкретной среде: 

export JBOSS_HOME=/path/to/wildfly
export JBOSS_MODULEPATH=$JBOSS_HOME/modules:/path/to/ogm/modules

Если вы работаете с  плагином Maven WildFly , например, для запуска WildFly во время разработки, вы добьетесь того же с помощью следующей конфигурации плагина в вашем файле POM: 

...
<plugin>
    <groupId>org.wildfly.plugins</groupId>
    <artifactId>wildfly-maven-plugin</artifactId>
    <version>1.1.0.Alpha1</version>
    <configuration>
        <jboss-home>/path/to/wildfly</jboss-home>
        <modules-path>/path/to/ogm/modules</modules-path>
    </configuration>
</plugin>
...

Настройка проекта
Начните с создания нового проекта Maven с использованием типа упаковки «war». Добавьте следующее в ваш  pom.xml

...
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.hibernate.ogm</groupId>
            <artifactId>hibernate-ogm-bom</artifactId>
            <type>pom</type>
            <version>4.2.0.Final</version>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
...

Это гарантирует, что вы получите соответствующие версии модулей Hibernate OGM и любые (необязательные) зависимости. Затем добавьте зависимость к API Java EE 7 и одному из внутренних модулей Hibernate OGM, например,  Infinispan , высокопроизводительную распределенную сетку данных ключ / значение JBoss (любую другую, такую ​​как  hibernate-ogm-mongodb  или совершенно новый  hibernate). -ogm-cassandra  модуль также будет работать): 

...
<dependencies>
    <dependency>
        <groupId>javax</groupId>
        <artifactId>javaee-api</artifactId>
        <version>7.0</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.hibernate.ogm</groupId>
        <artifactId>hibernate-ogm-infinispan</artifactId>
        <scope>provided</scope>
    </dependency>
</dependencies>
...

Предоставленная область действия делает эти зависимости доступными для компиляции, но предотвращает их добавление в результирующий файл WAR. Это потому, что API Java EE уже является частью WildFly, а Hibernate OGM будет добавлен через разархивированные ранее модули. 
Однако простое добавление этих модулей на сервер не сокращает его. Они также должны быть зарегистрированы как зависимости модуля с приложением. Для этого добавьте файл src / main / webapp / WEB-INF / jboss-web.xml  со следующим содержимым: 

<?xml version="1.0" encoding="UTF-8"?>
<jboss-deployment-structure
    xmlns="urn:jboss:deployment-structure:1.2"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <deployment>
        <dependencies>
            <module name="org.hibernate" slot="ogm" services="import" />
            <module name="org.hibernate.ogm.infinispan" services="import" />
            <module name="org.hibernate.search.orm" services="import" />
        </dependencies>
    </deployment>
</jboss-deployment-structure>

Это сделает ядро ​​Hibernate OGM и серверную часть Infinispan, а также  Hibernate Search  доступным для вашего приложения. Последний будет использоваться для выполнения запросов JP-QL немного. 

Добавление классов сущностей и репозиториев
Имея базовую инфраструктуру проекта, пришло время добавить классы сущностей и классы репозиториев для доступа к ним. Типы сущностей в основном такие же, как в  части 1 , только теперь они аннотируются с помощью @Indexed, чтобы можно было запрашивать их через Hibernate Search и Lucene: 

@Entity
@Indexed
public class Person {

    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private String id;

    private String firstName;
    private String lastName;

    @OneToMany(
        mappedBy = "organizer",
        cascade = { CascadeType.PERSIST, CascadeType.MERGE },
        fetch = FetchType.EAGER
    )
    private Set<Hike> organizedHikes = new HashSet<>();

    // constructors, getters and setters...
}
@Entity
@Indexed
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(fetch = FetchType.EAGER)
    @OrderColumn(name = "sectionNo")
    private List<HikeSection> sections;

    // constructors, getters and setters...
}
@Embeddable
public class HikeSection {

    private String start;
    private String end;

    // constructors, getters and setters...
}

Чтобы использовать эти объекты, должна быть определена единица сохранения JPA. Для этого создайте файл  src / main / resources / META-INF / persistence.xml

<?xml version="1.0" encoding="utf-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
    version="1.0">

    <persistence-unit name="hike-PU" transaction-type="JTA">
        <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>

            <class>org.hibernate.ogm.demos.ogm101.part3.model.Person</class>
            <class>org.hibernate.ogm.demos.ogm101.part3.model.Hike</class>

            <properties>
                <property name="hibernate.ogm.datastore.provider" value="INFINISPAN" />
                <property name="hibernate.ogm.datastore.database" value="hike_db" />
                <property name="hibernate.ogm.datastore.create_database" value="true" />
            </properties>
    </persistence-unit>
</persistence>

Здесь мы определяем единицу постоянства с именем «Hike-PU». Infinispan является полностью транзакционным хранилищем данных, и использование JTA в качестве типа транзакции позволяет единице персистентности участвовать в транзакциях, управляемых контейнером. Указание HibernateOgmPersistence в качестве класса провайдера включает Hibernate OGM (вместо Hibernate ORM), который настроен с некоторыми свойствами для параметра бэкэнда (в данном случае INFINISPAN), имени базы данных и т. Д. 
Обратите внимание, что на самом деле не нужно указывать типы сущностей в persistence.xml при запуске в контейнере Java EE, таком как WildFly. Вместо этого они должны быть подобраны автоматически. При использовании Hibernate OGM это, к сожалению, необходимо в данный момент. Это известное ограничение (см.  OGM-828 ), которое мы надеемся вскоре исправить. 
Следующим шагом является реализация классов репозитория для доступа к данным похода и организатора. В качестве примера ниже показан класс PersonRepository: 

@ApplicationScoped
public class PersonRepository {

    @PersistenceContext
    private EntityManager entityManager;

    public Person create(Person person) {
        entityManager.persist( person );
        return person;
    }

    public Person get(String id) {
        return entityManager.find( Person.class, id );
    }

    public List<Person> getAll() {
        return entityManager.createQuery( "FROM Person p", Person.class ).getResultList();
    }

    public Person save(Person person) {
        return entityManager.merge( person );
    }

    public void remove(Person person) {
        entityManager.remove( person );
        for ( Hike hike : person.getOrganizedHikes() ) {
            hike.setOrganizer( null );
        }
    }
}

Реализация проста; с помощью аннотации @ApplicationScoped класс помечается как компонент CDI в области приложения (т. е. один единственный экземпляр этого компонента существует на протяжении всего жизненного цикла приложения). Он получает диспетчер сущностей JPA посредством внедрения зависимостей и использует его для реализации некоторых простых методов CRUD (Create, Read, Update, Delete). 
Обратите внимание, как метод getAll () использует запрос JP-QL для возврата всех объектов person. После выполнения этот запрос будет преобразован в эквивалентный индексный запрос Lucene, который будет выполняться через Hibernate Search. 
Хранилище для походов выглядит очень похоже, поэтому для краткости оно здесь опущено. Вы можете найти  его исходный код  на GitHub. 

Предоставление REST-услуг
JAX-RS упрощает создание веб-сервисов REST. Он определяет декларативную модель программирования, в которой вы аннотируете простые старые классы Java, чтобы обеспечить реализации для операций GET, POST, PUT и т. Д. Конечной точки HTTP. 
Подробное описание JAX-RS выходит за рамки данного руководства, например, обратитесь к руководству по  Java EE 7,  если вы хотите узнать больше. Давайте просто посмотрим на некоторые методы класса ресурсов для управления людьми в качестве примера: 

@Path("/persons")
@Produces("application/json")
@Consumes("application/json")
@Stateless
public class Persons {

    @Inject
    private PersonRepository personRepository;

    @Inject
    private ResourceMapper mapper;

    @Inject
    private UriMapper uris;

    @POST
    @Path("/")
    public Response createPerson(PersonDocument request) {
        Person person = personRepository.create( mapper.toPerson( request ) );
        return Response.created( uris.toUri( person ) ).build();
    }

    @GET
    @Path("/{id}")
    public Response getPerson(@PathParam("id") String id) {
        Person person = personRepository.get( id );
        if ( person == null ) {
            return Response.status( Status.NOT_FOUND ).build();
        }
        else {
            return Response.ok( mapper.toPersonDocument( person ) ).build();
        }
    }

    @GET
    @Path("/")
    public Response listPersons() { … }

    @PUT
    @Path("/{id}")
    public Response updatePerson(PersonDocument request, @PathParam("id") String id) { … }

    @DELETE
    @Path("/{id}")
    public Response deletePerson(@PathParam("id") String id) { … }
}

Аннотации @Path, @Produces и @Consumes определяются JAX-RS. Они связывают методы ресурсов с конкретными URL-адресами, ожидая и создавая сообщения на основе JSON. @GET, @POST, @PUT и @DELETE определяют, за какой HTTP-глагол отвечает каждый метод. 
Аннотация @Stateless определяет этот POJO как сессионный компонент без сохранения состояния. Такие зависимости, как PersonRepository, можно получить с помощью внедрения зависимостей на основе @ Inject. Реализация сессионного компонента дает вам удобство прозрачного управления транзакциями контейнером. Вызовы методов Persons будут автоматически заключены в транзакцию, и все взаимодействия Hibernate OGM с хранилищем данных будут участвовать в них. Это означает, что любые изменения, которые вы вносите в управляемые объекты — например, сохраняя нового человека с помощью PersonRepository # create () или изменяя объект Person, полученный из менеджера объектов — будут зафиксированы в хранилище данных после возврата вызова метода. 

Картографические модели
Обратите внимание, что методы нашей службы REST не возвращают и не принимают сами типы управляемых объектов, а представляют собой специфические транспортные структуры, такие как PersonDocument: 

public class PersonDocument {

    private String firstName;
    private String lastName;
    private Set<URI> organizedHikes;

    // constructors, getters and setters...
}

Основанием для этого является представление элементов ассоциаций (Person # organizHikes, Hike # organizer) в форме URI, что позволяет клиенту извлекать эти связанные ресурсы по мере необходимости. Например, вызов GET для  http: // myserver / ogm-demo-part3 / hike-manager / people / 123  может вернуть структуру JSON, как показано ниже: 

{
    "firstName": "Saundra",
    "lastName": "Johnson",
    "organizedHikes": [
        "http://myserver/ogm-demo-part3/hike-manager/hikes/456",
        "http://myserver/ogm-demo-part3/hike-manager/hikes/789"
    ]
}

Отображение между внутренней моделью (например, сущностью Person) и внешней (например, PersonDocument) может быстро стать утомительной и скучной задачей, поэтому некоторая поддержка на основе инструментов для этого желательна. Для этой работы существует несколько инструментов, большинство из которых используют отражение или генерацию байт-кода времени выполнения для распространения состояния между различными моделями. 
Другой подход к этому —  MapStruct., который является моим проектом свободного времени и генерирует реализации bean mapper во время компиляции (например, с Maven или в вашей IDE) через процессор аннотаций Java. Код, который он генерирует, является типобезопасным, быстрым (он использует простые вызовы методов, без отражения) и не зависит от зависимостей. Вам просто нужно объявить Java-интерфейсы с методами отображения для нужного исходного и целевого типов, и MapStruct сгенерирует реализацию как часть процесса компиляции: 

@Mapper(
    // allows to obtain the mapper via @Inject
    componentModel = "cdi",

    // a hand-written mapper class for converting entities to URIs; invoked by the generated
    // toPersonDocument() implementation for mapping the organizedHikes property
    uses = UriMapper.class
)
public interface ResourceMapper {

    PersonDocument toPersonDocument(Person person);

    List<PersonDocument> toPersonDocuments(Iterable<Person> persons);

    @Mapping(target = "date", dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
    HikeDocument toHikeDocument(Hike hike);

    // other mapping methods ...
}

Сгенерированная реализация может затем использоваться в ресурсе Persons REST для сопоставления внутренней и внешней модели и наоборот. Если вы хотите узнать больше об этом подходе для сопоставлений моделей, ознакомьтесь с  полным интерфейсом сопоставления  в GitHub или  справочной документацией MapStruct

Заключение
В этой части нашей серии руководств вы узнали, как добавить Hibernate OGM на сервер приложений WildFly и использовать его для доступа к Infinispan в качестве хранилища данных для небольшого приложения REST. 
WildFly — это отличная среда выполнения для приложений, использующих Hibernate OGM, поскольку она предоставляет большинство необходимых строительных блоков из коробки (например, JPA / Hibernate ORM, JTA, управление транзакциями и т. Д.), Тесно интегрированных и готовых к использованию. Наш модуль ZIP позволяет очень легко помещать модули Hibernate OGM в микс, без необходимости повторного их развертывания каждый раз с вашим приложением. В  WildFly Swarm  также есть поддержка архитектурного стиля микросервисов, но мы оставим это на другой раз, чтобы показать, как использовать Hibernate OGM с Wildfly Swarm (в настоящее время поддержка JPA по-прежнему отсутствует в WildFly Swarm). 
Вы можете найти источники проекта  на GitHub . Для сборки проекта запустите mvn clean install (который выполняет  интеграционный тест для сервисов REST, использующих Arquillian, интересная тема сама по себе). Кроме того, подключаемый модуль Maven WildFly можно использовать для запуска экземпляра WildFly и развертывания приложения с помощью mvn wildfly: run, который отлично подходит для ручного тестирования, например, путем отправки HTTP-запросов через curl или wget. 
Если у вас есть какие-либо вопросы, сообщите нам об этом в комментариях ниже или отправьте нам твит на  @Hibernate . Также приветствуются ваши пожелания к будущим частям этого урока. Будьте на связи!