欢迎回到我们的教程系列“使用 Hibernate OGM 的 NoSQL”!
在本部分中,您将学习如何在 WildFly 服务器上运行的 Java EE 应用程序中从 Hibernate OGM 中使用。使用您从本教程的前几部分已经了解的 实体模型,我们将构建一个用于管理徒步旅行的小型基于 REST 的应用程序。如果您还没有阅读本系列的第一个和第二个部分,您可以在以下位置找到它们
在以下内容中,您将学习如何为使用 Hibernate OGM 准备 WildFly,配置 JPA 持久化单元,创建用于访问您的数据和在这些数据上提供 REST 资源的仓库类。在本篇文章中,我们将主要关注与持久化相关的方面,因此一些基本的 REST/JAX-RS 经验可能会有所帮助。本教程的 完整源代码 已托管在 GitHub 上。
准备 WildFly
WildFly 服务器运行时基于 JBoss 模块 系统。这提供了一个模块化类加载环境,其中每个库(如 Hibernate OGM)都是其自己的模块,声明它所依赖的其他模块列表,并且仅“看到”来自这些其他依赖项的类。这种隔离提供了从可怕的“类路径地狱”中解脱出来的方法。
包含Hibernate OGM所有必需模块的ZIP文件可在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版本的存档解压到应用程序服务器的modules目录中。如果您希望原始的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>
...
设置项目
首先,使用“war”打包类型创建一个新的Maven项目。将以下内容添加到您的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模块及其任何(可选)依赖项的匹配版本。然后添加Java EE 7 API的依赖项和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文件中。这是因为Java EE API已经是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)、数据库名称等。
请注意,在实际使用WildFly等Java EE容器运行时,通常不需要在persistence.xml中指定实体类型。相反,它们应该会自动获取。但是,在使用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 Bean(即在整个应用程序生命周期中只存在一个此Bean的实例)。它通过依赖注入获取JPA实体管理器,并使用它来实现一些简单的CRUD方法(创建、读取、更新、删除)。
注意,getAll()方法使用JP-QL查询返回所有人员对象。在执行此查询时,它将被转换为等效的Lucene索引查询,并通过Hibernate Search运行。
徒步旅行仓库看起来非常相似,因此这里省略以节省篇幅。您可以在GitHub上找到其源代码。
公开REST服务
JAX-RS使得构建RESTful Web服务变得轻而易举。它定义了一个声明性编程模型,通过注释普通的Java类来提供HTTP端点的GET、POST、PUT等操作的实现。
深入介绍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定义为无状态会话Bean。依赖项如PersonRepository可以通过@Inject依赖注入来获取。实现会话Bean可以让容器为您提供透明的交易管理。对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#organizedHikes, Hike#organizer)表示为URI形式,这允许客户端根据需要获取这些链接资源。例如,对http://myserver/ogm-demo-part3/hike-manager/persons/123的GET调用可能返回以下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 是追求这一目标的方法之一,它是我的一项业余项目,可以在编译时(例如使用 Maven 或在您的 IDE 中)通过 Java 注解处理器生成 Bean 映射实现。它生成的代码是类型安全的、快速的(它使用普通方法调用,没有反射)且无依赖。您只需要声明带有映射方法的 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 ...
}
生成的实现可以用于PersonsREST 资源,以从内部模型映射到外部模型,反之亦然。如果您想了解更多关于模型映射的方法,请查看 GitHub 上的完整的映射接口或MapStruct 参考文档。
总结
在本教程系列的这一部分,您学习了如何将 Hibernate OGM 添加到 WildFly 应用程序服务器,并使用它作为小型 REST 应用程序的数据存储。
WildFly 是 Hibernate OGM 应用程序的优秀运行环境,因为它提供了大多数所需的构建块,开箱即用(例如 JPA/Hibernate ORM、JTA、事务管理等),紧密集成并可用于使用。我们的模块 ZIP 允许您轻松地将 Hibernate OGM 模块混合在一起,无需每次与应用程序重新部署。此外,WildFly Swarm 也支持微服务架构风格,但我们将留待以后展示如何使用 Hibernate OGM 与 Wildfly Swarm(目前 WildFly Swarm 还不支持 JPA)。
您可以在 GitHub 上找到项目的源代码此处。要构建项目,请运行mvn clean install(这会执行一个集成测试,用于使用 Arquillian 的 REST 服务,这是一个令人兴奋的话题)。或者,可以使用 Maven WildFly 插件启动 WildFly 实例并通过mvn wildfly:run部署应用程序,这对于手动测试很有帮助,例如通过 curl 或 wget 发送 HTTP 请求。
如果您有任何问题,请在下方的评论中告诉我们,或者给我们发一条推文到@Hibernate。您对教程未来部分的期望也欢迎。请保持关注!