前言

Hibernate OGM 是一个持久化引擎,它为 NoSQL 数据存储提供 Java 持久性 (JPA) 支持。它重用了 Hibernate ORM 的对象生命周期管理和(反)序列化引擎,但将实体持久化到 NoSQL 存储(键/值、文档、面向列等)而不是关系数据库中。

除了使用特定 NoSQL 数据库的本机查询外,它还允许使用 Java 持久性查询语言 (JPQL) 作为查询存储数据的接口。

该项目在存储策略方面已经相当成熟,其功能集足以在您的项目中使用。然而,我们对它抱有远大于简单对象映射器的雄心壮志。路线图上还有很多事情(更多 NoSQL、查询、反规范化引擎等)。如果您觉得缺少功能,向我们报告。如果您想贡献,那就更好了

Hibernate OGM 在 LGPL 开源许可下发布。

该项目的未来正在由我们用户的需求塑造。请就以下方面给我们反馈

  • 您喜欢什么

  • 您不喜欢什么

  • 什么让您感到困惑

  • 您缺少什么功能

查看如何贡献,了解如何与我们联系。

我们为这份文档付出了很多努力,但我们知道它远非完美。如果您发现任何令人困惑的地方,或者觉得缺少解释,请告诉我们。联系我们非常容易:请查看 联系开发者社区

目标

Hibernate OGM

  • 提供熟悉的编程范式 (JPA) 来处理 NoSQL 存储

  • 将模型反规范化从手动命令式工作转变为由引擎处理的声明式方法

  • 鼓励在更“传统”的企业中使用新的数据使用模式和 NoSQL 探索

  • 帮助使用 NoSQL 前端扩展传统数据库的现有应用程序

NoSQL 可能非常令人困惑,因为它由许多不同的解决方案组成,它们具有不同的优点和缺点。NoSQL 数据库可以大致分为四个类别

  • 面向图的数据库

  • 键/值存储:本质上是 Map,但具有不同的行为和各种产品的理念(数据网格,具有强一致性或最终一致性的持久性等)

  • 基于文档的数据存储:包含半结构化文档的映射(例如 JSON)

  • 面向列的数据存储

nosql
图 1. 各种 NoSQL 类别

每种类别都有不同的优点和缺点,一种解决方案可能比另一种解决方案更适合某个用例。但是,访问模式和 API 在不同的产品之间有所不同。

Hibernate OGM 不期望成为用于在所有用例中与所有 NoSQL 解决方案交互的“通用语言”。但是对于将数据建模为域模型的人来说,它提供了与原始 API 相比的独特优势,并且具有提供 Java 开发人员熟悉的 API 和语义的优势。重用相同的编程模型并尝试不同的(No)SQL 引擎将有希望帮助人们探索替代数据存储。

Hibernate OGM 还旨在通过提供 NoSQL 前端并保持相同的 JPA API 和域模型,帮助人们扩展传统的关系数据库。例如,它可以帮助将模型中的部分内容从 RDBMS 迁移到更适合典型用例的特定 NoSQL 解决方案中。

我们目前拥有的功能

目前,Hibernate OGM 还不支持所有这些目标。以下是我们拥有的功能列表

  • 将数据存储在键/值存储(Infinispan 和 Ehcache)中

  • 将数据存储在文档存储(MongoDB 和 CouchDB - 后者处于预览阶段)中

  • 将数据存储在图数据库 (Neo4J) 中

  • 为实体创建、读取、更新和删除操作 (CRUD)

  • 多态实体(支持超类、子类等)。

  • 可嵌入对象(又名组件)

  • 支持基本类型(数字、字符串、URL、日期、枚举等)

  • 支持关联

  • 支持集合 (Set、List、Map 等)

  • 支持 JPQL 查询(但不支持任意联接)

  • 支持将本机查询结果映射到管理的实体

  • 支持 Hibernate Search 的全文查询

  • 通常,支持 JPA 和本机 Hibernate ORM API 支持

简而言之,一个适用于多个流行 NoSQL 数据存储的完全有能力的对象映射器。

实验性功能

由于 Hibernate OGM 是一个相当年轻的项目,因此它的一些部分可能被标记为实验性。这可能会影响特定的 API 或 SPI(例如,目前 SchemaInitializer SPI 合同的情况)、整个方言(例如,目前 CouchDB 方言的情况)或交付成果。

实验性 API/SPI 通过 @Experimental 注释进行标记。实验性方言通过其数据存储名称(例如,“COUCHDB_EXPERIMENTAL”)使这一点变得明显,实验性交付成果使用“experimental”工件分类器。

如果某个部分被标记为实验性,则它可能会在将来的版本中经历向后不兼容的更改。例如,API/SPI 方法可能会被修改,因此使用它们的代码也需要进行调整。对于实验性方言,数据的持久格式可能会发生更改,因此,此类方言的未来版本可能无法读取先前版本写入的数据。因此,可能需要手动更新受影响的数据。实验性交付成果应谨慎使用,因为它们尚处于开发阶段。您应该将它们用于测试,而不是用于生产用例。

但是我们的大多数方言都已成熟,所以不用担心;)

用例

以下是一些 Hibernate OGM 可能有益的领域

  • 需要快速扩展数据存储(通过底层 NoSQL 数据存储的功能)

  • 使域模型独立于底层数据存储技术(RDBMS、Infinispan、NoSQL)

  • 探索最适合用例的工具

  • 对数据存储使用熟悉的 JPA 前端

  • 使用 Hibernate Search 的全文搜索/文本分析功能并将数据集存储在可扩展的数据存储中

这些只是一些想法,随着我们为 Hibernate OGM 添加更多功能,这个列表会不断增长。

1. 如何获取帮助和贡献 Hibernate OGM

Hibernate OGM 是一个年轻的项目。加入我们并帮助我们塑造它!

1.1. 如何获取帮助

首先,请确保阅读本参考文档。这是最全面的正式信息来源。当然,它并不完美:欢迎您随时提出问题、评论或提出改进建议,您可以在我们的 Hibernate OGM 论坛 中进行。

您还可以

  • JIRA 中提交错误报告

  • 开发邮件列表 上提出改进建议

  • 加入我们的 IRC 频道,讨论开发和改进(freenode.net 上的 #hibernate-dev;您需要在 freenode 注册:该频道不接受“匿名”用户)。

1.2. 如何贡献

欢迎!

有很多方法可以贡献:

  • JIRA 中提交错误报告

  • 在论坛、IRC 或开发邮件列表中提供反馈

  • 改进文档

  • 修复错误或贡献新功能

  • 为您的首选 NoSQL 引擎提出并编写数据存储方言

Hibernate OGM 的代码在 GitHub 上 https://github.com/hibernate/hibernate-ogm 上可用。

1.2.1. 如何构建 Hibernate OGM

Hibernate OGM 使用 Git 和 Maven 3,请确保在您的系统上安装了这两个软件。

从 GitHub 克隆 git 仓库

#get the sources
git clone https://github.com/hibernate/hibernate-ogm
cd hibernate-ogm

运行 maven

#build project
mvn clean install -s settings-example.xml

请注意,Hibernate OGM 使用了 JBoss 托管的 Maven 仓库中的工件。请确保使用 -s settings-example.xml 选项或根据 此 jboss.org wiki 页面 上的描述调整您的 ~/.m2/settings.xml

这些设置是开发 Hibernate OGM 所必需的,但使用它时可能不需要。

要跳过构建文档,请将 skipDocs 属性设置为 true

mvn clean install -DskipDocs=true -s settings-example.xml

如果您只想构建文档,请从 hibernate-ogm-documentation/manual 子目录运行它。

1.2.2. 如何有效地贡献代码

分享代码的最佳方式是在 GitHub 上为 Hibernate OGM 仓库创建分支,在准备就绪时创建一个分支并打开一个拉取请求。在提交拉取请求之前,请确保将其重新定位到主分支的最新版本。

以下是团队遵循的几种方法

  • 我们为每个代码更改执行小的独立提交。特别是,我们不会在同一个提交中混合代码样式更改(导入、拼写错误等)和新功能。

  • 提交消息遵循以下约定:JIRA 问题编号、简短的提交摘要、空行、如有必要,更详细的说明。请确保将行长限制在 80 个字符,即使在今天这个时代,它也能使提交注释更易读。

OGM-123 Summary of commit operation

Optional details on the commit
and a longer description can be
added here.
  • 一个拉取请求可以包含多个提交,但应该自包含:包含实现、单元测试、文档和 javadoc 更改(如有必要)。

  • 所有提交都通过拉取请求提出,并在推送到参考仓库之前由团队中的另一名成员进行审查。没错,我们永远不会在没有代码审查的情况下直接提交到上游。

1.3. 如何构建数据存储支持

高级部分

这是一个高级主题,如果您没有构建数据存储,可以随意跳过此部分。

Hibernate OGM 通过使用 DatastoreProviderGridDialect 对各种数据存储进行抽象来支持它们。支持的功能因数据存储而异,方言不必实现所有功能。Hibernate OGM 实施了 TCK(技术兼容性工具包)来验证方言的互操作性和功能。Hibernate OGM 支持各种文档和键值存储,并附带一些用于文档和键值存储的抽象和实用程序类(如 KeyValueStorePropertiesDocumentStoreProperties)。

1.3.1. 数据存储提供程序

支持数据存储通常从 DatastoreProvider 开始。提供程序可以实现生命周期(startstop)来初始化、配置和关闭资源。查看现有的数据存储支持,例如 MongoDB(参见 org.hibernate.ogm.datastore.mongodb.impl.MongoDBDatastoreProvider)是了解如何启动数据存储支持的一个好方法。提供程序被视为服务,它们可以实现各种服务接口来激活某些功能(有关详细信息,请参见 org.hibernate.service.spi 包)。

在实现新数据存储时,通常会遇到的一个问题是事务性。一些数据存储提供事务支持,可以在 JTA 包裹的 Hibernate OGM 上下文中使用。如果您的数据存储不支持事务,则可以在 DatastoreProvider 中启用事务模拟。

DatastoreProvider 的功能

  • 资源生命周期

  • 管理连接资源

  • 配置

  • 访问查询解析器

  • 定义/验证模式

1.3.2. 方言

数据存储可以有一个或多个方言。方言描述了数据如何映射到特定数据存储的样式。NoSQL 数据存储意味着某种性质,即如何映射数据。面向文档的数据存储鼓励使用实体作为文档的模式,其中嵌入式数据结构可以存储在文档本身中。键值数据存储允许使用不同的方法,例如将实体存储为 JSON 文档,或甚至存储将实体映射到哈希表数据结构中的各个键值对。Hibernate OGM 允许每个数据存储使用多个方言,用户可以选择最合适的方言。

通过实现 GridDialect 接口来提供最基本的支持。必须实现该接口才能支持特定的数据存储。

GridDialect 通常支持

  • 实体的创建/读取/更新/删除

  • 关联的创建/读取/更新/删除

  • Id/序列生成器

  • 提供锁定策略

方言可以选择实现一个或多个其他方面接口,以提供更广泛的某些功能支持

  • QueryableGridDialect

  • BatchableGridDialect

  • IdentityColumnAwareGridDialect

  • OptimisticLockingAwareGridDialect

  • MultigetGridDialect

QueryableGridDialect 的功能

  • 查询执行

  • 支持原生查询

BatchableGridDialect 的功能

  • 操作排队

  • 将排队的创建/更新/删除作为批处理执行

IdentityColumnAwareGridDialect 的功能

  • 支持在数据插入时生成标识值

OptimisticLockingAwareGridDialect 的功能

  • 以原子方式查找和更改版本化记录

MultigetGridDialect 的功能

  • 在一次操作中检索多个元组

在开始之前,请明确计划如何将实体、关系和嵌套结构最佳地表示在您计划实施的 NoSQL 存储中。了解这一点有助于您形成清晰的认识,这将需要您对您计划支持的 NoSQL 数据库有一定的经验。

从一个小的功能集开始,体验一下 Hibernate OGM,例如,只实现 CRUD 操作,并忽略关系和查询。您可以随时在继续操作时扩展功能。

从现有的方言开始或学习现有的方言也是一种有趣的策略。不过,对于复杂的方言来说,这可能很令人生畏。

Hibernate OGM 对特定数据存储的数据存储/加载方式没有偏见,但特定的方言却有。Hibernate OGM 努力实现最自然的映射风格。其理念是通过坚持该存储的既定模式和惯例,促进与该数据库的其他应用程序的集成。

1.3.3. 实体

方言将实体视为 TupleTuple 包含

  • 一个快照(即从数据库加载的数据的视图),

  • 一组携带实际数据的键值对,

  • 以及要应用于原始快照的操作列表。元组键使用点路径属性标识符来指示嵌套。这在使用文档存储时非常有用,因为您可以根据这些详细信息构建文档结构。

1.3.4. 关联

大多数 NoSQL 数据存储没有内置的实体之间关联的支持(除非您使用的是图数据库)。

Hibernate OGM 通过存储从给定实体到其关联实体(列表)的导航信息来模拟对没有支持的数据存储的关联。可以将其视为查询物化。此导航信息数据可以存储在实体本身内,也可以存储在外部(作为自己的文档或关系项)。

1.3.5. 配置

Hibernate OGM 可以从各种来源读取其配置属性。最常见的配置来源是

  • hibernate.properties 文件

  • persistence.xml 文件

  • 环境变量覆盖或集成在上述配置文件中设置的属性

  • 注释配置(实体类)

  • 编程配置

org.hibernate.ogm.options 包提供配置基础结构。

您可能想查看 MongoDBConfigurationInfinispanConfiguration,以了解配置的工作原理。配置通常在启动数据存储提供程序或操作时读取。访问运行时配置的一个很好的例子是关联存储选项,用户可以在其中定义如何存储特定关联(在实体内或作为单独的集合/键/文档/节点)。

配置和选项上下文基础结构允许支持数据存储特定的选项,例如 MongoDB 的 ReadPreference 或 Redis 的 TTL

编程配置

数据存储支持可以实现编程配置。配置分为三个部分

  • 全局配置

  • 实体配置

  • 属性配置

编程配置包含两部分:配置接口(参见 org.hibernate.ogm.options.navigation)和部分(抽象)实现类。这些部分在运行时使用 ASM 类生成进行合并。

1.3.6. 类型

每个数据存储都支持一组独特的数据类型。一些存储支持浮点类型和日期类型,而另一些则只支持字符串。Hibernate OGM 允许用户为其数据模型使用各种数据类型(参见 JPA 规范)。另一方面,这些数据需要存储在数据存储中并映射回来。

方言可以提供 GridType 来描述特定数据类型的处理方式,这意味着您可以指定日期、浮点类型甚至字节数组的处理方式。它们是映射到其他数据类型(例如,使用 double 来表示 float 或使用 base64 编码的字符串来表示字节数组),还是包装在字符串中。

数据存储特定的类型可以以相同的方式处理,查看 StringAsObjectIdType,了解 MongoDB 的 ObjectId 类型的字符串映射。

类型映射可能是一项繁琐的任务。整个类型处理处于不断变化之中,并且会随着 Hibernate OGM 的发展而发生变化。如果您不确定,请询问。

1.3.7. 测试

Hibernate OGM 提供了一个非常适合测试的基础设施。测试基础设施由通用的基类(用于 OGM 的 OgmTestCase 和用于 JPA 的 JpaTestCase)以及测试助手(参见 GridDialectTestHelper)组成。这些类用于获取与 SessionEntityManager 的前端视图不同的数据视图。

为不同的场景创建一组自己的测试用例总是很有帮助,以验证数据是否按预期方式映射,或者验证特定于数据存储的选项,例如 TTL

另一组测试称为后端 TCK。这些测试类从用户的角度测试 Hibernate OGM 的几乎所有方面。测试包含简单/复杂实体、关联、列表和映射数据类型、使用 Hibernate Search 的查询以及数据类型支持的测试用例。

后端 TCK 使用类路径过滤器包含,只需检查当前实现之一(例如 RedisBackendTckHelper)。当您开发一个包含在发行版中的核心模块时,您需要将您的方言添加到某些测试的 @SkipByGridDialect 注解中。

成功运行 20% 的测试是一个巨大的成就。逐步进行。大量的测试可能会失败,仅仅是因为一个处理方式不同的问题。不要犹豫,随时寻求帮助。

2. Hibernate OGM 入门

如果您熟悉 JPA,那么您几乎可以开始了。尽管如此,我们将带您完成使用 Hibernate OGM 持久化和检索实体的最初几个步骤。

在开始之前,请确保您已配置以下工具:

  • Java JDK 8

  • Maven 3.2.3 或更高版本

Hibernate OGM 发布在 Maven 中央仓库中。

org.hibernate.ogm:hibernate-ogm-bom:5.4.1.Final 添加到您的依赖管理块中,并将 org.hibernate.ogm:hibernate-ogm-infinispan-embedded:5.4.1.Final 添加到您的项目依赖项中。

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

<dependencies>
    <dependency>
        <groupId>org.hibernate.ogm</groupId>
        <artifactId>hibernate-ogm-infinispan-embedded</artifactId>
    </dependency>
</dependencies>

前者是一个所谓的“物料清单”POM,它指定了 Hibernate OGM 及其依赖项的匹配版本集。这样,您就无需在依赖项块中显式指定版本,而是会自动从 BOM 中获取版本。

如果您将应用程序部署到 WildFly 14.0 上,则无需将 Hibernate OGM 模块添加到您的部署单元中,而是可以将其作为模块添加到应用程序服务器中。请参考 如何为 WildFly 14.0 打包 Hibernate OGM 应用程序 了解更多信息。

在本教程中,我们将使用 JPA API。虽然 Hibernate OGM 依赖于 JPA 2.1,但在 Maven POM 文件中被标记为“已提供”。如果您在 Java EE 容器之外运行,请确保显式添加依赖项。

<dependency>
    <groupId>org.hibernate.javax.persistence</groupId>
    <artifactId>hibernate-jpa-2.1-api</artifactId>
</dependency>

现在让我们映射我们的第一个 Hibernate OGM 实体。

@Entity
public class Dog {
   @Id @GeneratedValue(strategy = GenerationType.TABLE, generator = "dog")
   @TableGenerator(
      name = "dog",
      table = "sequences",
      pkColumnName = "key",
      pkColumnValue = "dog",
      valueColumnName = "seed"
   )
   public Long getId() { return id; }
   public void setId(Long id) { this.id = id; }
   private Long id;

   public String getName() { return name; }
   public void setName(String name) { this.name = name; }
   private String name;

   @ManyToOne
   public Breed getBreed() { return breed; }
   public void setBreed(Breed breed) { this.breed = breed; }
   private Breed breed;
}

@Entity
public class Breed {

   @Id @GeneratedValue(generator = "uuid")
   @GenericGenerator(name="uuid", strategy="uuid2")
   public String getId() { return id; }
   public void setId(String id) { this.id = id; }
   private String id;

   public String getName() { return name; }
   public void setName(String name) { this.name = name; }
   private String name;
}

我骗了你,我们已经映射了两个实体!

如果您熟悉 JPA,您会发现我们的映射中没有任何特定于 Hibernate OGM 的内容。

在本教程中,我们将使用 JBoss 事务作为我们的 JTA 事务管理器。因此,让我们将 JTA API 和 JBoss 事务也添加到我们的 POM 中。

我们还将添加 Hibernate Search,因为 Infinispan 方言需要它来运行 JPQL 查询,但我们将把细节留到下次讨论 (使用 Hibernate Search)。

最终的依赖项列表应如下所示:

<dependencies>
    <!-- Hibernate OGM Infinispan module; pulls in the OGM core module -->
    <dependency>
        <groupId>org.hibernate.ogm</groupId>
        <artifactId>hibernate-ogm-infinispan-embedded</artifactId>
    </dependency>

    <1-- Optional, needed to run JPQL queries on some datastores -->
    <dependency>
        <groupId>org.hibernate.search</groupId>
        <artifactId>hibernate-search-orm</artifactId>
    </dependency>

    <!-- Standard APIs dependencies - provided in a Java EE container -->
    <dependency>
        <groupId>org.hibernate.javax.persistence</groupId>
        <artifactId>hibernate-jpa-2.1-api</artifactId>
    </dependency>
    <dependency>
        <groupId>org.jboss.spec.javax.transaction</groupId>
        <artifactId>jboss-transaction-api_1.2_spec</artifactId>
    </dependency>

    <!-- Add the Narayana Transactions Manager
         an implementation would be provided in a Java EE container,
         but this works nicely in Java SE as well -->
    <dependency>
        <groupId>org.jboss.narayana.jta</groupId>
        <artifactId>narayana-jta</artifactId>
    </dependency>
    <dependency>
        <groupId>org.jboss</groupId>
        <artifactId>jboss-transaction-spi</artifactId>
    </dependency>
</dependencies>

接下来,我们需要定义持久化单元。创建一个 META-INF/persistence.xml 文件。

<?xml version="1.0"?>
<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_2_0.xsd"
             version="2.0">

    <persistence-unit name="ogm-jpa-tutorial" transaction-type="JTA">
        <!-- Use the Hibernate OGM provider: configuration will be transparent -->
        <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
        <properties>
            <!-- Here you will pick which NoSQL technology to use, and configure it;
                 in this example we start a local in-memory Infinispan node. -->
            <property name="hibernate.ogm.datastore.provider" value="infinispan_embedded"/>
        </properties>
    </persistence-unit>
</persistence>

现在让我们持久化一组实体并检索它们。

//accessing JBoss's Transaction can be done differently but this one works nicely
TransactionManager tm = com.arjuna.ats.jta.TransactionManager.transactionManager();

//build the EntityManagerFactory as you would build in in Hibernate ORM
EntityManagerFactory emf = Persistence.createEntityManagerFactory(
    "ogm-jpa-tutorial");

final Logger logger = LoggerFactory.getLogger(DogBreedRunner.class);

[..]

//Persist entities the way you are used to in plain JPA
tm.begin();
logger.infof("About to store dog and breed");
EntityManager em = emf.createEntityManager();
Breed collie = new Breed();
collie.setName("Collie");
em.persist(collie);
Dog dina = new Dog();
dina.setName("Dina");
dina.setBreed(collie);
em.persist(dina);
Long dinaId = dina.getId();
em.flush();
em.close();
tm.commit();

[..]

//Retrieve your entities the way you are used to in plain JPA
tm.begin();
logger.infof("About to retrieve dog and breed");
em = emf.createEntityManager();
dina = em.find(Dog.class, dinaId);
logger.infof("Found dog %s of breed %s", dina.getName(), dina.getBreed().getName());
em.flush();
em.close();
tm.commit();

[..]

emf.close();

可以在 Hibernate OGM 的发行版中找到一个可运行的示例,位于 hibernate-ogm-documentation/examples/gettingstarted 目录下。

我们看到了什么?

  • Hibernate OGM 是一个 JPA 实现,用于映射和 API 使用。

  • 它被配置为一个特定的 JPA 提供程序:org.hibernate.ogm.jpa.HibernateOgmPersistence

让我们在接下来的章节中进一步探索。

Hibernate OGM 可能还需要 Hibernate Search 位于类路径中。这取决于您要在项目中使用的方言或功能,您将在接下来的章节中找到更多详细信息。

如果您想将 Hibernate OGM 与 WildFly 14.0 一起使用,您将需要一些额外的配置,您可以在 如何为 WildFly 14.0 打包 Hibernate OGM 应用程序 段落中找到所有详细信息。

3. 架构

Hibernate OGM 定义了一个由 DatastoreProviderGridDialect 表示的抽象层,以将 OGM 引擎与数据存储交互分离。它已经成功地抽象了各种键值存储、文档存储和图形数据库。我们正在努力在其他 NoSQL 家族中测试它。

在本节中,我们将探讨:

  • 通用架构

  • 数据如何持久化到 NoSQL 数据存储中

  • 我们如何支持 JPQL 查询

让我们从通用架构开始。

3.1. 通用架构

Hibernate OGM 通过重用几个关键组件成为可能:

  • 用于 JPA 支持的 Hibernate ORM

  • 用于与底层数据存储交互的 NoSQL 驱动程序

  • 可选地,用于索引和查询目的的 Hibernate Search

  • 可选地,Infinispan 的 Lucene 目录,将索引存储在 Infinispan 本身中,或者使用 Infinispan 的写通缓存存储存储在许多其他 NoSQL 中。

  • Hibernate OGM 本身

ogm architecture
图 2. 通用架构

Hibernate OGM 尽可能多地重用 Hibernate ORM 基础设施。无需重写新的 JPA 引擎。PersisterLoader(Hibernate ORM 使用的两个接口)已被重写以将数据持久化到 NoSQL 存储中。这些实现是 Hibernate OGM 的核心。我们将在 如何持久化数据 中看到数据是如何结构化的。

NoSQL 存储之间的特殊性通过 DatastoreProviderGridDialect 的概念抽象出来。

  • DatastoreProvider 抽象了如何启动和维护 Hibernate OGM 与数据存储之间的连接。

  • GridDialect 抽象了数据本身(包括关联)是如何持久化的。

将它们视为我们 NoSQL 存储的 JDBC 层。

除了这些之外,所有创建/读取/更新/删除 (CRUD) 操作都是由 Hibernate ORM 引擎实现的(对象水化和脱水、级联、生命周期等)。

截至目前,我们已经实现了以下数据存储提供程序:

  • 基于 HashMap 的数据存储提供程序(用于测试)

  • Infinispan 嵌入式数据存储提供程序,用于将您的实体持久化到在同一 JVM 中运行的 Infinispan 实例中。

  • Infinispan 远程数据存储提供程序,用于将您的实体持久化到通过远程 Hot Rod 客户端访问的 Infinispan 中。

  • 基于 Ehcache 的数据存储提供程序,用于将您的实体持久化到 Ehcache 中。

  • 基于 MongoDB 的数据存储提供程序,用于将数据持久化到 MongoDB 数据库中。

  • 基于 Neo4j 的数据存储提供程序,用于将数据持久化到 Neo4j 图形数据库中。

  • 基于 CouchDB 的数据存储提供程序,用于将数据持久化到 CouchDB 文档存储中(实验性)。

  • 基于 Cassandra 的数据存储提供程序,用于将数据持久化到 Apache Cassandra 中(实验性)。

  • 基于 Redis 的数据存储提供程序,用于将数据持久化到 Redis 键值存储中(实验性)。

  • 基于 Ignite 的数据存储提供程序,用于将数据持久化到 Apache Ignite 中(实验性)。

为了实现 JPQL 查询,Hibernate OGM 解析 JPQL 字符串并调用相应的翻译函数来构建原生查询。由于并非所有 NoSQL 技术都支持查询,因此在缺少查询的情况下,我们可以使用 Hibernate Search 作为外部查询引擎。

我们将在 如何查询数据 中更详细地讨论查询主题。

Hibernate OGM 最适合在 JTA 环境中工作。最简单的解决方案是将其部署到 Java EE 容器中。或者,您可以使用独立的 JTA TransactionManager。我们在 独立 JTA 环境中 解释了如何操作。

现在让我们看看数据如何以及以何种结构持久化到 NoSQL 数据存储中。

3.2. 如何持久化数据

Hibernate OGM 尽可能多地重用关系模型概念,至少在这些概念在 OGM 的情况下实用且有意义时。出于充分的理由,关系模型在 30 多年前为数据库领域带来了和平。特别是,Hibernate OGM 继承了以下特性:

  • 应用程序对象模型与持久化数据模型之间的抽象

  • 将数据持久化为基本类型

  • 保留主键的概念来寻址实体

  • 保留外键的概念来链接两个实体(未强制执行)

如果应用程序数据模型与持久化数据模型过于紧密耦合,就会出现一些问题:

  • 应用程序对象层次结构/组合的任何更改都必须反映在持久化数据中。

  • 应用程序对象模型的任何更改都需要在数据级别进行迁移。

  • 另一个应用程序对数据的任何访问都会将两个应用程序捆绑在一起,从而失去灵活性。

  • 从另一个平台访问数据会变得更加困难。

  • 序列化实体会导致许多其他问题(见下文说明)。

为什么实体不会被序列化到键值条目中?

直接在数据存储中序列化实体(特别是键值存储)会导致一些问题,原因有以下几点:

  • 当实体指向其他实体时,您是否存储了整个图?提示:这可能相当大!

  • 如果这样做,您如何保证重复对象之间的对象标识甚至一致性?从不同的根对象存储相同的对象图可能是有意义的。

  • 如果类模式发生变化会发生什么?如果添加或删除属性或包含超类,您必须迁移数据存储中的所有实体,以避免反序列化问题。

实体由 Hibernate OGM 存储为值元组。更准确地说,每个实体在概念上都由一个 Map<String,Object> 表示,其中键表示列名(通常是属性名,但不总是),值表示列值作为基本类型。我们更倾向于使用基本类型而不是复杂类型,以提高可移植性(跨平台以及跨类型/类模式随时间的演变)。例如,一个 URL 对象存储为它的字符串表示形式。

标识给定实体实例的键由以下部分组成:

  • 表名

  • 主键列名

  • 主键列值

data entity
图 3. 存储实体

然后,您目标 NoSQL 数据存储的特定 GridDialect 负责将此映射转换为最自然的模型:

  • 对于键值存储或数据网格,我们使用逻辑键作为网格中的键,并将映射存储为值。请注意,这是一个近似值,一些键值提供程序会使用更量身定制的方法。

  • 对于面向文档的存储,映射由一个文档表示,映射中的每个条目对应于文档中的一个属性。

关联也存储为元组。Hibernate OGM 存储了从一个实体导航到其关联所需的信息。这与纯粹的关系模型有所不同,但它确保关联数据可以通过基于我们要从中导航的实体元组中包含的信息的键查找来访问。请注意,这会导致一定程度的重复,因为必须为关联的两端存储信息。

存储关联数据的键由以下部分组成:

  • 表名

  • 表示我们来自的实体的外键列名

  • 表示我们来自的实体的外键列值

使用这种方法,我们更倾向于快速读取和(稍慢的)写入。

data association
图 4. 存储关联

请注意,这种方法有优点也有缺点。

  • 它确保所有 CRUD 操作都可以通过键查找来完成。

  • 它优先考虑读取而不是写入(对于关联)。

  • 但它会复制数据。

同样,特定 NoSQL 存储中数据的存储方式也存在特殊性。例如,在面向文档的存储中,关联信息(包括关联实体的标识符)可以存储在拥有关联的实体中。这对于文档来说是一个更自然的模型。

data association document
图 5. 在文档存储中存储关联

某些标识符需要在数据存储中存储一个种子(例如序列)。种子存储在键由以下部分组成的值的存储中:

  • 表名

  • 表示段的列名。

  • 表示段的列值。

此描述是 Hibernate OGM 在概念上如何要求数据存储提供程序存储数据的。根据数据存储的类型甚至具体数据存储,存储方式会尽可能优化为最自然的方式。换句话说,就像你自然地存储特定结构一样。请确保查看专门针对你目标 NoSQL 存储的章节以了解其特殊性。

许多 NoSQL 存储没有模式的概念。同样,Hibernate OGM 存储的元组也不与特定模式绑定:元组由 Map 表示,而不是与特定实体类型相关的类型化 Map。但是,JPA 确实通过以下方式描述了模式:

  • 类模式。

  • JPA 物理注释,如 @Table@Column

虽然它与应用程序绑定,但当模式发生变化时,它提供了某种程度的稳健性和显式理解,因为模式就在开发人员的眼前。这是严格类型化关系模型和一些 NoSQL 家族推崇的完全无模式方法之间的中间模型。

3.3. 使用序列生成 ID

您可以使用以下注释与序列一起使用:

  • @SequenceGenerator:它将在可用时使用原生序列。

  • @TableGenerator:它将模拟序列,并将值存储在最合适的数据结构中;例如 MongoDB 中的文档或 Neo4j 中的节点。

在处理序列生成时,请牢记以下几点:

  • @TableGenerator 是当底层数据存储不支持原生序列生成时使用的后备方法。

  • 如果数据存储不支持原子操作并且不支持原生序列,Hibernate OGM 将在引导时抛出异常并建议其他方法。

  • 序列的映射可能会根据使用的注释而改变,您应该查看与您使用方言相关的文档中的映射段落。

  • 在数据存储中保存的值可能不是序列中的下一个值。

3.4. 如何查询数据

由于 Hibernate OGM 想要提供所有 JPA 功能,因此它需要支持 JPQL 查询。Hibernate OGM 解析 JPQL 查询字符串并提取其含义。从那里,根据你目标 NoSQL 存储的功能,有几种选择:

  • 它直接将原生查询生成委托给数据存储特定的查询转换器实现。

  • 它使用 Hibernate Search 作为查询引擎来执行查询。

如果 NoSQL 数据存储具有一些查询功能,并且 JPQL 查询足够简单,可以由数据存储执行,那么 JPQL 解析器会直接将查询生成推送到 NoSQL 特定的查询转换器。查询返回匹配的实体列或投影列表,Hibernate OGM 返回托管实体。

一些 NoSQL 存储的查询支持很差,或者根本没有查询支持。在这种情况下,Hibernate OGM 可以使用 Hibernate Search 作为其索引和查询引擎。Hibernate Search 能够索引和查询对象 - 实体 - 并运行全文查询。它使用众所周知的 Apache Lucene 来完成此操作,但添加了一些有趣的功能,例如集群支持和面向对象的抽象,包括面向对象的查询 DSL。让我们看看 Hibernate OGM 使用 Hibernate Search 时的架构。

ogm architecture with hsearch
图 6. 使用 Hibernate Search 作为查询引擎 - 灰色区域是 Hibernate OGM 架构中已经存在的块。

在这种情况下,Hibernate ORM Core 将更改事件推送到 Hibernate Search,Hibernate Search 会相应地索引实体并保持索引和数据存储同步。JPQL 查询解析器将查询转换委托给 Hibernate Search 查询转换器,并在 Lucene 索引之上执行查询。索引可以通过多种方式存储:

  • 在文件系统上(Lucene 中的默认值)。

  • 在 Infinispan 中,通过 Infinispan Lucene 目录实现:索引然后透明地在多个服务器上进行分布。

  • 在能够原生存储 Lucene 索引的 NoSQL 存储中。

  • 在可以用作 Infinispan 溢出的 NoSQL 存储中:在这种情况下,Infinispan 用作中间层以高效地提供索引,但在另一个 NoSQL 存储中持久化索引。

即使你打算使用 NoSQL 数据存储查询功能,你也可以使用 Hibernate Search。Hibernate Search 提供了一些有趣的选择:

  • 可集群性。

  • 全文查询 - 例如你的实体的 Google。

  • 地理空间查询。

  • 查询分面(例如按价格、品牌等对查询结果进行动态分类)。

4. 配置和启动 Hibernate OGM

Hibernate OGM 优先使用易用性和约定优于配置。这使得它的配置在默认情况下非常简单。

4.1. 引导 Hibernate OGM

Hibernate OGM 可以通过 Hibernate 原生 API(Session)或通过 JPA API(EntityManager)使用。根据你的选择,引导策略略有不同。

4.1.1. 使用 JPA

如果你使用 JPA 作为你的主要 API,配置非常简单。Hibernate OGM 被视为一个持久性提供程序,你需要在你的 persistence.xml 中配置它。就是这样!提供程序名称为 org.hibernate.ogm.jpa.HibernateOgmPersistence

示例 1. persistence.xml 文件
<?xml version="1.0"?>
<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_2_0.xsd"
             version="2.0">

    <persistence-unit name="org.hibernate.ogm.tutorial.jpa" transaction-type="JTA">
        <!-- Use Hibernate OGM provider: configuration will be transparent -->
        <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
        <properties>
            <property name="hibernate.transaction.jta.platform"
                      value="JBossTS" />
            <property name="hibernate.ogm.datastore.provider"
                      value="infinispan_embedded" />
        </properties>
    </persistence-unit>
</persistence>

需要注意的是:

  • 没有 JDBC 方言设置。

  • 除了有时 jta-data-source 之外,没有 JDBC 设置(有关更多信息,请查看 在 Java EE 容器中)。

  • 大多数 NoSQL 数据库不需要模式,在这种情况下,模式生成选项(hbm2ddl)不适用。

  • 如果你使用 JTA(我们推荐),你需要设置 JTA 平台。

你还需要配置要使用哪个 NoSQL 数据存储以及如何连接到它。我们将在后面的 NoSQL 数据存储 中详细说明如何操作。

在本例中,我们使用了 Infinispan 的默认设置:这将启动一个本地、内存中的 Infinispan 实例,这对测试很有用,但存储的数据将在关闭时丢失。你可能认为这种配置类似于将你的数据存储在哈希表中,但你当然可以改变 Infinispan 配置以启用集群(用于可扩展性和故障转移)并启用永久性持久化策略。

从那里,只需像你习惯使用 Hibernate ORM 一样引导 JPA:

  • 通过 Persistence.createEntityManagerFactory

  • 通过在 Java EE 容器中注入 EntityManager / EntityManagerFactory

  • 通过使用你喜欢的注入框架(CDI - Weld、Spring、Guice)。

请注意,你正在启动的不是一个奇特的新 JPA 实现,而是所有效果上都是 Hibernate ORM 的一个实例,尽管使用了一些替代的内部组件来处理 NoSQL 存储。这意味着任何与 Hibernate ORM 集成的框架和工具都可以与 Hibernate OGM 集成 - 当然,只要它没有做出假设,例如将使用 JDBC 数据源。

4.1.2. 使用 Hibernate ORM 原生 API

如果你想使用 Hibernate 原生 API 引导 Hibernate OGM,请使用 Hibernate ORM 5 中的新引导 API。将 OgmProperties.ENABLED 设置为 true,Hibernate OGM 组件将被激活。请注意,将 OgmSessionFactoryBuilder 解包并不是严格需要的,但它将允许你将来设置 Hibernate OGM 特定选项,并且还会为你提供 OgmSessionFactory 而不是 SessionFactory 的引用。

示例 2. 使用 Hibernate ORM 原生 API 引导 Hibernate OGM
StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
    .applySetting( OgmProperties.ENABLED, true )
    //assuming you are using JTA in a non container environment
    .applySetting( AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, "jta" )
    //assuming JBoss TransactionManager in standalone mode
    .applySetting( AvailableSettings.JTA_PLATFORM, "JBossTS" )
    //assuming Infinispan as the backend, using the default settings
    .applySetting( OgmProperties.DATASTORE_PROVIDER, InfinispanEmbedded.DATASTORE_PROVIDER_NAME );
    .build();

//build the SessionFactory
OgmSessionFactory sessionFactory = new MetadataSources( registry )
    .addAnnotatedClass( Order.class )
    .addAnnotatedClass( Item.class )
    .buildMetadata()
    .getSessionFactoryBuilder()
    .unwrap( OgmSessionFactoryBuilder.class )
    .build();

需要注意的是:

  • 没有 DDL 模式生成选项(hbm2ddl),因为 Infinispan 在嵌入模式下运行时不需要模式。

  • 如果你使用基于 JTA 的事务策略,你需要设置正确的事务策略和正确的事务管理器查找策略(参见 环境)。

你还需要配置要使用哪个 NoSQL 数据存储以及如何连接到它。我们将在后面的 NoSQL 数据存储 中详细说明如何操作。在本例中,我们使用了 Infinispan 的默认设置。

4.2. 环境

Hibernate OGM 在各种环境中运行:它应该在所有 Hibernate ORM 运行的环境中都能很好地工作。但是,有一些特定的环境,我们在这些环境中对其进行了比其他环境更彻底的测试。当前版本正在 Java SE(无容器)和 WildFly 14.0 应用程序服务器中定期测试;在撰写本文时,没有已知的理由说明它不能在其他容器中工作,只要你记住它需要特定版本的 Hibernate ORM:一些容器可能打包了冲突的版本。

4.2.1. 在 Java EE 容器中

在这种情况下,你不需要做太多事情。你需要三个特定的设置:

  • 事务协调器类型。

  • JTA 平台。

  • JTA 数据源。

如果你使用 JPA,只需将 transaction-type 设置为 JTA,事务工厂将为你设置。

如果你只使用 Hibernate ORM 原生 API,那么将 hibernate.transaction.coordinator_class 设置为 "jta"。

将 JTA 平台设置为正确的 Java EE 容器。属性为 hibernate.transaction.jta.platform,必须包含查找实现的完全限定类名。可用值的列表在 Hibernate ORM 的配置部分 中列出。例如,在 WildFly 14.0 中,你应该选择 JBossAS,尽管在 WildFly 中这些设置是自动注入的,所以你可以跳过它。

在你的 persistence.xml 中,你通常需要定义一个现有的数据源。Hibernate OGM 不需要这个:它会忽略数据源,但 JPA 规范要求设置。

示例 3. persistence.xml 文件
<?xml version="1.0"?>
<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_2_0.xsd"
             version="2.0">

    <persistence-unit name="org.hibernate.ogm.tutorial.jpa" transaction-type="JTA">
        <!-- Use Hibernate OGM provider: configuration will be transparent -->
        <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
        <jta-data-source>java:/DefaultDS</jta-data-source>
        <properties>
            <property name="hibernate.transaction.jta.platform" value="JBossAS" />
            <property name="hibernate.ogm.datastore.provider" value="infinispan_embedded" />
        </properties>
    </persistence-unit>
</persistence>

java:DefaultDS 将适用于开箱即用的 WildFly 部署。

4.2.2. 在独立的 JTA 环境中

Java 社区对 JTA 存在一些常见的误解:

  • JTA 很难使用。

  • 只有在需要跨越多个数据库的事务时才需要 JTA。

  • JTA 仅在 Java EE 中有效。

  • JTA 比 "简单" 事务慢。

这些都不正确:让我向你展示如何在独立环境中使用 Narayana Transactions Manager 与 Hibernate OGM。

在 Hibernate OGM 中,请确保设置以下属性:

  • 如果你使用 JPA,在你的 persistence.xml 中将 transaction-type 设置为 JTA

  • 或者,如果你使用 StandardServiceRegistryBuilder/OgmConfiguration 引导 Hibernate OGM,将 hibernate.transaction.coordinator_class 设置为 "jta"。

  • 在两种情况下,都将 hibernate.transaction.jta.platform 设置为 JBossTS

将 Narayana Transactions Manager 添加到你的类路径。如果你使用 maven,它应该看起来像这样:

示例 4. Narayana 事务管理器依赖声明
<dependency>
    <groupId>org.jboss.narayana.jta</groupId>
    <artifactId>narayana-jta</artifactId>
    <version>5.5.30.Final</version>
</dependency>

下一步是获取事务管理器的访问权限。最简单的解决方案是按照以下示例操作

TransactionManager transactionManager =
   com.arjuna.ats.jta.TransactionManager.transactionmanager();

然后使用标准 JTA API 来划分你的事务,你就完成了!

示例 5. 使用独立 JTA 来划分你的事务
//note that you must start the transaction before creating the EntityManager
//or else call entityManager.joinTransaction()
transactionManager.begin();

final EntityManager em = emf.createEntityManager();

Poem poem = new Poem();
poem.setName("L'albatros");
em.persist(poem);

transactionManager.commit();

em.clear();

transactionManager.begin();

poem = em.find(Poem.class, poem.getId());
assertThat(poem).isNotNull();
assertThat(poem.getName()).isEqualTo("L'albatros");
em.remove(poem );

transactionManager.commit();

em.close();

并不难吧?请注意,像 Spring Framework 这样的应用程序框架应该能够初始化事务管理器并调用它来为你划分事务。请查看它们各自的文档。

4.2.3. 不使用 JTA

虽然这种方法在今天有效,但它不能保证操作以事务方式进行,因此无法回滚你的工作。这将在将来发生变化,但目前,不建议使用这种环境。

对于不支持事务的 NoSQL 数据存储来说,这就不那么重要了。

4.3. 配置选项

配置 Hibernate OGM 时最重要的选项与数据存储相关。它们在 NoSQL 数据存储 中有解释。

否则,使用 Hibernate OGM 时,Hibernate ORM 和 Hibernate Search 的大多数选项都适用。你可以像往常一样在你的 persistence.xml 文件、你的 hibernate.cfg.xml 文件或以编程方式传递它们。

更有趣的是,有一系列选项**不**适用于 Hibernate OGM,也不应该设置

  • hibernate.dialect

  • hibernate.connection.*,特别是 hibernate.connection.provider_class

  • hibernate.show_sqlhibernate.format_sql

  • hibernate.default_schemahibernate.default_catalog

  • hibernate.use_sql_comments

  • hibernate.jdbc.*

  • hibernate.hbm2ddl.autohibernate.hbm2ddl.import_file

Hibernate Search 与 Hibernate OGM 的集成方式与 Hibernate ORM 相同。测试的 Hibernate Search 版本是 5.10.4.Final。将依赖项添加到你的项目中 - 组 ID 是 org.hibernate,工件 ID 是 hibernate-search-orm

然后配置你想要存储索引的位置,使用相关的索引注释映射你的实体,你就完成了。有关更多信息,请查看 Hibernate Search 参考文档

将 Lucene 索引存储在 Infinispan 中 中,我们将讨论如何将 Lucene 索引存储在 Infinispan 中。即使你不打算将 Infinispan 作为你的主要数据存储,这也很有用。

仅当你需要使用某些数据存储运行 JPQL 或 HQL 查询时,Hibernate OGM 才需要类路径上的 Hibernate Search。这是因为一些数据存储没有查询语言,或者我们还没有支持它。在这种情况下,你需要索引要查询的实体,Hibernate OGM 将把查询转换为 Lucene 查询。查看与你选择的数据存储相关的段落,以查看它是否需要 Hibernate Search。

4.5. 如何为 WildFly 14.0 打包 Hibernate OGM 应用程序

假设你在 WildFly 上部署,还有另一种方法可以将 OGM 依赖项添加到你的应用程序中。

在 WildFly 中,类加载基于模块;该系统定义了对其他模块的显式、非传递依赖项。

模块允许跨多个应用程序共享相同的工件,从而使部署更小、更快,并且还可以部署任何库的多个不同版本。

有关模块的更多详细信息,请参阅 WildFly 中的类加载

在 WildFly 上部署 JPA 应用程序时,你应该知道 WildFly JPA 子系统定义了一些额外的有用配置属性。这些属性记录在 WildFly JPA 参考指南 中。

如果你应用以下说明,你可以创建不包含任何依赖项的小型高效部署,因为你可以将你喜欢的 Hibernate OGM 版本直接包含到容器提供的库集合中。

4.5.1. 为 WildFly 14.0 打包 Hibernate OGM 应用程序

使用 WildFly 时,它包含的几种技术会自动启用。例如,如果你的 persistence.xml 定义了一个使用 Hibernate 作为持久性提供程序的持久性单元(或者没有指定任何提供程序,因为 Hibernate 是默认提供程序),那么 Hibernate ORM 将可用于你的应用程序。

同样,如果应用程序服务器检测到需要 Hibernate Search,Hibernate Search 会自动激活并在用户的应用程序类路径上可用。这是默认行为,但你可以控制并覆盖它;有关可以显式设置的属性完整列表,请参阅 WildFly JPA 参考指南

然而,WildFly 14.0 不包含 Hibernate OGM,需要一些配置才能使其正常工作。

Hibernate OGM 5.4.1.Final 需要 Hibernate ORM 5.3.6.Final 和 Hibernate Search 5.10.4.Final。

与其他版本不同,WildFly 14.0 包含兼容版本的 Hibernate ORM 和 Hibernate Search。因此,将不再需要安装额外的 Hibrnate Search 模块。

通过 Maven 进行服务器配置

Maven 用户可以使用 wildfly-server-provisioning-maven-plugin 创建一个包含 Hibernate OGM 模块的自定义 WildFly 服务器

<plugins>
    <plugin>
        <groupId>org.wildfly.build</groupId>
        <artifactId>wildfly-server-provisioning-maven-plugin</artifactId>
        <version>1.2.10.Final</version>
        <executions>
            <execution>
            <id>server-provisioning</id>
            <goals>
                <goal>build</goal>
            </goals>
            <phase>compile</phase>
            <configuration>
                <config-file>server-provisioning.xml</config-file>
                <server-name>wildfly-with-hibernate-ogm</server-name>
            </configuration>
            </execution>
        </executions>
    </plugin>
</plugins>

你还需要一个位于项目根目录下的 server-provisioning.xml

<server-provisioning xmlns="urn:wildfly:server-provisioning:1.1">
    <feature-packs>

        <feature-pack
            groupId="org.hibernate.ogm"
            artifactId="hibernate-ogm-featurepack-infinispan-remote"
            version="5.4.1.Final" /> (1)

        <feature-pack
            groupId="org.hibernate.ogm"
            artifactId="hibernate-ogm-featurepack-infinispan-embedded"
            version="5.4.1.Final" /> (1)

        <feature-pack
            groupId="org.hibernate.ogm"
            artifactId="hibernate-ogm-featurepack-mongodb"
            version="5.4.1.Final" /> (1)

        <feature-pack
            groupId="org.hibernate.ogm"
            artifactId="hibernate-ogm-featurepack-neo4j"
            version="5.4.1.Final" /> (1)

    </feature-packs>
</server-provisioning>
1 添加一个或多个 Hibernate OGM 特性包,具体取决于你的应用程序需要哪些方言。

拥有这些归档文件后,你需要将它们解压缩到 WildFly 14.0 安装的 modules 文件夹中。包含的模块是

  • org.hibernate.ogm,核心 Hibernate OGM 库。

  • org.hibernate.ogm.<%DATASTORE%>,每个数据存储一个模块,其中 <%DATASTORE%>infinispanmongodb 等中的一个。

  • org.hibernate.orm,Hibernate ORM 库。

  • 几个共享依赖项,例如 org.hibernate.hql:<%VERSION%>(包含查询解析器)和其他依赖项

用于 Hibernate OGM 5.4.1.Final 的模块槽位是 5.4,因为槽位名称的格式不包含项目版本的“微”部分。

现在 WildFly 已经准备就绪,你可以通过两种方式将依赖项包含在你的应用程序中

使用清单文件包含依赖项

将此条目添加到归档文件中的 MANIFEST.MF(将 <%DATASTORE%> 替换为你选择的数据存储的正确值)

Dependencies: org.hibernate.ogm:5.4 services, org.hibernate.ogm.<%DATASTORE%>:5.4 services
使用 jboss-deployment-structure.xml 包含依赖项

这是一个 JBoss 特定描述符。在你的归档文件中添加一个 WEB-INF/jboss-deployment-structure.xml,其中包含以下内容(将 <%DATASTORE%> 替换为你选择的数据存储的正确值)

<jboss-deployment-structure>
    <deployment>
        <dependencies>
            <module name="org.hibernate.ogm" slot="5.4" services="export" />
            <module name="org.hibernate.ogm.<%DATASTORE%>" slot="5.4" services="export" />
        </dependencies>
    </deployment>
</jboss-deployment-structure>

有关描述符的更多信息,请参阅 WildFly 文档

有关 Maven Wildfly 配置插件的更多信息,请参阅 WildFly 配置构建工具

如果你在项目中不使用 Maven,那么官方门户网站上也提供了一个 Gradle 插件 org.wildfly.build.provision

4.5.2. Hibernate OGM WildFly/JBoss 特性包列表

核心特性包

它包含 Hibernate OGM 的核心,所有方言特性包都扩展它。

它扩展了基本的 WildFly 特性包发行版。它包括 Hibernate ORM 5.3.6.Final 模块,Hibernate OGM 5.4.1.Final 需要这些模块。

该特性包发布在 JBoss Nexus 存储库和 Maven Central 上,名称为 org.hibernate.ogm:hibernate-ogm-featurepack-core:5.4.1.Final:zip

Infinispan 远程特性包

这是 Infinispan 远程方言特性包。它包含主模块

  • org.hibernate.ogm.infinispan-remote,包含 Hibernate OGM Infinispan 远程模块

它包含 Hibernate OGM 核心特性包和 Infinispan 客户端 Wildfly 模块。该特性包发布在 JBoss Nexus 存储库和 Maven Central 上,名称为 org.hibernate.ogm:hibernate-ogm-featurepack-infinispan-remote:5.4.1.Final:zip

Infinispan 嵌入式特性包

这是 Infinispan 嵌入式方言特性包。它包含主模块

  • org.hibernate.ogm.infinispan-embedded,包含 Hibernate OGM Infinispan 嵌入式模块

它包含 Hibernate OGM 核心特性包和 Infinispan 客户端 Wildfly 模块。该特性包发布在 JBoss Nexus 存储库和 Maven Central 上,名称为 org.hibernate.ogm:hibernate-ogm-featurepack-infinispan-embedded:5.4.1.Final:zip

MongoDB 特性包

这是 MongoDB 方言特性包。它包含主模块

  • org.hibernate.ogm.mongodb,包含 Hibernate OGM MongoDB 模块

它包含 Hibernate OGM 核心特性包和 MongoDB Java 客户端。该特性包发布在 JBoss Nexus 存储库和 Maven Central 上,名称为 org.hibernate.ogm:hibernate-ogm-featurepack-mongodb:5.4.1.Final:zip

Neo4j 特性包

这是 Neo4j 方言特性包。它包含主模块

  • org.hibernate.ogm.neo4j,包含 Hibernate OGM Neo4j 模块

它包含 Hibernate OGM 核心特性包和 Neo4j 客户端库。该特性包发布在 JBoss Nexus 存储库和 Maven Central 上,名称为 org.hibernate.ogm:hibernate-ogm-featurepack-neo4j:5.4.1.Final:zip

4.5.3. 配置你的 persistence.xml 以使用你选择的持久性提供程序

WildFly 默认情况下会尝试通过查看 persistence.xmlprovider 部分来猜测你需要哪个持久性提供程序。

4.5.4. 启用对 EE 8 的支持

Hibernate OGM 5.4.1.Final 需要 **CDI 2.0** 和 **JPA 2.2**,它们属于 **EE 8** 规范。WildFly 13 支持 JavaEE 8。

但是,为了启用所需的 CDI 和 JPA 版本,我们需要使用 ee8.preview.mode Java 系统属性设置为 **true** 来启动服务器

-Dee8.preview.mode=true

4.5.5. 将 Hibernate OGM 模块与 Infinispan 一起使用

Infinispan 项目还为 WildFly 14.0 提供了自定义模块。如果你打算在 WildFly 上使用 Hibernate OGM/Infinispan 组合,Hibernate OGM 模块需要这些模块。

这个版本的 Hibernate OGM 专门在 Infinispan 版本 9.4.0.Final 上进行了测试;Infinispan 项目通常尝试在同一个主版本和次版本中保持相同的 API 和集成点,因此微版本更新应该是安全的,但没有经过测试。

如果你想尝试更重要的版本升级,你需要编辑 Hibernate OGM 的模块:模块标识符在代表模块的 XML 文件中是硬编码的。

从这里下载适用于 WildFly 14.0 的 Infinispan 模块包

然后,与你对 Hibernate OGM 模块 zip 文件所做的事情类似,将此文件也解压缩到应用程序服务器的 modules 目录中。

如果你使用的是 Hibernate OGM Infinispan 特性包,则不必担心这一点。Infinispan 客户端已包含在其中。

4.6. 与 WildFly NoSQL 集成

WildFly NoSQL 项目允许在 WildFly 子系统中配置 NoSQL 数据存储客户端。请参阅 WildFly NoSQL 文档。简而言之,它为 NoSQL 数据存储提供了类似于 SQL 数据源 的概念。

可以使用特殊的 Hibernate 属性 hibernate.connection.resource 配置 Hibernate OGM 以使用 WildFly NoSQL 提供的连接。

在撰写本文时,该功能及其属性仅支持 MongoDBNeo4j Bolt 客户端。

4.6.1. 如何将 WildFly NoSQL 与 Hibernate OGM 结合使用

一个典型的 Hibernate OGM 持久化配置(没有 WildFly NoSQL 的支持)如下所示

<persistence version="2.1"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
  <persistence-unit name="primary" transaction-type="JTA">
    <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
    <properties>
      <property name="hibernate.ogm.datastore.provider" value="mongodb" />
      <property name="hibernate.ogm.datastore.create_database" value="true"/>
      <property name="hibernate.ogm.datastore.host" value="localhost:27018"/>
      <property name="hibernate.ogm.datastore.database" value="mongodb"/>
      <property name="hibernate.ogm.mongodb.write_concern" value="JOURNALED"/>
      <property name="hibernate.ogm.mongodb.read_preference" value="NEAREST"/>
    </properties>
  </persistence-unit>
</persistence>

某些配置,例如主机名、端口、数据库名称和其他数据存储特定属性,可以重构/移动到 WildFly NoSQL 子系统,如下所示

<subsystem xmlns="urn:jboss:domain:mongodb:1.0">
    <mongo name="default" id="mongodb" jndi-name="java:jboss/mongodb/client" database="mongodb" module="org.hibernate.ogm.mongodb">
        <host name="default" outbound-socket-binding-ref="mongodb"/>
        <properties name="default">
            <property name="writeConcern" value="JOURNALED"/>
            <property name="readConcern" value="LOCAL"/>
        </properties>
    </mongo>
</subsystem>

请注意,这里的 jndi-name 属性定义了外部查找的字符串,它将在本章后面使用。而 module 属性则指示要从中加载客户端驱动程序的静态模块。

如果您已使用 Hibernate OGM 特性包为您的 WildFly 进行了配置,这也是使用 WildFly 时建议的做法,则对于 MongoDB 驱动程序,模块属性将为 org.hibernate.ogm.mongodb,对于 Neo4j 驱动程序,则为 org.hibernate.ogm.neo4j

此外,您应该有一个像这样的 WildFly 套接字绑定

<socket-binding-group ...
    <outbound-socket-binding name="mongodb">
        <remote-destination host="localhost" port="27018"/>
    </outbound-socket-binding>
</socket-binding-group>

此时,您可以在您的 persistence.xml 中使用 Hibernate 属性 hibernate.connection.resource 将 WildFly NoSQL 与您的 Hibernate OGM 集成。

在我们的示例中,我们将有

<persistence version="2.1"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
  <persistence-unit name="primary" transaction-type="JTA">
    <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
    <properties>
      <property name="hibernate.ogm.datastore.provider" value="mongodb" />
      <property name="hibernate.connection.resource" value="java:jboss/mongodb/client"/>
      <property name="hibernate.ogm.datastore.create_database" value="true"/>
    </properties>
  </persistence-unit>
</persistence>

5. 映射您的实体

本节主要描述 Hibernate OGM 映射的特性。它并非旨在成为实体映射的完整指南,完整指南请参阅 Hibernate ORM 的文档:毕竟 Hibernate OGM 就是 Hibernate ORM。

5.1. 支持的实体映射

几乎所有与实体相关的构造都应该在 Hibernate OGM 中开箱即用。@Entity@Table@Column@Enumerated@Temporal@Cacheable 等将按预期工作。如果您需要示例,请查看 Hibernate OGM 入门 或 Hibernate ORM 文档。让我们集中讨论与 Hibernate OGM 不同或根本不支持的功能。

Hibernate OGM 支持以下继承策略:* InheritanceType.TABLE_PER_CLASS * InheritanceType.SINGLE_TABLE

如果您需要支持其他策略,请告诉我们(请参阅 如何贡献)。

JPA 注释引用的是表,但数据库将使用哪种抽象取决于您正在处理的 NoSQL 数据存储的性质。例如,在 MongoDB 中,表将被映射为文档。

您可以找到有关实体在您正在使用的数据存储的相应映射部分中的存储方式的更多详细信息。

目前 Hibernate OGM 不支持辅助表。如果您需要此功能,请告诉我们(请参阅 如何贡献)。

部分支持查询,您将在 查询章节 中找到更多信息。

所有标准 JPA ID 生成器都受支持:IDENTITY、SEQUENCE、TABLE 和 AUTO。如果您需要支持其他生成器,请告诉我们(请参阅 如何贡献)。

某些 NoSQL 数据库无法为 IDENTITY 或 SEQUENCE 提供有效的实现,对于这些情况,我们建议您使用基于 UUID 的生成器。例如,在 Infinispan(嵌入模式)中,使用 IDENTITY 是可能的,但这将需要使用集群范围内的协调来维护计数器,这将无法很好地执行。

@Entity
public class Breed {

    @Id @GeneratedValue(generator = "uuid")
    @GenericGenerator(name="uuid", strategy="uuid2")
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    private String id;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    private String name;
}

5.2. 支持的类型

目前支持大多数 Java 内置类型。但是,不支持自定义类型 (@Type)。

以下是支持的 Java 类型列表

  • 布尔值

  • 字节

  • 字节数组

  • 日历

  • 日期

  • 双精度

  • 整数

  • 长整数

  • 短整数

  • 浮点数

  • 字符

  • 字符串

  • BigDecimal(映射为科学记数法)

  • BigInteger

  • URL(如 RFC 1738 所述,并由 Java URL 类型的 toString 返回)

  • UUID(如 RFC 4122 所述存储)

  • 枚举

如果您需要更多类型支持,请告诉我们 如何贡献

5.3. 支持的关联映射

支持所有关联类型 (@OneToOne@OneToMany@ManyToOne@ManyToMany)。同样,所有集合类型也受支持 (SetMapList)。然而,Hibernate OGM 存储关联信息的方式与传统的 RDBMS 表示方式有很大不同。每个专门针对数据存储的章节都描述了如何持久化关联,请务必查看它们。

并非所有类型的关联都可以在所有数据存储上有效地映射:这将取决于所使用的 NoSQL 技术的特定功能。例如,键值存储将为给定实体存储在一个键中的所有关联导航。如果您的集合包含 100 万个元素,则 Hibernate OGM 将不得不将 100 万个元组存储在关联键中。例如,Infinispan 嵌入式方言会遇到此限制,因为它被视为纯键值存储,而 Infinispan 远程方言则不会,因为它更类似于文档存储。

5.3.1. 关联中元素的顺序

默认情况下,Hibernate OGM 不保证每次从数据存储中加载关联时,关联中的元素都将按相同的顺序检索。

如果顺序很重要,您可以使用以下注释强制执行顺序

  • @javax.persistence.OrderColumn:集合在集合链接表中使用专用的顺序列

  • @javax.persistence.OrderBy:集合在检索时使用子实体属性进行排序

您可以在 Hibernate ORM 文档 中找到更多详细信息和示例。

目前,Hibernate OGM 不支持关联中的重复项。这意味着即使实体或嵌入式对象在关联中出现两次,Hibernate OGM 也只会保存或读取该元素一次。这仅适用于具有相同 ID 的元素。

解决此问题的变通方法是使用注释 @javax.persistence.OrderColumn

有关更多详细信息,您可以查看问题 OGM-1237OGM-1537

6. Hibernate OGM API

Hibernate OGM 只有很少的特定 API。在大多数情况下,您将通过以下方式与它交互

  • JPA API

  • 原生 Hibernate ORM API

本章将仅讨论有关这些 API 的 Hibernate OGM 特定行为。如果您需要了解 JPA 或原生 Hibernate API,请查看 Hibernate ORM 文档

6.1. 引导 Hibernate OGM

我们之前已经讨论过这个主题,请查看 配置和启动 Hibernate OGM 以获取所有详细信息。

需要提醒的是,它基本上归结为以下两种方式之一

  • 在您的 persistence.xml 文件中设置正确的持久化提供程序,并以通常的方式创建 EntityManagerFactory

  • 使用 StandardServiceRegistryBuilderMetadataSources 通过 Hibernate ORM 原生 API 启动 SessionFactory

6.2. JPA 和原生 Hibernate ORM API

您了解 Java 持久化和 Hibernate ORM 原生 API 吗?您已经可以开始使用了。如果您需要复习一下,请务必阅读 Hibernate ORM 文档

不过,有些地方有所不同,让我们来讨论一下。

大多数 EntityManagerSession 契约都受支持。以下是几个例外

  • Session.createCriteria:Hibernate OGM 目前不支持条件查询

  • Session.createFilter:目前不支持对集合的查询

  • SessionenableFilterdisableFilter 等:目前不支持查询过滤器

  • doWorkdoReturningWork 未实现,因为它们依赖于 JDBC 连接 - 请参阅 OGM-694

  • 不支持 Session 的存储过程 API

  • 目前不支持 Session 的自然 ID API

  • Session.lock 目前尚未完全支持

  • 不支持 EntityManager 的条件查询 API

  • 不支持 EntityManager 的存储过程 API - 请参阅 OGM-695

  • EntityManager.lock 目前尚未完全支持

  • 请参阅 查询您的实体 以了解 JPQL 和原生查询支持哪些内容

6.2.1. 访问 OgmSession API

要执行 NoSQL 原生查询,一种方法是使用 OgmSession#createNativeQuery。您可以在 使用 NoSQL 的原生查询语言 中阅读更多相关内容。但让我们看看如何访问 OgmSession 实例。

从 JPA 中,使用 EntityManagerunwrap 方法

示例 6. 从 EntityManager 获取 OgmSession
EntityManager entityManager = ...
OgmSession ogmSession = entityManager.unwrap(OgmSession.class);
NativeQuery query = ogmSession.createNativeQuery(...);

在 Hibernate 原生 API 的情况下,您应该已经可以访问 OgmSession。您使用的 OgmConfiguration 将返回 OgmSessionFactory。反过来,此工厂将生成 OgmSession

示例 7. 使用 Hibernate ORM 原生 API 获取 OgmSession
StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
    .applySetting( OgmProperties.ENABLED, true )
    .build();

OgmSessionFactory ogmSessionFactory = new MetadataSources( registry )
    .buildMetadata()
    .getSessionFactoryBuilder()
    .unwrap( OgmSessionFactoryBuilder.class )
    .build();
OgmSession ogmSession = ogmSessionFactory.openSession();
NativeQuery query = ogmSession.createNativeQuery(...);

6.3. 关于刷新和事务

即使某些底层 NoSQL 数据存储不支持事务,但在使用 Hibernate OGM API 时,划分事务也很重要。让我们看看原因。

Hibernate 会尽可能长时间地堆积更改,然后将其推送到数据存储中。这为巨大的优化打开了大门(避免重复、批量操作等)。您可以通过调用 Session.flushEntityManager.flush 强制更改发送到数据存储中。在某些情况下(例如,在执行某些查询之前),Hibernate 会自动刷新。它也会在事务划分发生时刷新(无论是否存在真正的交易)。

最好的方法是始终如以下所示划分事务。这将避免手动调用刷新的需要,并将为将来的 Hibernate OGM 提供机会。

示例 8. 明确划分事务

以下是如何在非 JTA 环境中执行操作。

Session session = ...

Transaction transaction = session.beginTransaction();
try {
    // do your work
    transaction.commit(); // will flush changes to the datastore
catch (Exception e) {
    transaction.rollback();
}

// or in JPA
EntityManager entityManager = ...
EntityTransaction transaction = entityManager.getTransaction();
try {
    // do your work
    transaction.commit(); // will flush changes to the datastore
}
catch (Exception e) {
    transaction.rollback();
}

在 JTA 环境中,容器会为您划分事务,Hibernate OGM 会透明地加入该事务并在提交时刷新。或者您需要手动划分事务。在后一种情况下,最好在检索 SessionEntityManager 之前启动/停止事务,如以下所示。另一种方法是在事务启动后调用 EntityManager.joinTransaction()

transactionManager.begin();
Session session = sessionFactory.openSession();
// do your work
transactionManager.commit(); // will flush changes to the datastore

// or in JPA
transactionManager.begin();
EntityManager entityManager = entityManagerFactory.createEntityManager();
// do your work
transactionManager.commit(); // will flush changes to the datastore

6.3.1. 对应用更改期间的错误采取行动

以下部分中描述的错误补偿 API 是一项实验性功能。它将随着时间的推移而添加更多功能。这可能需要更改现有方法签名,因此可能会破坏使用 API 的先前版本的代码。

请告诉我们您对 API 的使用情况以及您对进一步功能的期望!

如果在刷新一组更改时发生错误,一些数据更改可能已经应用于数据存储。如果存储是非事务性的,则无法回滚(撤消)这些更改(如果它们已刷新)。在这种情况下,最好知道哪些更改已应用以及哪些更改失败,以便采取适当的操作。

Hibernate OGM 为此目的提供了一个错误补偿 API。通过实现 org.hibernate.ogm.failure.ErrorHandler 接口,您将在以下情况收到通知:

  • Hibernate OGM 引擎与网格方言之间的交互失败

  • 触发了当前事务的回滚

错误补偿 API 的用例包括

  • 记录所有应用的操作

  • 重试失败的操作(例如,在超时后)

  • 尝试补偿(应用逆向操作)应用的更改

目前,API 为手动执行这些任务以及类似任务奠定了基础,但我们设想在未来版本中采用更加自动化的方式,例如,自动重试失败的操作或自动应用补偿操作。

让我们看一个例子

示例 9. 自定义 ErrorHandler 实现
public class ExampleErrorHandler extends BaseErrorHandler {

    @Override
    public void onRollback(RollbackContext context) {
        // write all applied operations to a log file
        for ( GridDialectOperation appliedOperation : context.getAppliedGridDialectOperations() ) {
            switch ( appliedOperation.getType() ) {
                case INSERT_TUPLE:
                    EntityKeyMetadata entityKeyMetadata = appliedOperation.as( InsertTuple.class ).getEntityKeyMetadata();
                    Tuple tuple = appliedOperation.as( InsertTuple.class ).getTuple();

                    // write EKM and tuple to log file...
                    break;
                case REMOVE_TUPLE:
                    // ...
                    break;
                case ...
                    // ...
                    break;
            }
        }
    }

    @Override
    public ErrorHandlingStrategy onFailedGridDialectOperation(FailedGridDialectOperationContext context) {
        // Ignore this exception and continue
        if ( context.getException() instanceof TupleAlreadyExistsException ) {
            GridDialectOperation failedOperation = context.getFailedOperation();
            // write to log ...

            return ErrorHandlingStrategy.CONTINUE;
        }
        // But abort on all others
        else {
            return ErrorHandlingStrategy.ABORT;
        }
    }
}

onRollback() 方法(在回滚事务时调用,无论是用户回滚还是容器回滚)显示了如何迭代回滚之前应用的所有方法,检查它们的特定类型,例如,将它们写入日志文件。

onFailedGridDialectOperation() 方法针对每个失败的特定数据存储操作调用。它允许您决定是继续忽略失败,重试还是中止操作。如果返回 ABORT,则会重新抛出导致异常,最终导致当前事务回滚。如果返回 CONTINUE,则将忽略该异常,导致当前事务继续。

是否中止或继续的决定可以基于特定的异常类型或导致失败的网格方言操作。在示例中,所有类型为 TupleAlreadyExistsException 的异常都被忽略,而所有其他异常会导致当前刷新周期中止。如果需要,您还可以对特定于数据存储的异常(例如 MongoDB 的 MongoTimeoutException)做出反应。

请注意,通过扩展提供的基类 BaseErrorHandler 而不是直接实现接口,您只需要实现您实际感兴趣的那些回调方法。如果在未来的版本中向 ErrorHandler 接口添加了更多回调方法,该实现也不会中断。

实现错误处理程序后,需要将其注册到 Hibernate OGM。为此,使用 hibernate.ogm.error_handler 属性指定它,例如,在 META-INF/persistence.xml 中作为持久化单元属性

<property name="hibernate.ogm.error_handler" value="com.example.ExampleErrorHandler"/>

6.4. SPI

一些 Hibernate OGM 公共契约针对数据存储提供者的集成者或实现者。它们不应由常规应用程序使用。这些契约称为 SPI,位于 .spi 包中。

为了不断改进 Hibernate OGM,我们可能会在版本之间破坏这些 SPI。如果您打算编写数据存储,请与我们联系。

非公共契约存储在 .impl 包中。如果您发现自己正在使用这些类之一,请注意我们可能会在未经通知的情况下破坏这些类。

7. 查询您的实体

数据进入数据存储后,是时候进行一些查询操作了!使用 Hibernate OGM,您有几个可以满足您需求的替代方案

  • 使用 JPQL - 目前仅用于简单查询

  • 使用 NoSQL 原生查询将结果映射为托管实体

  • 使用 Hibernate Search 查询 - 主要用于全文查询

  • 使用存储过程将结果映射为托管实体

7.1. 使用 JPQL

对于 Hibernate OGM,我们开发了一个全新的 JPQL 解析器,它已经能够将简单查询转换为底层数据存储的原生查询语言(例如,MongoDB 的 MongoQL,Neo4J 的 CypherQL 等)。此解析器还可以为不支持查询语言的数据存储生成 Hibernate Search 查询。

对于像 Infinispan 这样的数据存储,它们需要 Hibernate Search 来执行 JPQL 查询,必须满足以下先决条件:

  • 不涉及联接、聚合或其他关系操作

  • 查询中涉及的实体必须被索引

  • 谓词中涉及的属性必须被索引

这是一个例子

@Entity @Indexed
public class Hypothesis {

    @Id
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    private String id;

    @Field(analyze=Analyze.NO)
    public String getDescription() { return description; }
    public void setDescription(String description) { this.description = description; }
    private String description;
}

Query query = session
    .createQuery("from Hypothesis h where h.description = :desc")
    .setParameter("desc", "tomorrow it's going to rain");

请注意,description 字段被标记为未分析。这对于支持 JPQL 定义的字段相等和比较是必需的。

您可以使用以下 JPQL 结构:

  • 使用“<”、“<=”、“=”、“>=” 和“>” 的简单比较

  • IS NULLIS NOT NULL

  • 布尔运算符 ANDORNOT

  • LIKEINBETWEEN

  • ORDER BY

特别是需要注意的是,不支持以下内容:

  • 跨实体联接

  • JPQL 函数,尤其是聚合函数,例如 count

  • JPQL 更新和删除查询

对于您的用例来说,这可能听起来限制很大,请耐心等待。这是一个我们想要改进的热门领域,请告诉我们您缺少哪些功能 通过打开 JIRA 或通过电子邮件。另外阅读下一节,您将看到其他实现查询的替代方案。

让我们看看您可以在 JPQL 中表达的一些查询

示例 10. 一些 JPQL 查询
// query returning an entity based on a simple predicate
select h from Hypothesis h where id = 16

// projection of the entity property
select id, description from Hypothesis h where id = 16

// projection of the embedded properties
select h.author.address.street from Hypothesis h where h.id = 16

// predicate comparing a property value and a literal
from Hypothesis h where h.position = '2'

// negation
from Hypothesis h where not h.id = '13'
from Hypothesis h where h.position <> 4

// conjunction
from Hypothesis h where h.position = 2 and not h.id = '13'

// named parameters
from Hypothesis h where h.description = :myParam

// range query
from Hypothesis h where h.description BETWEEN :start and :end

// comparisons
from Hypothesis h where h.position < 3

// in
from Hypothesis h where h.position IN (2, 3, 4)

// like
from Hypothesis h where h.description LIKE '%dimensions%'

// comparison with null
from Hypothesis h where h.description IS null

// order by
from Hypothesis h where h.description IS NOT null ORDER BY id
from Helicopter h order by h.make desc, h.name

还有一些部分支持的功能

  • 嵌入关联中的内部 JOIN:Neo4j 和 MongoDB 按预期工作;如果您的数据存储提供者使用 Hibernate Search 实现 JPQL 查询,则不适用。

  • 嵌入标识符属性的投影或过滤器:Neo4j 和 MongoDB 按预期工作;如果您的数据存储提供者使用 Hibernate Search 实现 JPQL 查询,则不适用。

以下示例可以更好地说明这一点

示例 11. 具有嵌入集合和受支持的 JPQL 查询的实体
@Indexed
@Entity
public class StoryGame {

    @DocumentId
    @EmbeddedId
    @FieldBridge(impl = NewsIdFieldBridge.class)
    private StoryID storyId;

    @ElementCollection
    @IndexedEmbedded
    private List<OptionalStoryBranch> optionalEndings;

    ...

}

@Embeddable
public class StoryID implements Serializable {

    private String title;
    private String author;

    ...
}

@Embeddable
public class OptionalStoryBranch {

    // Analyze.NO for filtering in query
    // Store.YES for projection in query
    @Field(store = Store.YES, analyze = Analyze.NO)
    private String text;

    ...

}

使用受支持的运算符过滤结果将适用于所有数据存储

String query =
    "SELECT sg" +
    "FROM StoryGame sg JOIN sg.optionalEndings ending WHERE ending.text = 'Happy ending'"
List<StoryGame> stories = session.createQuery( query ).list();

嵌入关联属性的投影适用于 Neo4j 和 MongoDB,但其他数据存储只会从关联中返回一个元素。这是因为 Hibernate Search 当前不支持关联的投影。以下是一个受此影响的查询示例

String query =
     "SELECT ending.text " +
     "FROM StoryGame sg JOIN sg.optionalEndings ending WHERE ending.text LIKE 'Happy%'";
List<String> endings = session.createQuery( query ).list();

对嵌入 ID 属性进行投影和过滤适用于 Neo4j 和 MongoDB,但在其他数据存储中会抛出异常

String query =
     "SELECT sg.storyId.title FROM StoryGame sg WHERE sg.storyId.title = 'Best Story Ever'";
List<String> title = session.createQuery( query ).list();

如果数据存储使用 Hibernate Search 来执行 JPQL 查询,它将导致以下异常:

org.hibernate.hql.ParsingException: HQL100002: The type [storyId] has no indexed property named title.

为了反映在当前会话中执行的更改,在查询执行之前,所有受给定查询影响的实体都将刷新到数据存储(Hibernate ORM 以及 Hibernate OGM 都是如此)。

对于非完全事务性存储,这会导致更改作为运行查询的副作用写入,这些更改无法通过以后可能的回滚来恢复。

根据您的特定用例和需求,您可能更愿意禁用自动刷新,例如,通过调用 query.setFlushMode(FlushMode.MANUAL)。但请记住,查询结果将不会反映在当前会话中应用的更改。

7.2. 使用 NoSQL 的原生查询语言

通常,您希望拥有底层 NoSQL 查询引擎的强大功能。即使这会让您失去可移植性。

Hibernate OGM 通过允许您表达原生查询(例如,在 MongoQL 或 CypherQL 中)并将这些查询的结果映射为映射的实体来解决该问题。

在 JPA 中,使用 EntityManager.createNativeQuery。如果您的结果集映射实体的映射定义,则第一个形式接受一个结果类。第二个形式接受 resultSetMapping 的名称,并允许您自定义属性如何映射到查询的列。您也可以使用预定义的命名查询,它定义其结果集映射。

让我们看看如何为 Neo4J 完成此操作

示例 12. 在 JPA 中创建原生查询的各种方法
@Entity
@NamedNativeQuery(
   name = "AthanasiaPoem",
   query = "{ $and: [ { name : 'Athanasia' }, { author : 'Oscar Wilde' } ] }",
   resultClass = Poem.class )
public class Poem {

    @Id
    private Long id;

    private String name;

    private String author;

   // getters, setters ...

}

...

javax.persistence.EntityManager em = ...

// a single result query
String query1 = "MATCH ( n:Poem { name:'Portia', author:'Oscar Wilde' } ) RETURN n";
Poem poem = (Poem) em.createNativeQuery( query1, Poem.class ).getSingleResult();

// query with order by
String query2 = "MATCH ( n:Poem { name:'Portia', author:'Oscar Wilde' } ) " +
                "RETURN n ORDER BY n.name";
List<Poem> poems = em.createNativeQuery( query2, Poem.class ).getResultList();

// query with projections
String query3 = MATCH ( n:Poem ) RETURN n.name, n.author ORDER BY n.name";
List<Object[]> poemNames = (List<Object[]>)em.createNativeQuery( query3 )
                               .getResultList();

// named query
Poem poem = (Poem) em.createNamedQuery( "AthanasiaPoem" ).getSingleResult();

在原生 Hibernate API 中,使用 OgmSession.createNativeQuerySession.getNamedQuery。前者允许您以编程方式定义结果集映射。后者接收预定义查询的名称,该查询已描述其结果集映射。

示例 13. Hibernate API 定义结果集映射
OgmSession session = ...
String query1 = "{ $and: [ { name : 'Portia' }, { author : 'Oscar Wilde' } ] }";
Poem poem = session.createNativeQuery( query1 )
                      .addEntity( "Poem", Poem.class )
                      .uniqueResult();

请查看每个单独的数据存储章节,以获取有关原生查询语言映射细节的更多信息。特别是 Neo4JMongoDB.

Hibernate Search 提供了一种将 Java 对象索引到 Lucene 索引并在其上执行全文查询的方法。索引存在于数据存储之外。这在功能集和可扩展性方面提供了几个有趣的属性。

Apache Lucene 是一个全文索引和查询引擎,具有出色的查询性能。在功能方面,全文意味着您可以执行比简单相等匹配更复杂的操作。

Hibernate Search 与 Hibernate ORM 本地集成。当然还有 Hibernate OGM!

示例 14. 通过 Maven 将 Hibernate Search 工件添加到您的项目
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-search-orm</artifactId>
</dependency>
示例 15. 使用 Hibernate Search 进行全文匹配
@Entity @Indexed
public class Hypothesis {

    @Id
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    private String id;

    @Field(analyze=Analyze.YES)
    public String getDescription() { return description; }
    public void setDescription(String description) { this.description = description; }
    private String description;
}
EntityManager entityManager = ...
//Add full-text superpowers to any EntityManager:
FullTextEntityManager ftem = Search.getFullTextEntityManager(entityManager);

//Optionally use the QueryBuilder to simplify Query definition:
QueryBuilder b = ftem.getSearchFactory()
   .buildQueryBuilder()
   .forEntity(Hypothesis.class)
   .get();

//Create a Lucene Query:
Query lq = b.keyword().onField("description").matching("tomorrow").createQuery();

//Transform the Lucene Query in a JPA Query:
FullTextQuery ftQuery = ftem.createFullTextQuery(lq, Hypothesis.class);

//List all matching Hypothesis:
List<Hypothesis> resultList = ftQuery.getResultList();

假设我们的数据库包含一个 Hypothesis 实例,其描述为“有时明天我们会发布”,那么该实例将被我们的全文查询返回。

文本相似性非常强大,因为它可以针对特定语言或特定领域术语进行配置;它可以处理拼写错误和同义词,最重要的是,它可以根据相关性返回结果。

值得注意的是,Lucene 索引是术语出现统计信息的矢量空间:因此,从文本中提取标签,字符串的频率,以及关联这些数据,可以非常轻松地构建高效的数据分析应用程序。

虽然 Lucene 查询的潜力非常大,但它并不适用于所有用例。让我们看看将 Lucene 查询作为主要查询引擎的一些局限性

  • Lucene 不支持联接。任何 to-One 关系都可以很好地映射,Lucene 社区正在其他形式上取得进展,但目前无法实现对 OneToManyManyToMany 的限制。

  • 由于我们在提交时将更改应用于索引,因此您的更新不会影响查询,直到您提交(我们可能会改进这一点)。

  • 虽然查询非常快,但写入操作并不那么快(但我们可以使其扩展)。

要完全了解 Hibernate Search 可以为您做什么以及如何使用它,请查看 Hibernate Search 参考文档.

7.4. 使用 Criteria API

目前,我们尚未实现对 Criteria API 的支持(无论是 Hibernate 原生还是 JPA)。

7.5. 使用存储过程

通常,您希望拥有底层 NoSQL 查询引擎的强大功能。即使这会让您失去可移植性。

Hibernate OGM 通过允许您表达存储过程(例如,在 MongoDB 的服务器端 JavaScript 中)并将这些查询的结果映射为映射的实体或返回原始结果来解决该需求。

在 JPA 中,使用EntityManager.createStoredProcedureQueryEntityManager.createNamedStoredProcedureQuery。第一个形式接受结果类,如果您的结果集映射到实体的映射定义。第二个形式接受 resultSetMapping 的名称,并允许您通过查询自定义属性如何映射到列。您也可以使用预定义的命名查询,它定义其结果集映射。

让我们看看它是如何完成的

示例 16. 在 JPA 中创建存储过程查询的各种方法
@Entity
@NamedStoredProcedureQueries({
        @NamedStoredProcedureQuery(name = "find_cars_by_brand", procedureName = "resultSetResultProc", parameters = {
                @StoredProcedureParameter(mode = ParameterMode.REF_CURSOR, type = Void.class),
                @StoredProcedureParameter(mode = ParameterMode.IN, type = String.class)
        }, resultSetMappings = "carMapping")
})

@SqlResultSetMapping(name = "carMapping", entities = { @EntityResult(entityClass = Car.class) })
public class Car {

    @Id
    private Long id;

    private String brand;

   // getters, setters ...

}

...

javax.persistence.EntityManager em = ...

StoredProcedureQuery storedProcedureQuery = em.createStoredProcedureQuery( "mostExpensiveCarsPerYear", Car.class );
storedProcedureQuery.registerStoredProcedureParameter( 0, Void.class, ParameterMode.REF_CURSOR );
storedProcedureQuery.registerStoredProcedureParameter( 1, Integer.class, ParameterMode.IN );
storedProcedureQuery.setParameter( 1, 1995 );
List<Car> cars = storedProcedureQuery.getResultList();

// named stored procedure query
StoredProcedureQuery storedProcedureQuery = em.createNamedStoredProcedureQuery( "find_cars_by_brand" );
storedProcedureQuery.setParameter( 1, "Bentley" );
List<Car> cars = storedProcedureQuery.getResultList();

您也可以使用命名参数和StoredProcedureQuery#getSingleResult()

查看每个单独的数据存储章节,了解有关本机查询语言映射的详细信息。

存储过程的“OUT”和“IN_OUT”参数目前尚不支持。其主要原因是支持的数据存储仅支持“IN”参数。

8. NoSQL 数据存储

目前,Hibernate OGM 支持以下 NoSQL 数据存储

  • Map: 在内存中 Java 映射中存储数据以存储数据。仅在单元测试中使用。

  • Infinispan Embedded: 将数据存储到 Infinispan(数据网格)中,直接参与集群

  • Infinispan Remote:(也称为 Hot Rod,Infinispan 客户端的名称)通过作为 Hot Rod 客户端连接将数据存储到 Infinispan

    • 在此阶段,此数据存储处于实验阶段

  • Ehcache: 将数据存储到 Ehcache(缓存)中

  • MongoDB: 将数据存储到 MongoDB(文档存储)中

  • Neo4j: 将数据存储到 Neo4j(图数据库)中

  • CouchDB: 将数据存储到 CouchDB(文档存储)中

    • 在此阶段,此数据存储处于实验阶段

  • Redis: 将数据存储到 Redis(键值存储)中

    • 在此阶段,此数据存储处于实验阶段

更多计划正在进行中,如果您有兴趣,请与我们联系(请参阅 如何在 Hibernate OGM 上获得帮助并做出贡献)。

对于每个数据存储,Hibernate OGM 都具有称为数据存储提供程序的特定集成代码。所有这些代码都在一个专门的 Maven 模块中,您只需要依赖于您使用的那个模块。

Hibernate OGM 通过两个契约与 NoSQL 数据存储进行交互

  • 一个DatastoreProvider,它负责启动和停止与数据存储的连接,并在需要时支撑数据存储

  • 一个GridDialect,它负责将 Hibernate OGM 操作转换为特定于数据存储的操作

8.1. 使用特定的 NoSQL 数据存储

只需要几个步骤

  • 将数据存储提供程序模块添加到您的类路径

  • 要求 Hibernate OGM 使用该数据存储提供程序

  • 配置 URL 或配置以访问数据存储

在您的类路径中添加相关 Hibernate OGM 模块在 Maven 中看起来像这样

<dependency>
    <groupId>org.hibernate.ogm</groupId>
    <artifactId>hibernate-ogm-infinispan-embedded</artifactId>
    <version>5.4.1.Final</version>
</dependency>

模块名称为hibernate-ogm-infinispanhibernate-ogm-infinispan-remotehibernate-ogm-ehcachehibernate-ogm-mongodbhibernate-ogm-neo4jhibernate-ogm-couchdbhibernate-ogm-redishibernate-ogm-cassandra。地图数据存储包含在 Hibernate OGM 引擎模块中。

接下来,配置要使用哪个数据存储提供程序。这通过hibernate.ogm.datastore.provider 选项完成。可能的值是

  • 特定快捷方式(优选):map(仅用于单元测试)、infinispan_embeddedinfinispan_remoteehcachemongodbneo4j_embeddedneo4j_httpneo4j_bolt

  • DatastoreProvider 实现的完全限定类名

以编程方式引导会话工厂或实体管理器工厂时,应使用org.hibernate.ogm.cfg.OgmProperties 上声明的常量来指定配置属性,例如hibernate.ogm.datastore.provider

在这种情况下,您还可以以数据存储提供程序类型类对象的形式指定提供程序,或者传递数据存储提供程序类型实例

Map<String, Object> properties = new HashMap<String, Object>();

// pass the type
properties.put( OgmProperties.DATASTORE_PROVIDER, MongoDBDatastoreProvider.class );

EntityManagerFactory emf = Persistence.createEntityManagerFactory( "my-pu", properties );

默认情况下,数据存储提供程序会透明地选择网格方言,但您可以使用hibernate.ogm.datastore.grid_dialect 选项覆盖此设置。使用GridDialect 实现的完全限定类名。大多数用户应该完全忽略此设置。

现在让我们看看每个数据存储提供程序的具体内容。如何进一步配置它,使用什么映射结构等等。

9. Infinispan

Infinispan 是一个开源的内存中数据网格,专注于高性能。作为数据网格,您可以将其部署在多个服务器(称为节点)上,并像连接到单个存储引擎一样连接到它:它将巧妙地分配计算工作量和数据存储。

在单个节点上设置它非常简单,Hibernate OGM 知道如何引导一个节点,因此您可以轻松地尝试它。但 Infinispan 在多节点部署中真正大放异彩:您需要配置一些网络详细信息,但应用程序行为不会发生任何变化,而性能和数据大小可以线性扩展。

从它的所有功能中,我们只会描述与 Hibernate OGM 相关的那些功能;有关其所有功能和配置选项的完整描述,请参阅 infinispan.org 上的 Infinispan 项目文档。

9.1. 为什么将 Hibernate OGM 与 Infinispan 结合使用?

Infinispan 提供了出色的可扩展性和弹性功能,但它可能具有陡峭的学习曲线。

如果您已经熟悉 JPA API,那么您将能够快速将数据存储在 Infinispan 中,并且您还将受益于 Hibernate OGM 等框架在幕后应用的优化。

尤其是

  • 您无需先学习Protobuf 即可入门

  • 无需学习 Infinispan API

  • Hibernate OGM 将为您设置和管理Hot Rod 客户端

  • 与 Hibernate ORM 相同的 API,这意味着您可以使用相同的工具

您仍然需要了解 Infinispan、其所有功能以及如何配置它们以达到应用程序的最佳性能,但您可以使用示例配置快速完成概念验证。

9.2. Infinispan:在嵌入模式和 Hot Rod 之间进行选择

Java 应用程序可以通过两种截然不同的方式使用 Infinispan

  • 嵌入模式下运行 Infinispan。

  • 使用Hot Rod 客户端连接到Infinispan 服务器

Hibernate OGM 支持以任何一种模式连接,但由于两种模式的 API 和功能不同,因此它提供了两个不同的模块,每个模块都有一组自己的配置选项和功能。

嵌入模式下运行 Infinispan 意味着 Infinispan 节点与使用它的代码在同一个 JVM 中运行。好处是部分数据(或所有数据)将存储在同一个 JVM 中,使得对这些数据的读取非常快且高效,因为不会有 RPC 到其他系统。写入操作仍然需要发出一些协调 RPC,但它们也受益于减少的必要操作。

但是,部分数据存储在同一个 JVM 中的事实也是这种选择的缺点:这通常意味着必须为 JVM 配置更大的堆大小,而这些堆大小更难调整以获得最佳性能。其他系统参数也可能需要配置,因为此 JVM 节点现在将被视为“数据持有节点”,而不是无状态应用程序节点。一些架构师和系统管理员不喜欢这样。

通过Hot Rod 客户端连接到Infinispan 服务器时,架构类似于 Hibernate 连接到传统数据库:数据存储在Infinispan 服务器节点上,Hibernate OGM 使用带有 TCP 连接池的客户端与服务器通信。但 Hot Rod 客户端不是事务性的,请参阅此处描述的限制:(Infinispan Remote dataprovider 的存储原则)。

另一个重要区别是,通过Hot Rod 连接到Infinispan 服务器时,数据使用Google Protobuf 编码,这需要一个模式。此模式由 Hibernate OGM 自动生成。

拥有Protobuf 模式使得能够以非破坏性的方式演化模式,并且使其他客户端能够访问数据 - 甚至是用其他编程语言编写的客户端。

大多数关于 Hibernate OGM 的入门教程都专注于嵌入模式下的 Infinispan,因为在这种模式下,OGM 可以使用简单、仅限本地的 Infinispan 配置启动它自己的嵌入式 Infinispan 节点。

当改用Infinispan 服务器时,您需要下载服务器发行版,解压缩并启动它,然后设置 Hibernate OGM 配置属性,以便集成的 Hot Rod 客户端知道如何连接到它。

高级性能选项和与 Hibernate 二级缓存的交互

在嵌入模式下使用 Infinispan 时,如果它的缓存配置为REPLICATION 模式,所有节点将包含数据库的完整副本:写性能不会扩展,但您的读取速度将非常快,并且会随着集群大小线性扩展,从而使 Hibernate 的二级缓存使用变得多余。

当将 Infinispan 配置为DISTRIBUTED 缓存模式时,您的每个节点将拥有您数据的一部分的本地副本;请记住,您可以使用各种 Infinispan 配置选项(如numOwners)调整该部分的大小,并且您可以将此与 Hibernate 的二级缓存结合起来,或启用 Infinispan 的一级缓存。

您甚至可以将 Infinispan 与将其钝化为其他存储系统(如 RDBMS 或其他 NoSQL 引擎)结合起来;此类存储可以配置为异步。此选项适用于 Infinispan Embedded 和 Infinispan Server;甚至可以使用轻量级的 Infinispan Embedded 层(包含少量数据集),并将其支持到 Infinispan Server 集群,以扩展其存储能力,而无需在嵌入式应用程序节点上过大地增加堆大小。

最后,请记住,诸如复制与分布(CacheMode)和钝化为附加存储(CacheStores)之类的选项可以针对每个 Infinispan 缓存进行不同的配置。

9.3. Hibernate OGM 和 Infinispan Embedded

让我们看看如何配置和使用 Hibernate OGM 与嵌入模式下的 Infinispan。

对于通过 Hot Rod 使用 Infinispan 服务器,请跳到Hibernate OGM 和通过 Hot Rod 的 Infinispan 服务器

9.3.1. 为 Infinispan Embedded 配置 Hibernate OGM

您基本上通过两个步骤配置 Hibernate OGM 和 Infinispan

  • 将依赖项添加到您的类路径

  • 然后选择其中之一

    • 使用默认的 Infinispan 配置(无需操作)

    • 指向您自己的配置资源文件

    • 指向 Infinispan CacheManager 的现有实例的JNDI 名称

  • 如果您需要运行 JPQL 或 HQL 查询,请在类路径上添加 Hibernate Search (使用 Hibernate Search)

请注意,除了使用JNDI 之外,Hibernate OGM 将在同一个 JVM 中引导 Infinispan Embedded,并在 Hibernate OGM 会话工厂关闭时终止它。

Hibernate OGM 将在自动发现中启动 Infinispan Embedded。如果您使用的是集群配置,它可能会自动加入网络上现有的 Infinispan 集群。

9.3.2. 添加 Infinispan 依赖项

您可以使用以下 Maven GAV 将 Infinispan Embedded 方言添加到您的项目中

<dependency>
    <groupId>org.hibernate.ogm</groupId>
    <artifactId>hibernate-ogm-infinispan-embedded</artifactId>
    <version>5.4.1.Final</version>
</dependency>

如果您没有使用依赖项管理工具,请将发行版中目录下的所有依赖项复制到

  • /lib/required

  • /lib/infinispan

  • 可选 - 根据您的容器 - 您可能需要 /lib/provided 中的一些 jar 包

9.3.3. Infinispan 特定配置属性

Infinispan 缓存的详细高级配置信息定义在 Infinispan 特定的 XML 配置文件中;Hibernate OGM 属性很简单,通常只指向这个外部资源。

要使用 Hibernate OGM 提供的默认配置 - 这是新用户的良好起点 - 您无需设置任何属性。

Infinispan 方言的 Hibernate OGM 属性
hibernate.ogm.datastore.provider

将其设置为 infinispan_embedded 以在嵌入模式下使用 Infinispan 作为数据存储提供程序。

hibernate.ogm.infinispan.cachemanager_jndi_name

如果您在 JNDI 中注册了 Infinispan EmbeddedCacheManager,请提供 JNDI 名称,Hibernate OGM 将使用此实例而不是启动新的 CacheManager。这将忽略任何其他配置属性,因为假定 Infinispan 已经配置。Infinispan 通常可以通过 WildFly、Spring 或 Seam 推送到 JNDI。

hibernate.ogm.infinispan_remote.configuration_resource_name

应指向 Infinispan 配置文件的资源名称。如果设置了 JNDI 查找,则忽略此项。默认值为 org/hibernate/ogm/datastore/infinispan/default-config.xml

hibernate.ogm.datastore.keyvalue.cache_storage

在 Infinispan 中持久化数据的策略。存在以下两种策略(org.hibernate.ogm.datastore.keyvalue.options.CacheMappingType 枚举的值)

  • CACHE_PER_TABLE:每个实体类型、关联类型和 ID 源表将使用一个专用缓存。

  • CACHE_PER_KIND:将使用三个缓存:一个用于所有实体的缓存,一个用于所有关联的缓存,以及一个用于所有 ID 源的缓存。

默认值为 CACHE_PER_TABLE。它是推荐的策略,因为它更容易针对给定实体的特定缓存。

以编程方式引导会话工厂或实体管理器工厂时,在指定上面列出的配置属性时,应使用可通过 org.hibernate.ogm.datastore.infinispan.InfinispanProperties 访问的常量。

存储之间共享的通用属性在 OgmPropertiesInfinispanProperties 的超接口)上声明。

为了在存储之间实现最大程度的可移植性,请使用尽可能通用的接口。

9.3.4. Hibernate OGM 使用的缓存名称

根据缓存映射方法,Hibernate OGM 将

  • 将每个实体类型、关联类型和 ID 源表存储在专用缓存中,非常类似于 Hibernate ORM 的操作方式。这是 CACHE_PER_TABLE 方法。

  • 在使用 CACHE_PER_KIND 方法时,将数据存储在三个不同的缓存中

    • ENTITIES:将用于存储所有实体的主要属性。

    • ASSOCIATIONS:存储表示实体之间链接的关联信息。

    • IDENTIFIER_STORE:包含 Hibernate OGM 需要提供序列和自动增量编号以进行主键生成的内部元数据。

首选策略是 CACHE_PER_TABLE,因为它提供了更细粒度的配置选项,以及以更简单的方式处理特定实体的能力。

在以下段落中,我们将解释您可能希望从默认值重新配置的 Infinispan 的哪些方面。我们没有提及的 Infinispan 中的所有属性和元素都可以忽略。有关大师级性能调优和自定义,请参阅 Infinispan 用户指南

Infinispan 配置文件是符合 Infinispan 架构的 XML 文件;以下示例显示了基本结构

示例 17. Infinispan 配置文件的简单示例
<?xml version="1.0" encoding="UTF-8"?>
<infinispan
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="urn:infinispan:config:9.1 http://www.infinispan.org/schemas/infinispan-config-9.1.xsd"
    xmlns="urn:infinispan:config:9.1">

    <cache-container name="HibernateOGM" default-cache="DEFAULT">

        <!-- Default cache settings -->
        <local-cache name="DEFAULT">
            <transaction mode="NON_DURABLE_XA" />
        </local-cache>

        <local-cache name="User"/>

        <local-cache name="Order"/>

        <local-cache name="associations_User_Order"/>

    </cache-container>
</infinispan>

可以在 cache_container 部分之前设置全局设置。这些设置将影响整个实例;对于 Hibernate OGM 用户来说,最重要的是 jgroups 元素,我们将在其中设置 JGroups 配置覆盖。

cache-container 部分中,定义了显式命名的缓存及其配置,以及如果我们希望影响所有命名缓存的默认缓存(此处名为 DEFAULT)。这是我们可能想要配置集群模式、驱逐策略和 CacheStore 的地方。

9.3.5. 管理数据大小

在其默认配置中,Infinispan 将所有数据存储在 JVM 的堆中;在这种基础模式下,从概念上讲,它与使用 HashMap 并没有太大区别:数据的大小应适合您的 VM 的堆,停止/杀死/崩溃应用程序将导致所有数据丢失,并且无法恢复。

要永久存储数据(超出 JVM 内存),应启用 CacheStore。Infinispan 项目提供了许多 CacheStore 实现;一个简单的实现是 "单文件存储",它能够将数据存储在简单的二进制文件中,在任何读/写挂载的文件系统上;您可以在许多其他实现中找到,将您的数据存储在从 JDBC 连接的数据库、其他 NoSQL 引擎(如 MongoDB 和 Cassandra)到甚至委托给其他 Infinispan 集群的任何地方。最后,实现自定义 CacheStore 非常容易。

为了限制宝贵堆空间的内存消耗,您可以激活 passivationeviction 策略;同样,有几种策略可以使用,现在让我们只考虑您可能需要一种策略来避免在将太多条目存储在有界 JVM 内存空间中时出现内存不足的情况;当然,在使用有限的数据大小进行实验时,您无需选择一种策略:启用此类策略不会对您的 Hibernate OGM 应用程序的功能产生任何其他影响(除了性能:存储在 Infinispan 内存空间中的条目比从任何 CacheStore 访问快得多)。

CacheStore 可以配置为直写,在返回(并在同一事务中)之前将所有更改提交到 CacheStore,或者配置为后写。通常不建议在存储引擎中使用后写配置,因为节点故障意味着一些数据可能会丢失而不会收到任何有关该故障的通知,但是由于 Infinispan 能够将 CacheStore 后写与同步复制到其他 Infinispan 节点相结合,因此缓解了此问题。

示例 18. 启用 FileCacheStore 和驱逐
<local-cache name="User">
    <transaction mode="NON_DURABLE_XA" />
    <eviction strategy="LIRS" max-entries="2000"/>
    <persistence passivation="true">
        <file-store
           shared="false"
           path="/var/infinispan/myapp/users">
            <write-behind flush-lock-timeout="15000" thread-pool-size="5" />
        </file-store>
    </persistence>
</local-cache>

在此示例中,我们启用了 evictionCacheStorepersistence 元素)。LIRS 是驱逐策略中的选择之一。在此,它被配置为在活动内存中保留(大约)2000 个条目,并将剩余条目驱逐出去,作为内存使用控制策略。

CacheStore 正在启用 passivation,这意味着驱逐的条目将存储在文件系统上。

您可以配置驱逐策略,而不配置被动 CacheStore!这对 Infinispan 来说是有效的配置,但会导致驱逐器永久删除条目。Hibernate OGM 将在这样的配置中中断。

9.3.6. 集群:将数据存储在多个 Infinispan 节点上

Infinispan 最好的地方在于所有节点都被平等对待,几乎不需要事先进行容量规划:要向集群添加更多节点,您只需启动新的 JVM(在相同或不同的物理服务器上),使用相同的 Infinispan 配置和相同的应用程序即可。

Infinispan 支持多种集群 缓存模式;每种模式都提供相同的 API 和功能,但性能、可扩展性和可用性选项不同

Infinispan 缓存模式
local

适用于单个 VM:网络堆栈已禁用

replication

所有数据都复制到每个节点;每个节点都包含所有条目的完整副本。因此,读取速度更快,但写入的可扩展性不佳。不适合非常大的数据集。

distribution

每个条目都分布在多个节点上,以实现冗余和故障恢复,但不是所有节点。为读写操作提供线性可扩展性。distribution 是默认模式。

要使用 replicationdistribution 缓存模式,Infinispan 将使用 JGroups 来发现并连接到其他节点。

在默认配置中,JGroups 将尝试使用多播套接字自动检测对等节点;这在大多数网络环境中都可以开箱即用,但在云环境(通常会阻止多播数据包)或在严格的防火墙情况下,需要一些额外的配置。请参阅 JGroups 参考文档,特别是查找 发现协议 以自定义对等节点的检测。

如今,JVM 默认使用 IPv6 网络堆栈;这将与 JGroups 一起正常工作,但前提是您已正确配置 IPv6。强制 JVM 使用 IPv4 通常很有用。

让 JGroups 知道您要使用哪个网络接口也很重要;它将默认绑定到一个接口,但如果您有多个网络接口,则可能不是您期望的接口。

示例 19. 用于集群的 JVM 属性设置
#192.168.122.1 is an example IPv4 address
-Djava.net.preferIPv4Stack=true -Djgroups.bind_addr=192.168.122.1

您无需使用 IPv4:只要您正确配置了路由并分配了有效地址,JGroups 就与 IPv6 兼容。

jgroups.bind_addr 需要与您的 JGroups 配置中的占位符名称匹配,以防您不使用默认名称。

默认配置使用 distribution 作为缓存模式,并使用 jgroups-tcp.xml 配置进行 JGroups,该配置包含在 Infinispan jar 中,作为 Infinispan 用户的默认配置。让我们看看如何重新配置它

示例 20. 重新配置缓存模式并覆盖 JGroups 配置
<?xml version="1.0" encoding="UTF-8"?>
<infinispan
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="urn:infinispan:config:9.1 http://www.infinispan.org/schemas/infinispan-config-9.1.xsd"
    xmlns="urn:infinispan:config:9.1">

    <jgroups>
        <stack-file name="custom-stack" path="my-jgroups-conf.xml" />
    </jgroups>

    <cache-container name="HibernateOGM" default-cache="DEFAULT">
        <transport stack="custom-stack" />

        <!-- *************************************** -->
        <!--     Default cache used as template      -->
        <!-- *************************************** -->
        <distributed-cache name="DEFAULT" mode="SYNC">
            <locking striping="false" acquire-timeout="10000"
                concurrency-level="500" write-skew="false" />
            <transaction mode="NON_DURABLE_XA" />
            <state-transfer enabled="true" timeout="480000"
                await-initial-transfer="true" />
        </distributed-cache>

        <!-- Override the cache mode: -->
        <replicated-cache name="User" mode="SYNC">
            <locking striping="false" acquire-timeout="10000"
                concurrency-level="500" write-skew="false" />
            <transaction mode="NON_DURABLE_XA" />
            <state-transfer enabled="true" timeout="480000"
                await-initial-transfer="true" />
        </replicated-cache>

        <distributed-cache name="Order" mode="SYNC">
            <locking striping="false" acquire-timeout="10000"
                concurrency-level="500" write-skew="false" />
            <transaction mode="NON_DURABLE_XA" />
            <state-transfer enabled="true" timeout="480000"
                await-initial-transfer="true" />
        </distributed-cache>

        <distributed-cache name="associations_User_Order" mode="SYNC">
            <locking striping="false" acquire-timeout="10000"
                concurrency-level="500" write-skew="false" />
            <transaction mode="NON_DURABLE_XA" />
            <state-transfer enabled="true" timeout="480000"
                await-initial-transfer="true" />
        </distributed-cache>

    </cache-container>

</infinispan>

在上面的示例中,我们指定了自定义 JGroups 配置文件,并将默认缓存的缓存模式设置为 distribution;这将被 Orderassociations_User_Order 缓存继承。但对于 User,我们选择了(为了这个示例)使用 replication

现在您已配置了集群,请在多个节点上启动该服务。每个节点都需要相同的配置和 jar 包。

我们只是展示了如何覆盖集群模式和网络堆栈以完整起见,但您无需这样做!

从默认配置开始,看看是否适合您。当您更接近投入生产时,可以微调这些设置。

9.3.7. 事务

Infinispan 支持事务,并与任何标准 JTA TransactionManager 集成;这对 JPA 用户来说是一个很大的优势,因为它允许体验与使用 RDBMS 数据库时我们习惯的体验 类似 的行为。

此功能现在可用于 Infinispan 嵌入式和 Hot Rod 客户端用户。

如果您让 Hibernate OGM 启动和管理 Infinispan,您可以跳过此步骤,因为它将注入您已在 Hibernate / JPA 配置中设置的相同 TransactionManager 实例。

如果您通过使用 JNDI 查找方法来提供已启动的 Infinispan CacheManager 实例,那么您必须确保 CacheManager 使用与 Hibernate 相同的 TransactionManager

示例 21. 在 Infinispan 配置中配置 JBoss Standalone TransactionManager 查找
<default>
   <transaction
      transactionMode="TRANSACTIONAL"
      transactionManagerLookupClass=
    "org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup" />
</default>

Infinispan 嵌入式支持不同的事务模式,如 PESSIMISTICOPTIMISTIC,支持 XA 恢复,并提供更多配置选项;有关更高级的配置选项,请参阅 Infinispan 用户指南

9.3.8. Infinispan 嵌入式存储过程

用于 Infinispan 嵌入式方言的存储过程只是一个 Java RunnableCallable 类。该类必须定义在应用程序类路径上,然后可以使用 JPA 存储过程 API 来执行它。

调用它有两种方法:* 直接使用类的名称作为过程名称 * 将任意名称映射到名为 ___stored_procedures 的特殊缓存。每个映射都需要一个条目,其中过程名称作为键,完整类名称作为值。

参数由 Hibernate OGM 自动填充。目前只支持命名参数。

9.3.9. 将 Lucene 索引存储在 Infinispan 中

Hibernate Search 可用于高级查询功能(参见 查询您的实体),它需要一些地方来存储其嵌入式 Apache Lucene 引擎的索引。

存储这些索引的常见位置是文件系统,这是 Hibernate Search 的默认位置;但是,如果您的目标是在多个节点上扩展您的 NoSQL 引擎,则需要共享此索引。网络共享文件系统是一种可能性,但我们不建议这样做。通常,最佳选择是将索引存储在您正在使用的任何 NoSQL 数据库中(或其他专用数据库中)。

即使您不打算将数据存储在 Infinispan 中,您也可能会发现本节很有用。

Infinispan 项目提供了一个适配器,可以插入 Apache Lucene,以便它将索引写入 Infinispan 并从中搜索数据。由于 Infinispan 可以通过使用 CacheStore 作为其他 NoSQL 存储引擎的应用程序缓存(参见 管理数据大小),因此您可以使用此适配器将 Lucene 索引存储在 Infinispan 支持的任何 NoSQL 存储中

  • JDBC 数据库

  • Cassandra

  • 文件系统(但在 Infinispan 级别正确锁定)

  • MongoDB

  • HBase

  • LevelDB

  • 一个辅助的(独立的)Infinispan 网格

如何配置?以下是一张简单的速查表,可以帮助您开始使用这种类型的设置

  • org.infinispan:infinispan-directory-provider:9.4.0.Final 添加到您的依赖项中

  • 设置以下配置属性

    • hibernate.search.default.directory_provider = infinispan

    • hibernate.search.default.exclusive_index_use = false

    • hibernate.search.infinispan.configuration_resourcename = [infinispan 配置文件名]

此配置很简单,在大多数情况下都能正常工作,但请记住,使用 'exclusive_index_use' 既不会快也不会可扩展。对于高性能、高并发或生产使用,请参阅 Infinispan 文档 以了解更高级的配置选项和调整。

引用的 Infinispan 配置应该定义一个 CacheStore,以便将索引加载/存储到所选 NoSQL 引擎中。它还应该定义三个缓存名称

表 1. 用于存储索引的 Infinispan 缓存
缓存名称 描述 建议的集群模式

LuceneIndexesLocking

传输锁定信息。不需要缓存存储。

replication

LuceneIndexesData

包含大部分 Lucene 数据。需要缓存存储。

distribution + L1

LuceneIndexesMetadata

存储索引段的元数据。需要缓存存储。

replication

此配置在写入操作方面不会很好地扩展:要做到这一点,您应该阅读 Hibernate Search 中的 master/slave 和分片选项。完整的说明和配置选项可以在 Hibernate Search 参考指南 中找到

一些 NoSQL 直接支持存储 Lucene 索引,在这种情况下,您可以通过为 Hibernate Search 实现自定义 DirectoryProvider 来跳过 Infinispan Lucene 集成。欢迎您分享代码并将其合并到 Hibernate Search 中,以便其他人使用、检查、改进和维护。

9.4. Hibernate OGM & Infinispan Server 通过 Hot Rod

在本节中,我们将了解如何配置 Hibernate OGM 以连接到“使用 Hot Rod 协议的 Infinispan Server”,为了简洁起见,我们将称之为“Infinispan 远程”,以将其与“Infinispan 嵌入式”区分开来。

在此模式下,Hibernate OGM 无法启动或以其他方式控制 Infinispan 的生命周期,因此我们将假设您已经有一个运行的 Infinispan Server 节点集群。有关设置方法的说明,请参见 Infinispan Server 指南

好消息是,由于它是一个独立的服务,因此在 Hibernate OGM 中无需进行太多配置。

Hibernate OGM 对 Infinispan Remote 的支持被认为是实验性的。特别是,存储格式尚未确定。

9.4.1. 添加 Infinispan Remote 依赖项

要使用 Hibernate OGM 连接到使用 Hot Rod 协议的 Infinispan Server,您将需要以下扩展及其传递依赖项(包括 Hot Rod 客户端等)

<dependency>
    <groupId>org.hibernate.ogm</groupId>
    <artifactId>hibernate-ogm-infinispan-remote</artifactId>
    <version>5.4.1.Final</version>
</dependency>

9.4.2. Infinispan Remote 的配置属性

首先,让 Hibernate 知道您想要使用 OGM Infinispan Remote 数据存储,方法是将 hibernate.ogm.datastore.provider 属性设置为 infinispan_remote

下一步是配置 Hot Rod 客户端。您有两个选择

  • 提供一个包含所有 Hot Rod 客户端配置属性的资源文件

  • 使用自定义前缀包含所有 Hot Rod 客户端配置属性,如下所述。

要使用外部配置资源,请将 hibernate.ogm.infinispan_remote.configuration_resource_name 配置属性设置为资源名称。

示例 22. 使用单独的资源配置 Hot Rod 客户端
<?xml version="1.0"?>
<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_2_0.xsd"
    version="2.0">

    <persistence-unit name="ogm-with-hotrod">
        <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider> (1)
        <properties>
            <property name="hibernate.ogm.datastore.provider"
                value="infinispan_remote" /> (2)
            <property name="hibernate.ogm.infinispan_remote.configuration_resource_name"
                value="hotrodclient.properties" /> (3)
        </properties>
    </persistence-unit>
</persistence>
1 选择 Hibernate OGM 作为 JPA 提供者
2 选择 infinispan_remote 作为数据存储
3 指向 Hot Rod 配置文件
infinispan.client.hotrod.server_list = 127.0.0.1:11222
infinispan.client.hotrod.tcp_no_delay = true
infinispan.client.hotrod.tcp_keep_alive = false

## below is connection pooling config
maxActive=-1
maxTotal = -1
maxIdle = -1
whenExhaustedAction = 1
timeBetweenEvictionRunsMillis = 120000
minEvictableIdleTimeMillis = 300000
testWhileIdle = true
minIdle = 1

hotrodclient.properties 是可选的,在这种情况下,Hibernate OGM 将使用以下内容

HotRod 客户端属性

Hibernate OGM 必需值

infinispan.client.hotrod.marshaller

org.hibernate.ogm.datastore.infinispanremote.impl.protostream.OgmProtoStreamMarshaller

infinispan.client.hotrod.force_return_values

true

所有其他属性将使用 Infinispan - Java Hot Rod 客户端 中定义的默认值。

这些用于 infinispan.client.hotrod.marshallerinfinispan.client.hotrod.force_return_values 的值对于保持方言的预期行为是必需的。如果您更改它们,将抛出异常。

或者,您可以在 Hibernate(或 JPA)配置文件中嵌入 Hot Rod 属性,但您必须将 infinispan.client.hotrod. 前缀替换为自定义前缀 hibernate.ogm.infinispan_remote.client.

一些 Hot Rod 客户端配置属性通常不使用前缀 - 具体来说是所有与连接池相关的属性,如前面的示例中 - 这些属性也需要使用 hibernate.ogm.infinispan_remote.client. 前缀。

使用 hibernate.ogm.infinispan_remote.client. 前缀设置的属性将覆盖使用外部资源文件配置的相同属性。

示例 23. 将 Hot Rod 客户端配置属性嵌入 Hibernate 配置中
<?xml version="1.0"?>
<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_2_0.xsd"
    version="2.0">

    <persistence-unit name="ogm-with-hotrod">
        <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider> (1)
        <properties>
            <property name="hibernate.ogm.datastore.provider"
                value="infinispan_remote" /> (2)
            <property name="hibernate.ogm.infinispan_remote.client.server_list"
                value="127.0.0.1:11222" /> (3)
            <property name="hibernate.ogm.infinispan_remote.client.tcp_no_delay"
                value="true" />
            <property name="hibernate.ogm.infinispan_remote.client.tcp_keep_alive"
                value="false" />
            <property name="hibernate.ogm.infinispan_remote.client.maxActive"
                value="-1" />
            <property name="hibernate.ogm.infinispan_remote.client.maxTotal"
                value="-1" />
            <property name="hibernate.ogm.infinispan_remote.client.maxIdle"
                value="-1" />
            <property name="hibernate.ogm.infinispan_remote.client.whenExhaustedAction"
                value="1" />
            <property name="hibernate.ogm.infinispan_remote.client.timeBetweenEvictionRunsMillis"
                value="120000" />
            <property name="hibernate.ogm.infinispan_remote.client.minEvictableIdleTimeMillis"
                value="300000" />
            <property name="hibernate.ogm.infinispan_remote.client.testWhileIdle"
                value="true" />
            <property name="hibernate.ogm.infinispan_remote.client.minIdle"
                value="1" />
        </properties>
    </persistence-unit>
</persistence>
1 选择 Hibernate OGM 作为 JPA 提供者
2 选择 infinispan_remote 作为数据存储
3 包含 Hot Rod 配置属性,只需替换/添加 OGM 前缀即可。

在下一节中,我们将看到几个可能引起您兴趣的更高级的属性。

hibernate.ogm.datastore.create_database

如果设置为 true,Hibernate OGM 将在 Infinispan Server 上创建任何缺少的缓存定义。这要求 Infinispan Server 配置定义一个默认配置,因为它将被复制到新定义的缓存中。如果设置为 false,则在预期缓存但未在服务器上显式配置时会抛出异常。默认为 false

hibernate.ogm.infinispan_remote.cache_configuration

Hibernate OGM 将用于在 Infinispan Server 上创建新缓存的默认缓存配置的名称。仅当 hibernate.ogm.datastore.create_database 设置为 true 时才使用此选项。有关缓存配置的更多详细信息,请查看部分 缓存创建和配置

hibernate.ogm.infinispan_remote.schema_capture_service

如果您将其设置为 org.hibernate.ogm.datastore.infinispanremote.schema.spi.SchemaCapture 的实现,则可以收集任何生成的 Protobuf 架构。可能对与其他工具的集成有用。您可以提供一个完全限定的类名或一个 SchemaCapture,或者如果您以编程方式启动 Hibernate,则可以在配置属性中传递一个 SchemaCapture 实例。

hibernate.ogm.infinispan_remote.schema_package_name

定义生成的 Protobuf 架构的包名称。默认为 HibernateOGMGenerated。有助于隔离使用相同 Infinispan Server 实例的不同应用程序。

hibernate.ogm.infinispan_remote.schema_file_name

定义生成的 Protobuf 架构的文件名。默认为 'Hibernate_OGM_Generated_schema.proto'。文件名必须具有有效的 *.proto 扩展名。

hibernate.ogm.infinispan_remote.schema_override_resource

可以覆盖生成的 Protobuf 架构,提供用户定义的 Protobuf 架构资源。属性值是字符串类型,它可以表示一个 **类路径元素**、一个 **URL** 或一个 **文件系统路径**。Hibernate OGM 将使用指定的 Protobuf 架构而不是生成的架构。这不会影响实体的编码方式,因此指定的架构应该与生成的架构兼容。这可以用来在 JPQL 和本机 Ickle 查询使用的缓存上定义服务器端索引。

hibernate.ogm.cache.transaction.mode

属性用于配置 Infinispan 缓存的事务模式。可能的值为:XANON_DURABLE_XA(默认值)、NON_XANONE(用于禁用事务)。有关更多信息,请参见章节 Infinispan Remote 事务

9.4.3. 数据编码:Protobuf 架构

使用 Infinispan Remote 后端,您的数据将使用 Protocol Buffers(也称为 Protobuf)进行编码。

Protocol Buffers 是一种与语言无关、与平台无关的扩展机制,用于序列化结构化数据

— https://developers.google.com/protocol-buffers/

此编码策略将在数据到数据网格的传输过程中以及作为数据网格上的存储格式使用。

Google 的 Java 开发人员工具的典型用法要求您下载 protoc 编译器来生成 Java 存根;使用 Hibernate OGM 时,您不需要这样做,因为后端将根据您的实体动态生成编码和解码函数。

让 Hibernate OGM 为您生成架构的好处是它更容易上手,但有一个缺点:您无法直接控制 protobuf 架构。它将部署此架构(或期望部署兼容的架构),因为它将使用其生成的编解码器来读写数据到 Infinispan Server。

使用 Google 的开发人员工具的典型用法要求您下载 protoc 编译器来生成 Java 存根;使用 Hibernate OGM 时,您不需要这样做,因为后端将根据您的实体动态生成编码和解码函数。

另一个确保部署的 protobuf 架构是以前架构的兼容演变的原因是,确保您仍然可以读取已存储在数据网格中的数据。

请记住,Protobuf 架构在传输存储过程中都使用。它在数据的传输过程中也使用的事实是与 SQL 数据库架构的关键区别。

例如,即使属性“A”在存储方面不可为空,您仍然希望它在 protobuf 架构中被标记为 optional,以便例如检索数据属性的子集,而不必总是检索属性“A”。

您无需对架构做任何事情:Hibernate OGM 会在 Hibernate 启动时自动将其部署到 Infinispan 数据网格。但是,您可能希望记住这一点,以便能够在不丢失数据的情况下演变架构,并能够为不使用 Hibernate OGM 的其他 Infinispan 客户端生成解码器。

已部署的模式可以从 Infinispan 服务器获取;Hibernate OGM 还会在日志类别 org.hibernate.ogm.datastore.infinispanremote.impl.protobuf.schema 中的 INFO 级记录生成的模式。

9.4.4. Infinispan 远程数据提供程序的存储原理

这实际上很简单。

假设您将实体映射到传统的基于表的 RDBMS;现在,您不再使用表,而是使用缓存。每个缓存都有一个名称和一个一致的模式,并且对于每个缓存,我们都定义了一个带有一些属性(ID,也称为主键)的键。

关系通过编码“外键”来映射;这些外键要么用作键来执行对另一个表的键查找,要么可以在其他表的查询中使用来识别基数大于 1 的关系。

所以让我们重点介绍与关系世界的区别

参照完整性

虽然我们可以使用基于外键的关系,但 Infinispan 没有参照完整性的概念。Hibernate 能够维护完整性,因为它不会“忘记”陈旧的引用,但有可能在这样的维护期间中断 Hibernate OGM,然后引入完整性破坏。

何时可能会破坏完整性

使用默认情况下启用的事务,我们可以降低风险。相反,如果没有事务,当工作单元涉及多个操作时,我们可能会冒着发生部分写入(更新、删除、插入)的风险;某些操作将被刷新到数据存储中,而其他操作不会。例如,假设您在一次交互中创建一个新实体,删除一个旧实体并更新从旧实体到新实体的关联,这将对应于三个不同的远程调用:实体插入、实体删除和关联更新。如果在第三次调用期间出现网络问题,我们可能会进行部分写入,其中只有第一个和第二个操作实际上存储在远程存储上,这可能会导致关联的参照完整性被破坏。因为,正如我们所说,Infinispan 不了解参照完整性约束。

如何检测完整性破坏

不幸的是,目前检测参照完整性错误的唯一方法是检查日志以查找错误消息,或者定期监控关联缓存和联接列值。我们建议您保留默认的事务配置并依赖它。

一个键。一个值。

在键值存储中,两个元素是不同的、独立的对象。Hibernate OGM 生成的模式以及由此产生的所有操作都将分别对待和编码这两个对象。您会注意到键的属性也编码在值中,因为无法例如对键的属性运行范围查询。

序列和自动递增值

Infinispan 现在支持序列,它们由存在 @SequenceGenerator 注释的方言创建。另一方面,@TableGenerator 仍然使用旧的 Hibernate OGM“比较和设置”实现;如果您的实体映射使用序列或自动递增主键,Hibernate OGM 会利用此类 CAS 操作来模拟对序列或自动递增主键的需求,但此解决方案在高负载下可能无法正常工作:确保使用不同的策略,例如显式分配 ID,或使用 org.hibernate.id.UUIDGenerator 生成器。如果检测到此类 CAS 操作上的过度旋转,Hibernate OGM 会记录警告。

未映射到 JDBC 类型,而是映射到 Protobuf 类型

您不是将 Java 属性映射到相应的 JDBC(SQL)类型,而是将 Java 属性映射到 Protobuf 类型。请参阅 protobuf 文档 以了解协议缓冲区“原始”类型的概述。

示例 24. 针对简单实体的示例自动生成的 Protobuf 模式
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Hypothesis {

    @Id String id;

    String description;

    @Column(name = "pos")
    int position;

}
package HibernateOGMGenerated; (1)

message Hypothesis_id { (2)
    required string id = 1;
}

message Hypothesis {
    required string id = 1;
    optional string description = 2;
    optional int32 pos = 3;  (3)
}
1 默认 Protobuf 包名称。
2 针对键值对的键的专用消息类型
3 pos 属性名称遵循 @Column 注释的选项。

上面的示例显示了 Protobuf 模式的外观,它是根据映射实体自动生成的。Hibernate ORM 支持的任何属性类型都将转换为匹配的 Protobuf 类型。

每个表都需要一个具有相同名称的缓存

在关系数据库世界中,当 Hibernate 定义模式时,这会隐式创建表;Infinispan 上的情况并非如此。

对于 Infinispan,Protobuf 模式只是解锁了使用这种有效载荷传输消息(读/写)的功能,并允许远程服务器处理字段,例如执行查询并从存储的条目中提取投影。因此,这建立了一个传输和存储编码协议,但不会实际启动或分配任何存储缓存。

根据惯例,Hibernate OGM 会写入几个名为 Cache 的缓存,将每个“表名”映射到“缓存名”。在上面的示例中,如果有一个 Hypothesis 实体,它将写入名为 Hypothesis 的缓存。

好处是您可以独立地调整或查询每个缓存(每个“表”);例如,您可以将最重要数据的缓存配置为具有同步缓存存储,该存储将数据复制到关系数据库,并让不太重要的条目使用异步缓存存储或根本不使用缓存存储,以牺牲冗余来换取性能。

9.4.5. 缓存创建和配置

如果远程 Infinispan 集群中不存在缓存,Hibernate OGM 可以创建它。您可以通过设置属性 hibernate.ogm.datastore.create_database=true 来启用此行为。

默认情况下,使用以下默认配置创建新的缓存

示例 25. Hibernate OGM 针对新的 Infinispan 缓存的默认配置
<infinispan>
  <cache-container>
    <distributed-cache-configuration name="configuration">
      <locking striping="false" acquire-timeout="10000" concurrency-level="50" isolation="REPEATABLE_READ"/>
      <transaction locking="PESSIMISTIC" mode="%s" /> (1)
      <expiration max-idle="-1" />
      <indexing index="NONE" />
      <state-transfer timeout="480000" await-initial-transfer="true" />
    </distributed-cache-configuration>
  </cache-container>
</infinispan>
1 事务模式属性将填充属性 hibernate.ogm.cache.transaction.mode 的值

如果在 Infinispan 服务器上定义了不同的默认配置,则可以使用属性 hibernate.ogm.datastore.cache_configuration 来引用它。这意味着 Hibernate OGM 将使用选定的配置创建新的缓存。

如果您的某些实体需要不同的配置,可以使用 @CacheConfiguration 注释。此注释将使用为带注释的实体定义的配置覆盖全局属性的行为。

请注意,Hibernate OGM 仅在缓存不存在时才创建缓存,并且它不会更新现有缓存的配置。

注释和属性引用的缓存配置必须存在于 Infinispan 集群中。否则将抛出异常。

假设我们有两个实体:SheepBlackSheep

示例 26. 具有不同缓存配置的实体示例
import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Sheep {

    @Id
    String name;

    // ...

}
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.ogm.datastore.infinispanremote.options.cache.CacheConfiguration;

@Entity
@CacheConfiguration("black_sheep")
public class BlackSheep {

    @Id
    String name;

    // ...

}

捆绑到一个 persistence.xml 中,这样

<?xml version="1.0"?>
<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_2_0.xsd"
    version="2.0">

    <persistence-unit name="ogm-with-hotrod">
        <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
        <properties>
            <property name="hibernate.ogm.datastore.provider"
                value="infinispan_remote" />

            <property name="hibernate.ogm.infinispan_remote.configuration_resource_name"
                value="hotrodclient.properties" />

            <property name="hibernate.ogm.datastore.create_database"
                value="true" /> (1)

            <property name="hibernate.ogm.infinispan_remote.cache_configuration"
                value="sheep" /> (2)

        </properties>
    </persistence-unit>
</persistence>
1 启用缓存创建
2 选择sheep 作为默认缓存配置

在这种情况下,Hibernate OGM 将使用配置sheep 创建Sheep 缓存,并使用black_sheep 缓存配置创建BlackSheep 缓存。

Hibernate OGM 默认配置 是事务性的。

我们通常建议使用事务性缓存,除非您确信数据一致性对您的用例并不重要。

9.4.6. 远程查询功能

Hibernate OGM 后端可以将 JPQL 查询转换为相应的 Infinispan 远程本机查询,或者它可以直接执行本机查询。不过,有一些限制

  • 不支持使用 TABLE_PER_CLASS 继承策略的多态实体上的查询

  • 关联的投影尚未实现(示例:从关联中选择 h.author.name 从 Hypothesis h 中选择,其中 author 是一个一对一关联)

9.4.7. Infinispan 远程事务

从版本5.4.0.CR1 开始,终于可以使用 HotRot 客户端上的事务。为了回退到以前的版本,只需将属性“hibernate.ogm.cache.transaction.mode”属性设置为 NONE 值即可。将模式设置为 NONE 后,事务将被禁用,然后客户端能够处理事务性缓存和非事务性缓存。相反,如果启用了事务,默认情况下它们是启用的,所有缓存都应定义为事务性的。换句话说,目前,事务性客户端无法处理非事务性缓存。因此,客户端侧定义的缓存的默认配置使用与客户端相同的事务模式。

事务通过 Infinispan HotRod 客户端实现,模仿嵌入式 Repeatable ReadOptimistic Locking 语义。即使是强制性的,使用当前的 Infinispan 版本,也必须使用

  1. 悲观锁定

  2. REPEATABLE_READ 作为隔离级别

事实上,这些也是客户端侧定义的缓存的默认配置的当前值。

重要的是要强调这样一个事实,即使缓存必须使用悲观锁定义,但客户端看到的行为与使用乐观锁时相同。

有关 HotRod 事务的更多信息,请参阅 Infinispan 用户指南

9.4.8. Infinispan 远程存储过程

远程方言现在支持存储过程。在远程网格中执行代码有两种方法:服务器任务远程脚本

Hibernate OGM 能够使用标准 JPA 存储过程语法执行两者。为了能够执行,脚本或任务必须首先由用户在服务器端定义。例如,SQL 数据存储的存储过程,它应该首先在数据库中定义,然后才能执行。

要运行远程脚本,脚本条目必须首先位于 Infinispan 远程缓存中。过程的名称是对应条目脚本的键。参数名称是在脚本本身的第一行定义的。有关更多信息,请参阅 Infinispan 指南 - 远程脚本

而要运行服务器任务,您需要首先将 java 任务作为 jar 部署到 Infinispan 服务器上。在这种情况下,过程的名称由任务服务类的 getName 方法定义。输入参数是从同一个任务服务类中的 TaskContext 中提取的。有关更多信息,请参阅 Infinispan 指南 - 服务器任务

在这两种情况下,只支持命名参数,而位置参数目前尚不支持。

9.4.9. 已知限制和未来改进

Infinispan 远程数据提供程序有一些已知的限制,其中一些无法在没有进一步开发 Infinispan 本身的情况下解决。

索引

Infinispan 直接支持嵌入在其 protobuf 模式定义中的 Hibernate Search 注释;这将使对它们的查询能够使用索引。Hibernate OGM 尚未在其生成的模式中生成这些注释。

对写入倾斜检查的本机支持

Hot Rod 客户端对数据网格条目的版本控制具有本机支持,但这在所有客户端 API 中都不受支持。为了使 Hibernate OGM 能够一致地使用版本控制,需要对 Hot Rod 客户端 API 进行增强。

枚举

Protobuf 对枚举类型具有本机支持,但 JPA 注释强制在序数或字符串编码之间选择。我们可能需要引入“本机”编码,可能是通过新的映射注释。Hibernate OGM 支持本机 protobuf 编码,但 JPA 元数据始终会强制使用序数或字符串表示。

嵌套和嵌入

Protobuf 模式允许我们将对象(包括对象系列)作为嵌套元素嵌入。这可以允许与基于文档的 NoSQL 存储(例如我们的 MongoDB 方言)类似的映射,但目前尚不支持。

自动创建 Cache

部署Protobuf Schema时,如果缓存未定义,也应自动定义并启动所需的缓存。目前,Hot Rod 协议不支持此操作。

9.5. 存储原则

简单来说,每个实体都存储在一个单独的键下。值本身是一个包含列/值对的映射。

从一个实体实例到另一个实体(集)的每个关联都存储在一个单独的键下。该值包含指向该实体(集)的导航信息。

9.5.1. 属性和内置类型

每个实体都由一个映射表示。每个属性,更准确地说,每个列都由该映射中的一个条目表示,键为列名。

Hibernate OGM 默认支持以下属性类型:

  • java.lang.String

  • java.lang.Character(或 char 原语);可选地,可以使用注解@Type(type = "true_false")@Type(type = "yes_no")@Type(type = "numeric_boolean") 将布尔属性分别映射到字符 'T'/'F'、'Y'/'N' 或整数值 0/1。

  • java.lang.Boolean(或 boolean 原语);

  • java.lang.Byte(或 byte 原语)

  • java.lang.Short(或 short 原语)

  • java.lang.Integer(或 integer 原语)

  • java.lang.Long(或 long 原语)

  • java.lang.Integer(或 integer 原语)

  • java.lang.Float(或 float 原语)

  • java.lang.Double(或 double 原语)

  • java.math.BigDecimal

  • java.math.BigInteger

  • java.util.Calendar

  • java.util.Date

  • java.util.UUID

  • java.util.URL

Hibernate OGM 不会在 Infinispan 中存储空值,将值设置为 null 等同于从 Infinispan 中删除相应的条目。

这可能对空值查询产生影响。

9.5.2. 标识符

实体标识符用于构建实体在缓存中存储的键。

键包含以下信息:

  • 标识符列名

  • 标识符列值

  • 实体表(对于CACHE_PER_KIND 策略)

CACHE_PER_TABLE 中,表名是从缓存名推断出来的。在CACHE_PER_KIND 中,表名是识别通用缓存中实体的必要条件。

示例 27. 定义标识符为原语类型
@Entity
public class Bookmark {

    @Id
    private Long id;

    private String title;

    // getters, setters ...
}
表 2. CACHE_PER_TABLEBookmark 缓存的内容
映射条目

["id"], [42]

id

42

title

"Hibernate OGM 文档"

表 3. CACHE_PER_KINDENTITIES 缓存的内容
映射条目

"Bookmark", ["id"], [42]

id

42

title

"Hibernate OGM 文档"

示例 28. 使用 @EmbeddedId 定义标识符
@Embeddable
public class NewsID implements Serializable {

    private String title;
    private String author;

    // getters, setters ...
}

@Entity
public class News {

    @EmbeddedId
    private NewsID newsId;
    private String content;

    // getters, setters ...
}
表 4. CACHE_PER_TABLENews 缓存的内容
映射条目

[newsId.author, newsId.title], ["Guillaume", "如何使用 Hibernate OGM?"]

newsId.author

"Guillaume"

newsId.title

"如何使用 Hibernate OGM?"

content

"简单,就像 ORM 一样,但使用的是 NoSQL 数据库"

表 5. CACHE_PER_KINDENTITIES 缓存的内容
映射条目

"News", [newsId.author, newsId.title], ["Guillaume", "如何使用 Hibernate OGM?"]

newsId.author

"Guillaume"

newsId.title

"如何使用 Hibernate OGM?"

content

"简单,就像 ORM 一样,但使用的是 NoSQL 数据库"

Infinispan Embedded 的标识符生成策略

Infinispan Embedded 方言将使用 Infinispan 集群计数器 来生成 id 和序列。

如果集群计数器尚未定义,Hibernate OGM 将使用默认配置创建它。以下可能是创建计数器的最简单情况。

示例 29. 默认计数器的示例
@Entity
public class GuitarPlayer {

    @Id
    @GeneratedValue
    private long id;

    // getters, setters ...
}

在这种情况下,Hibernate OGM 将创建一个名为 hibernate_sequence 的强持久计数器,初始值为 0。

默认配置将创建 PERSISTENT Infinispan 计数器。您需要在 infinispan 配置中定义一个位置,使用以下属性存储这些计数器:

<infinispan>
    ...
    <cache-container>

        <global-state>
            <persistent-location path="/counters"/>
        </global-state>

        ...
    </cache-container>
</infinispan>

如果属性缺失,将抛出异常。

您可以使用 @SequenceGenerator@TableGenerator 覆盖其中一些属性。

示例 30. 使用 @SequenceGenerator 定义计数器
@Entity
public class GuitarPlayer {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "gen1")
    @SequenceGenerator( name = "gen1",
                        sequenceName = "GuitarPlayerCounter",
                        initialValue = 5 )
    private long id;

    // getters, setters ...
}

这将创建一个名为 GuitarPlayerCounter 的计数器,初始值为 5。

如果您使用 @TableGenerator,除了使用 pkColumnValue 属性定义计数器名称之外,没有实质区别。

请注意,如果您需要对计数器进行更多控制,您仍然可以在 Infinispan 配置文件中重新定义它。以下是如何定义 VOLATILE 计数器的示例。

示例 31. 创建 VOLATILE 计数器
<infinispan>
...
    <cache-container>

    ...

        <!-- Clustered Counters are defined at runtime by InfinispanDialect -->
        <counters xmlns="urn:infinispan:config:counters:9.1"
            num-owners="4" reliability="CONSISTENT">

            <strong-counter name="GuitarPlayerCounter"
                initial-value="0" storage="VOLATILE">

                <lower-bound value="0" />
            </strong-counter>
        </counters>
    </cache-container>
</infinispan

在最后一种情况下,Hibernate OGM 不会创建计数器,而是使用 Infinispan 配置中定义的计数器。

有关计数器配置的详细信息,请参阅 Infinispan 文档

通过 Hot Rod 的 Infinispan Remote 的标识符生成策略

由于 Infinispan 本身没有原生序列或标识列支持,因此使用表策略模拟了这些支持,但它们的默认值有所不同。如果您想生成单调递增的标识符,我们强烈建议您显式使用 TABLE 策略。

但是,如果可以,请使用纯内存中可扩展的策略,例如 UUID 生成器。

示例 32. 使用默认值的 TABLE 标识符生成策略
@Entity
public class GuitarPlayer {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE)
    private long id;

    private String name;

    // getters, setters ...
}
表 6. CACHE_PER_TABLEhibernate_sequences 缓存的内容
下一个值

["sequence_name"], ["default"]

2

表 7. CACHE_PER_KINDIDENTIFIERS 缓存的内容
下一个值

"hibernate_sequences", ["sequence_name"], ["default"]

2

如您所见,在 CACHE_PER_TABLE 中,键不包含 id 源表名。它是从承载该键的缓存名推断出来的。

示例 33. 使用自定义表的 TABLE 标识符生成策略
@Entity
public class GuitarPlayer {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "guitarGen")
    @TableGenerator(
        name = "guitarGen",
        table = "GuitarPlayerSequence",
        pkColumnName = "seq"
        pkColumnValue = "guitarPlayer",
    )
    private long id;

    // getters, setters ...
}
表 8. CACHE_PER_TABLEGuitarPlayerSequence 缓存的内容
下一个值

["seq"], ["guitarPlayer"]

2

表 9. CACHE_PER_KINDIDENTIFIERS 缓存的内容
下一个值

"GuitarPlayerSequence", ["seq"], ["guitarPlayer"]

2

示例 34. SEQUENCE 标识符生成策略
@Entity
public class Song {

  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "songSequenceGenerator")
  @SequenceGenerator(
      name = "songSequenceGenerator",
      sequenceName = "song_sequence",
      initialValue = 2,
      allocationSize = 20
  )
  private Long id;

  private String title;

  // getters, setters ...
}
表 10. CACHE_PER_TABLEhibernate_sequences 缓存的内容
下一个值

["sequence_name"], ["song_sequence"]

11

表 11. CACHE_PER_KINDIDENTIFIERS 缓存的内容
下一个值

"hibernate_sequences", "["sequence_name"], ["song_sequence"]

11

9.5.3. 实体

使用 CACHE_PER_TABLE 策略时,实体存储在以实体名称命名的缓存中。在 CACHE_PER_KIND 策略中,实体存储在名为 ENTITIES 的单个缓存中。

键包含以下信息:

  • 标识符列名

  • 标识符列值

  • 实体表(对于CACHE_PER_KIND 策略)

CACHE_PER_TABLE 中,表名是从缓存名推断出来的。在CACHE_PER_KIND 中,表名是识别通用缓存中实体的必要条件。

条目值是 org.infinispan.atomic.FineGrainedMap 的实例,其中包含所有实体属性(更准确地说,是列)。每个列名和值都作为键/值对存储在映射中。我们使用此专用映射,因为 Infinispan 能够以更高效的方式传输更改。

示例 35. 实体的默认 JPA 映射
@Entity
public class News {

    @Id
    private String id;
    private String title;

    // getters, setters ...
}
表 12. CACHE_PER_TYPENews 缓存的内容
映射条目

["id"], ["1234-5678"]

id

"1234-5678"

title

"关于 NoSQL 的优点"

表 13. CACHE_PER_KINDENTITIES 缓存的内容
映射条目

"News", ["id"], ["1234-5678"]

id

"1234-5678"

title

"关于 NoSQL 的优点"

如您所见,对于 CACHE_PER_TYPE,表名不是键的一部分。在本节的其余部分,我们不再展示 CACHE_PER_KIND 策略。

示例 36. 使用 @Table 和 @Column 重命名字段和集合
@Entity
@Table(name = "Article")
public class News {

    @Id
    private String id;

    @Column(name = "headline")
    private String title;

    // getters, setters ...
}
表 14. Article 缓存的内容
映射条目

["id"], ["1234-5678"]

id

"1234-5678"

headline

"关于 NoSQL 的优点"

嵌入式对象和集合
示例 37. 嵌入式对象
@Entity
public class News {

    @Id
    private String id;
    private String title;

    @Embedded
    private NewsPaper paper;

    // getters, setters ...
}

@Embeddable
public class NewsPaper {

    private String name;
    private String owner;

    // getters, setters ...
}
表 15. News 缓存的内容
映射条目

["id"], ["1234-5678"]

id

"1234-5678"

title

"关于 NoSQL 的优点"

paper.name

"NoSQL 预言杂志"

paper.owner

"Delphy"

示例 38. 具有一个属性的 @ElementCollection
@Entity
public class GrandMother {

    @Id
    private String id;

    @ElementCollection
    private List<GrandChild> grandChildren = new ArrayList<GrandChild>();

    // getters, setters ...
}

@Embeddable
public class GrandChild {

    private String name;

    // getters, setters ...
}
表 16. GrandMother 缓存的内容
映射条目

["id"], ["granny"]

id

"granny"

表 17. CACHE_PER_TYPEassociations_GrandMother_grandChildren 缓存的内容
行键 行映射条目

["GrandMother_id"], ["granny"]

["GrandMother_id", "name"], ["granny", "Leia"]

GrandMother_id

"granny"

name

"Leia"

["GrandMother_id", "name"], ["granny", "Luke"]

GrandMother_id

"granny"

name

"Luke"

表 18. CACHE_PER_KINDASSOCIATIONS 缓存的内容
行键 行映射条目

"GrandMother_grandChildren", ["GrandMother_id"], ["granny"]

["GrandMother_id", "name"], ["granny", "Leia"]

GrandMother_id

"granny"

name

"Leia"

["GrandMother_id", "name"], ["granny", "Luke"]

GrandMother_id

"granny"

name

"Luke"

在这里,我们看到元素集合存储在单独的缓存和条目中。关联键由以下部分组成:

  • 指向该关联所有者的外键列名

  • 指向该关联所有者的外键列值

  • CACHE_PER_KIND 方法中,所有关联共享同一个缓存,因此需要关联表名。

关联条目是一个映射,包含集合中每个条目的表示。该映射的键由以下部分组成:

  • 唯一标识特定集合条目的列名称(例如,对于 Set,这是所有列)

  • 唯一标识特定集合条目的列值

附加到该集合条目键的值是包含键值对列名/列值的映射。

示例 39. 具有 @OrderColumn 的 @ElementCollection
@Entity
public class GrandMother {

    @Id
    private String id;

    @ElementCollection
    @OrderColumn( name = "birth_order" )
    private List<GrandChild> grandChildren = new ArrayList<GrandChild>();

    // getters, setters ...
}

@Embeddable
public class GrandChild {

    private String name;

    // getters, setters ...
}
表 19. GrandMother 缓存的内容
映射条目

["id"], ["granny"]

id

"granny"

表 20. GrandMother_grandChildren 缓存的内容
行键 行映射条目

["GrandMother_id"], ["granny"]

["GrandMother_id", "birth_order"], ["granny", 0]

GrandMother_id

"granny"

birth_order

0

name

"Leia"

["GrandMother_id", "birth_order"], ["granny", 1]

GrandMother_id

"granny"

birth_order

1

name

"Luke"

在这里,我们使用了一个索引集合,为了标识集合中的条目,只需要拥有实体 id 和索引值。

示例 40. 具有 @Embeddable 映射的 @ElementCollection
@Entity
public class ForumUser {

    @Id
    private String name;

    @ElementCollection
    private Map<String, JiraIssue> issues = new HashMap<>();

    // getters, setters ...
}

@Embeddable
public class JiraIssue {

    private Integer number;
    private String project;

    // getters, setters ...
}
表 21. ForumUser 缓存的内容
映射条目

["id"], ["Jane Doe"]

id

"Jane Doe"

表 22. ForumUser_issues 缓存的内容
行键 行映射条目

["ForumUser_id"], ["Jane Doe"]

["ForumUser_id", "issues_KEY"], ["Jane Doe", "issueWithNull"]

ForumUser_id

Jane Doe

issue_KEY

"issueWithNull"

issues.value.project

<null>

issues.value.number

<null>

["ForumUser_id", "issues_KEY"], ["Jane Doe", "issue1"]

ForumUser_id

"Jane Doe"

issue_KEY

"issue1"

issues.value.project

"OGM"

issues.value.number

1253

["ForumUser_id", "issues_KEY"], ["Jane Doe", "issue2"]

ForumUser_id

"Jane Doe"

issue_KEY

"issue2"

issues.value.project

"HSEARCH"

issues.value.number

2000

9.5.4. 关联

实体之间的关联像(集合)嵌入式那样映射,只是目标实体由其标识符(集)表示。

示例 41. 单向一对一
@Entity
public class Vehicle {

    @Id
    private String id;
    private String brand;

    // getters, setters ...
}

@Entity
public class Wheel {

    @Id
    private String id;
    private double diameter;

    @OneToOne
    private Vehicle vehicle;

    // getters, setters ...
}
表 23. Vehicle 缓存的内容
映射条目

["id"], ["V_01"]

id

"V_01"

brand

"Mercedes"

表 24. Wheel 缓存的内容
映射条目

["id"], ["W001"]

id

"W001"

diameter

0.0

vehicle_id

"V_01"

示例 42. 使用 @JoinColumn 的单向一对一
@Entity
public class Vehicle {

    @Id
    private String id;
    private String brand;

    // getters, setters ...
}


@Entity
public class Wheel {

    @Id
    private String id;
    private double diameter;

    @OneToOne
    @JoinColumn( name = "part_of" )
    private Vehicle vehicle;

    // getters, setters ...
}
表 25. Vehicle 缓存的内容
映射条目

["id"], ["V_01"]

id

"V_01"

brand

"Mercedes"

表 26. Wheel 缓存的内容
映射条目

"Wheel", ["id"], ["W001"]

id

"W001"

diameter

0.0

part_of

"V_01"

示例 43. 使用 @MapsId 和 @PrimaryKeyJoinColumn 的单向一对一
@Entity
public class Vehicle {

    @Id
    private String id;
    private String brand;

    // getters, setters ...
}

@Entity
public class Wheel {

    @Id
    private String id;
    private double diameter;

    @OneToOne
    @PrimaryKeyJoinColumn
    @MapsId
    private Vehicle vehicle;

    // getters, setters ...
}
表 27. Vehicle 缓存的内容
映射条目

["id"], ["V_01"]

id

"V_01"

brand

"Mercedes"

表 28. Wheel 缓存的内容
映射条目

["vehicle_id"], ["V_01"]

vehicle_id

"V_01"

diameter

0.0

示例 44. 双向一对一
@Entity
public class Husband {

    @Id
    private String id;
    private String name;

    @OneToOne
    private Wife wife;

    // getters, setters ...
}

@Entity
public class Wife {

    @Id
    private String id;
    private String name;

    @OneToOne(mappedBy="wife")
    private Husband husband;

    // getters, setters ...
}
表 29. Husband 缓存的内容
映射条目

["id"], ["alex"]

id

"alex"

name

"Alex"

wife

"bea"

表 30. Wife 缓存的内容
映射条目

["id"], ["bea"]

id

"bea"

name

"Bea"

表 31. associations_Husband 缓存的内容
行键 映射条目

["wife"], ["bea"]

["id", "wife"], ["alex", "bea"]

id

"alex"

wife

"bea"

示例 45. 单向一对多
@Entity
public class Basket {

    @Id
    private String id;

    private String owner;

    @OneToMany
    private List<Product> products = new ArrayList<Product>();

    // getters, setters ...
}

@Entity
public class Product {

    @Id
    private String name;

    private String description;

    // getters, setters ...
}
表 32. Basket 缓存的内容
映射条目

["id"], ["davide_basket"]

id

"davide_basket"

owner

"Davide"

表 33. Product 缓存的内容
映射条目

["name"], ["啤酒"]

name

"啤酒"

描述

"战术核企鹅"

["name"], ["椒盐卷饼"]

name

"椒盐卷饼"

描述

"Glutino 椒盐卷饼棒"

表 34. associations_Basket_Product 缓存内容
行键 映射条目

["Basket_id"], ["davide_basket"]

["Basket_id", "products_name"], ["davide_basket", "啤酒"]

Basket_id

"davide_basket"

products_name

"啤酒"

["Basket_id", "products_name"], ["davide_basket", "椒盐卷饼"]

Basket_id

"davide_basket"

products_name

"椒盐卷饼"

示例 46. 使用 @JoinTable 的单向一对多关系
@Entity
public class Basket {

    @Id
    private String id;

    private String owner;

    @OneToMany
    @JoinTable( name = "BasketContent" )
    private List<Product> products = new ArrayList<Product>();

    // getters, setters ...
}

@Entity
public class Product {

    @Id
    private String name;

    private String description;

    // getters, setters ...
}
表 35. Basket 缓存内容
映射条目

["id"], ["davide_basket"]

id

"davide_basket"

owner

"Davide"

表 36. Basket 缓存内容
映射条目

["name"], ["啤酒"]

name

"啤酒"

描述

"战术核企鹅"

["name"], ["椒盐卷饼"]

name

"椒盐卷饼"

描述

"Glutino 椒盐卷饼棒"

表 37. associations_BasketContent 缓存内容
行键 映射条目

["Basket_id"], ["davide_basket"]

["Basket_id", "products_name"], ["davide_basket", "啤酒"]

Basket_id

"davide_basket"

products_name

"啤酒"

["Basket_id", "products_name"], ["davide_basket", "椒盐卷饼"]

Basket_id

"davide_basket"

products_name

"椒盐卷饼"

示例 47. 使用带默认值的映射的单向一对多关系
@Entity
public class User {

    @Id
    private String id;

    @OneToMany
    private Map<String, Address> addresses = new HashMap<String, Address>();

    // getters, setters ...
}

@Entity
public class Address {

    @Id
    private String id;
    private String city;

    // getters, setters ...
}
表 38. User 缓存内容
映射条目

["id"], ["user_001"]

id

"user_001"

表 39. Address 缓存内容
映射条目

["id"], ["address_001"]

id

"address_001"

城市

"罗马"

["id"], ["address_002"]

id

"address_002"

城市

"巴黎"

表 40. associations_User_address 缓存内容
行键 映射条目

["User_id"], "user_001"]

["User_id", "addresses_KEY"], ["user_001", "home"]

User_id

"user_001"

addresses_KEY

"home"

addresses_id

"address_001"

["User_id", "addresses_KEY"], ["user_001", "work"]

User_id

"user_002"

addresses_KEY

"work"

addresses_id

"address_002"

示例 48. 使用 @MapKeyColumn 的带映射的单向一对多关系
@Entity
public class User {

    @Id
    private String id;

    @OneToMany
    @MapKeyColumn(name = "addressType")
    private Map<String, Address> addresses = new HashMap<String, Address>();

    // getters, setters ...
}

@Entity
public class Address {

    @Id
    private String id;
    private String city;

    // getters, setters ...
}
表 41. User 缓存内容
映射条目

["id"], ["user_001"]

id

"user_001"

表 42. Address 缓存内容
映射条目

["id"], ["address_001"]

id

"address_001"

城市

"罗马"

["id"], ["address_002"]

id

"address_002"

城市

"巴黎"

表 43. associations_User_address 缓存内容
行键 映射条目

["User_id"], "user_001"]

["User_id", "addressType"], ["user_001", "home"]

User_id

"user_001"

addressesType

"home"

addresses_id

"address_001"

["User_id", "addressType"], ["user_001", "work"]

User_id

"user_002"

addressesType

"work"

addresses_id

"address_002"

示例 49. 单向多对一关系
@Entity
public class JavaUserGroup {

    @Id
    private String jugId;
    private String name;

    // getters, setters ...
}

@Entity
public class Member {

    @Id
    private String id;
    private String name;

    @ManyToOne
    private JavaUserGroup memberOf;

    // getters, setters ...
}
表 44. JavaUserGroup 缓存内容
映射条目

["jugId"], ["summer_camp"]

jugId

"summer_camp"

name

"JUG 夏令营"

表 45. Member 缓存内容
映射条目

["member_id"], ["emmanuel"]

member_id

"emmanuel"

name

"Emmanuel Bernard"

memberOf_jug_id

"summer_camp"

["member_id"], ["jerome"]

member_id

"jerome"

name

"Jerome"

memberOf_jug_id

"summer_camp"

示例 50. 双向多对一关系
@Entity
public class SalesForce {

    @Id
    private String id;
    private String corporation;

    @OneToMany(mappedBy = "salesForce")
    private Set<SalesGuy> salesGuys = new HashSet<SalesGuy>();

    // getters, setters ...
}

@Entity
public class SalesGuy {
    private String id;
    private String name;

    @ManyToOne
    private SalesForce salesForce;

    // getters, setters ...
}
表 46. SalesForce 缓存内容
映射条目

["id"], ["red_hat"]

id

"red_hat"

公司

"Red Hat"

表 47. SalesGuy 缓存内容
映射条目

["id"], ["eric"]

id

"eric"

name

"Eric"

salesForce_id

"red_hat"

["id"], ["simon"]

id

"simon"

name

"Simon"

salesForce_id

"red_hat"

表 48. associations_SalesGuy 缓存内容
行键 映射条目

["salesForce_id"], ["red_hat"]

["salesForce_id", "id"], ["red_hat", "eric"]

salesForce_id

"red_hat"

id

"eric"

["salesForce_id", "id"], ["red_hat", "simon"]

salesForce_id

"red_hat"

id

"simon"

示例 51. 单向多对多关系
@Entity
public class Student {

    @Id
    private String id;
    private String name;

    // getters, setters ...
}

@Entity
public class ClassRoom {

    @Id
    private long id;
    private String lesson;

    @ManyToMany
    private List<Student> students = new ArrayList<Student>();

    // getters, setters ...
}

"数学" 班级有 2 名学生:John Doe 和 Mario Rossi

"英语" 班级有 2 名学生:Kate Doe 和 Mario Rossi

表 49. ClassRoom 缓存内容
映射条目

["id"], [1]

id

1

name

"数学"

["id"], [2]

id

2

name

"英语"

表 50. Student 缓存内容
映射条目

["id"], ["john"]

id

"john"

name

"John Doe"

["id"], ["mario"]

id

"mario"

name

"Mario Rossi"

["id"], ["kate"]

id

"kate"

name

"Kate Doe"

表 51. associations_ClassRoom_Student 缓存内容
行键 映射条目

["ClassRoom_id"], [1]

["ClassRoom_id", "students_id"], [1, "mario"]

ClassRoom_id

1

students_id

"mario"

["ClassRoom_id", "students_id"], [1, "john"]

ClassRoom_id

1

students_id

"john"

["ClassRoom_id"], [2]

["ClassRoom_id", "students_id"], [2, "kate"]

ClassRoom_id

2

students_id

"kate"

["ClassRoom_id", "students_id"], [2, "mario"]

ClassRoom_id

2

students_id

"mario"

示例 52. 双向多对多关系
@Entity
public class AccountOwner {

    @Id
    private String id;

    private String SSN;

    @ManyToMany
    private Set<BankAccount> bankAccounts;

    // getters, setters ...
}

@Entity
public class BankAccount {

    @Id
    private String id;

    private String accountNumber;

    @ManyToMany( mappedBy = "bankAccounts" )
    private Set<AccountOwner> owners = new HashSet<AccountOwner>();

    // getters, setters ...
}

David 拥有 2 个账户:"012345" 和 "ZZZ-009"

表 52. AccountOwner 缓存内容
映射条目

["id"], ["David"]

id

"David"

社会安全号码

"0123456"

表 53. BankAccount 缓存内容
映射条目

["id"], ["account_1"]

id

"account_1"

帐号

"X2345000"

["id"], ["account_2"]

id

"account_2"

帐号

"ZZZ-009"

表 54. AccountOwner_BankAccount 缓存内容
行键 映射条目

["bankAccounts_id"], ["account_1"]

["bankAccounts_id", "owners_id"], ["account_1", "David"]

bankAccounts_id

"account_1"

owners_id

"David"

["bankAccounts_id"], ["account_2"]

["bankAccounts_id", "owners_id"], ["account_2", "David"]

bankAccounts_id

"account_2"

owners_id

"David"

["owners_id"], ["David"]

["owners_id", "banksAccounts_id"], ["David", "account_1"]

bankAccounts_id

"account_1"

owners_id

"David"

["owners_id", "banksAccounts_id"], ["David", "account_2"]

bankAccounts_id

"account_2"

owners_id

"David"

10. MongoDB

MongoDB 是一个用 C++ 编写的文档型数据库,其重点是易用性。文档的嵌套性质使其成为大多数对象表示的自然选择。

此实现基于 MongoDB Java 驱动程序。当前支持的版本是 3.6。

10.1. 为什么我应该将 Hibernate OGM 与 MongoDB 结合使用

您的项目中可能存在一些可以从 MongoDB 动态模式中获益的实体,但拥有模式可以获得更好的性能,因为数据存储可以利用模式信息来应用一些优化,否则无法实现这些优化。

JPA 已经拥有定义约束和索引的方法,通过 Hibernate OGM,您可以对关系型和非关系型需求使用相同的注解。

Hibernate OGM 无法使 MongoDB 具有事务性,但通过使用 JPA 事务界定机制,它可以对操作进行分组并将它们刷新到数据存储,以最大程度地减少请求数量。

将 Hibernate OGM 与 MongoDB 结合使用的另一个好处是,它还将使您能够开箱即用地使用 Hibernate Search。Hibernate Search 将 Lucene 的强大功能带入您的项目,使您能够运行快速类似 Google 的搜索。

这意味着您可以使用以下方法查询数据存储

Hibernate OGM 的主要目标之一是以“自然”的方式映射实体,这意味着如果您需要使用其他工具或偶尔想要运行本机查询,您的数据存储仍然可以访问。

10.2. 配置 MongoDB

配置 Hibernate OGM 以使用 MongoDb 很简单

  • 将 MongoDB 模块和驱动程序添加到类路径

  • 将 MongoDB URL 提供给 Hibernate OGM

10.2.1. 添加 MongoDB 依赖项

要通过 Maven 添加依赖项,请添加以下模块

<dependency>
    <groupId>org.hibernate.ogm</groupId>
    <artifactId>hibernate-ogm-mongodb</artifactId>
    <version>5.4.1.Final</version>
</dependency>

这将透明地拉取 MongoDB 驱动程序。

如果您没有使用依赖项管理工具,请将发行版中目录下的所有依赖项复制到

  • /lib/required

  • /lib/mongodb

  • 可选 - 根据您的容器 - 您可能需要 /lib/provided 中的一些 jar 包

MongoDB 不需要 Hibernate Search 来执行 JPQL 或 HQL 查询。

10.2.2. MongoDB 特定配置属性

要快速入门,请注意以下选项

  • hibernate.ogm.datastore.provider

  • hibernate.ogm.datastore.host

  • hibernate.ogm.datastore.database

这样您就可以运行它。以下属性可用于配置 MongoDB 支持

MongoDB 数据存储配置属性
hibernate.ogm.datastore.provider

要使用 MongoDB 作为数据存储提供者,必须将此属性设置为 mongodb

hibernate.ogm.option.configurator

程序化选项配置器的完全限定类名或实例(请参阅 程序化配置

hibernate.ogm.datastore.host

MongoDB 实例的主机名和端口。可选端口将连接到主机并用冒号分隔。当使用副本集时,您可以在逗号分隔的主机和端口列表中定义各种服务器。让我们看几个有效的示例

  • mongodb.example.com

  • mongodb.example.com:27018

  • 2001:db8::ff00:42:8329 (IPv6)

  • [2001:db8::ff00:42:8329]:27018 (带端口的 IPv6 需要将 IPv6 括在方括号中)

  • www.example.com, www2.example.com:123, 192.0.2.1, 192.0.2.2:123, 2001:db8::ff00:42:8329, [2001:db8::ff00:42:8329]:123 (副本集)

    默认值为 127.0.0.1:27017。如果未定义,则默认端口为 27017

hibernate.ogm.datastore.port

已弃用:使用 hibernate.ogm.datastore.host。MongoDB 实例使用的端口。定义多个主机时忽略。默认值为 27017

hibernate.ogm.datastore.database

要连接的数据库。此属性没有默认值。

hibernate.connection.resource

或者,您可以查找数据存储客户端。请参阅 与 WildFly NoSQL 集成

hibernate.ogm.datastore.create_database

如果设置为 true,则如果数据库不存在,将创建该数据库。此属性的默认值为 false。

hibernate.ogm.datastore.username

连接到 MongoDB 服务器时使用的用户名。此属性没有默认值。

hibernate.ogm.datastore.password

连接到 MongoDB 服务器时使用的密码。此属性没有默认值。如果未指定用户名,则忽略此属性。

hibernate.ogm.error_handler

完全限定类名、类对象或 ErrorHandler 的实例,用于在刷新期间收到错误通知(请参阅 对应用更改期间的错误采取行动

hibernate.ogm.mongodb.driver.*

定义应传递到 MongoDB 驱动程序的所有选项的前缀。有关可用选项,请参阅 MongoClientOptions.Builder 的 JavaDocs。可以设置所有 Stringintboolean 属性,例如 hibernate.ogm.mongodb.driver.serverSelectionTimeout

hibernate.ogm.mongodb.authentication_database

定义身份验证数据库的名称,默认值为 admin

hibernate.ogm.mongodb.authentication_mechanism

定义要使用的身份验证机制。可能的值是

  • BEST:与服务器握手以找到最佳身份验证机制。

  • SCRAM_SHA_1:SCRAM SHA 1 质询响应机制,如本 RFC 中所述。

  • MONGODB_CR:MongoDB 质询响应机制(自 MongoDB 3 起已弃用)

  • GSSAPI:GSSAPI 机制。请参阅 RFC

  • MONGODB_X509:MongoDB X.509

  • PLAIN:PLAIN 机制。请参阅 RFC

hibernate.ogm.datastore.document.association_storage

定义 OGM 在 MongoDB 中存储关联信息的方式。存在以下两种策略(org.hibernate.ogm.datastore.document.options.AssociationStorageType 枚举的值)

  • IN_ENTITY:在实体中存储关联信息

  • ASSOCIATION_DOCUMENT:在每个关联的专用文档中存储关联信息

IN_ENTITY 是默认选项,也是推荐选项,除非关联导航数据远大于文档的核心并且会导致性能下降。

hibernate.ogm.mongodb.association_document_storage

定义如何存储关联文档(仅在使用 ASSOCIATION_DOCUMENT 关联存储策略时适用)。可能的策略是(org.hibernate.ogm.datastore.mongodb.options.AssociationDocumentStorageType 枚举的值)

  • GLOBAL_COLLECTION (default): 在所有关联的唯一 MongoDB 集合中存储关联信息

  • COLLECTION_PER_ASSOCIATION 在每个关联的专用 MongoDB 集合中存储关联

hibernate.ogm.datastore.document.map_storage

定义 OGM 在 MongoDB 中存储映射类型关联内容的方式。存在以下两种策略(org.hibernate.ogm.datastore.document.options.MapStorageType 枚举的值)

  • BY_KEY: 类型为String的具有单个键列的映射类型关联将作为子文档存储,并按给定键进行组织;不适用于其他类型的键列,在这种情况下,将始终使用AS_LIST

  • AS_LIST: 映射类型关联将作为数组存储,该数组包含每个映射条目的子文档。所有键和值列都将包含在数组元素中

hibernate.ogm.mongodb.write_concern

定义在对 MongoDB 数据存储发出写入时要应用的写入关注设置。可能的设置是(WriteConcernType 枚举的值):ACKNOWLEDGEDUNACKNOWLEDGEDFSYNCEDJOURNALEDREPLICA_ACKNOWLEDGEDMAJORITYCUSTOM。当设置为 CUSTOM 时,必须指定自定义 WriteConcern 实现类型。

此选项不区分大小写,默认值为 ACKNOWLEDGED

hibernate.ogm.mongodb.write_concern_type

指定自定义 WriteConcern 实现类型(完全限定名称、类对象或实例)。这在预定义配置不足的情况下很有用,例如,如果您希望确保写入传播到特定数量的副本或给定的“标签集”。仅当 hibernate.ogm.mongodb.write_concern 设置为 CUSTOM 时才生效。

hibernate.ogm.mongodb.read_preference

指定在对 MongoDB 数据存储发出读取时要应用的 ReadPreference。可能的设置是(ReadPreferenceType 枚举的值):PRIMARYPRIMARY_PREFERREDSECONDARYSECONDARY_PREFERREDNEAREST。目前无法插入自定义读取偏好类型。如果您有兴趣使用此功能,请告诉我们。

有关更多信息,请参阅官方文档

在以编程方式引导会话工厂或实体管理器工厂时,您应使用通过 org.hibernate.ogm.datastore.mongodb.MongoDBProperties 访问的常量来指定上面列出的配置属性。

存储之间共享的公共属性在 OgmPropertiesMongoDBProperties 的超接口)上声明。

为了在存储之间实现最大程度的可移植性,请使用尽可能通用的接口。

10.2.3. 基于注释的配置

Hibernate OGM 允许通过 Java 注释配置特定于存储的选项。您可以根据放置该注释的位置,覆盖特定实体或甚至指定属性的全局配置。

在使用 MongoDB 后端时,您可以指定以下设置

  • 使用 @WriteConcern 注释为实体和关联设置写入关注

  • 使用 @ReadPreference 注释为实体和关联设置读取偏好

  • 使用 @AssociationStorage@AssociationDocumentStorage 注释为存储关联指定策略

  • 使用 @MapStorage 注释为存储映射类型关联的内容指定策略

请参阅 <<mongodb-associations> 了解有关存储关联的选项的更多信息。

以下是示例

示例 53. 使用注释配置关联存储策略
@Entity
@WriteConcern(WriteConcernType.JOURNALED)
@ReadPreference(ReadPreferenceType.PRIMARY_PREFERRED)
@AssociationStorage(AssociationStorageType.ASSOCIATION_DOCUMENT)
@AssociationDocumentStorage(AssociationDocumentStorageType.COLLECTION_PER_ASSOCIATION)
@MapStorage(MapStorageType.AS_LIST)
public class Zoo {

    @OneToMany
    private Set<Animal> animals;

    @OneToMany
    private Set<Person> employees;

    @OneToMany
    @AssociationStorage(AssociationStorageType.IN_ENTITY)
    private Set<Person> visitors;

    // getters, setters ...
}

实体级别的 @WriteConcern 注释表示所有写入都应使用 JOURNALED 设置完成。类似地,@ReadPreference 注释建议引擎尽可能从主节点读取该实体。类型级别的其他两个注释指定 Zoo 类的所有关联都应存储在单独的关联文档中,每个关联使用一个专用集合。此设置适用于 animalsemployees 关联。根据该特定属性的配置,只有 visitors 关联的元素将存储在相应 Zoo 实体的文档中,该配置优先于实体级配置。

10.2.4. 编程配置

除了注释机制之外,Hibernate OGM 还提供了一个编程 API 用于应用特定于存储的配置选项。如果您无法修改某些实体类型或不想向其中添加特定于存储的配置注释,这将非常有用。该 API 允许以类型安全的方式在全局、实体和属性级别设置选项。

在使用 MongoDB 时,您目前可以使用 API 配置以下选项

  • 写入关注

  • 读取偏好

  • 关联存储策略

  • 关联文档存储策略

  • 存储映射类型关联内容的策略

要通过 API 设置这些选项,您需要创建一个 OptionConfigurator 实现,如以下示例所示

示例 54. 选项配置器示例
public class MyOptionConfigurator extends OptionConfigurator {

    @Override
    public void configure(Configurable configurable) {
        configurable.configureOptionsFor( MongoDB.class )
            .writeConcern( WriteConcernType.REPLICA_ACKNOWLEDGED )
            .readPreference( ReadPreferenceType.NEAREST )
            .entity( Zoo.class )
                .associationStorage( AssociationStorageType.ASSOCIATION_DOCUMENT )
                .associationDocumentStorage( AssociationDocumentStorageType.COLLECTION_PER_ASSOCIATION )
                .mapStorage( MapStorageType.ASLIST )
                .property( "animals", ElementType.FIELD )
                    .associationStorage( AssociationStorageType.IN_ENTITY )
            .entity( Animal.class )
                .writeConcern( new RequiringReplicaCountOf( 3 ) )
                .associationStorage( AssociationStorageType.ASSOCIATION_DOCUMENT );
    }
}

调用 configureOptionsFor() 并传递特定于存储的标识符类型 MongoDB,提供了 API 的入口点。然后,遵循流畅 API 模式,您可以配置全局选项(writeConcern()readPreference())并导航到单个实体或属性以应用特定于这些实体或属性的选项(associationStorage() 等)。对 Animal 实体的 writeConcern() 调用显示了如何使用特定写入关注类型。这里 RequiringReplicaCountOfWriteConcern 的自定义实现,它确保写入在确认写入之前传播到给定数量的副本。

在属性级别给出的选项优先于实体级别选项。因此,例如,Zoo 类的 animals 关联将使用实体策略存储,而 Zoo 实体的所有其他关联将使用单独的关联文档存储。

类似地,实体级别选项优先于全局级别给出的选项。通过 API 指定的全局级别选项补充通过配置属性给出的设置。如果通过配置属性和 API 同时给出了设置,则后者优先。

请注意,对于给定级别(属性、实体、全局),通过注释设置的选项将被通过编程方式设置的相同选项覆盖。这使您能够在必要时以更灵活的方式更改设置。

要注册选项配置器,请使用 hibernate.ogm.option.configurator 属性指定其类名。在以编程方式引导会话工厂或实体管理器工厂时,您也可以传递 OptionConfigurator 实例或表示配置器类型的类对象。

10.3. 存储原则

Hibernate OGM 试图使与底层数据存储的映射尽可能自然,以便不使用 Hibernate OGM 的第三方应用程序仍然可以读取和更新相同的数据存储。我们特别努力在 MongoDB 模型上提供对象模型和 MongoDB 文档之间的各种经典映射。

简单地说,每个实体都作为 MongoDB 文档存储。该文档存储在以实体类型命名的 MongoDB 集合中。从一个实体到(一组)实体的每个关联的导航信息都存储在代表我们离开的实体的文档中。

10.3.1. 属性和内置类型

每个实体都由一个文档表示。每个属性或更准确地说是列都由该文档中的一个字段表示,字段名是列名。

Hibernate OGM 默认支持以下属性类型

  • java.lang.String

  { "text" : "Hello world!" }
  • java.lang.Character(或 char 原语);可选地,可以使用注解@Type(type = "true_false")@Type(type = "yes_no")@Type(type = "numeric_boolean") 将布尔属性分别映射到字符 'T'/'F'、'Y'/'N' 或整数值 0/1。

  { "delimiter" : "/" }
  • java.lang.Boolean(或布尔值基本类型)

  { "favorite" : true } # default mapping
  { "favorite" : "T" } # if @Type(type = "true_false") is given
  { "favorite" : "Y" } # if @Type(type = "yes_no") is given
  { "favorite" : 1 } # if @Type(type = "numeric_boolean") is given
  • java.lang.Byte(或 byte 原语)

  { "display_mask" : "70" }
  • java.lang.Byte[](或 byte[])

  { "pdfAsBytes" : BinData(0,"MTIzNDU=") }
  • java.lang.Short(或 short 原语)

  { "urlPort" : 80 }
  • java.lang.Integer(或 integer 原语)

  { "stockCount" : 12309 }
  • java.lang.Long(或 long 原语)

  { "userId" : NumberLong("-6718902786625749549") }
  • java.lang.Float(或 float 原语)

  { "visitRatio" : 10.39 }
  • java.lang.Double(或 double 原语)

  { "tax_percentage" : 12.34 }
  • java.math.BigDecimal

  { "site_weight" : "21.77" }
  • java.math.BigInteger

  { "site_weight" : "444" }
  • java.util.Calendar

  { "creation" : "2014/11/03 16:19:49:283 +0000" }
  • java.util.Date

  { "last_update" : ISODate("2014-11-03T16:19:49.283Z") }
  • java.util.UUID

  { "serialNumber" : "71f5713d-69c4-4b62-ad15-aed8ce8d10e0" }
  • java.util.URL

  { "url" : "http://www.hibernate.org/" }
  • org.bson.types.ObjectId

  { "object_id" : ObjectId("547d9b40e62048750f25ef77") }

Hibernate OGM 在 MongoDB 中不存储空值,将值设置为 null 等同于从数据库中对应对象中删除字段。

这可能对空值查询产生影响。

10.3.2. GridFS 支持

此功能处于实验阶段。

GridFS 是一种用于存储和检索超过 16 MB BSON 文档大小限制的文件的规范。

您可以在MongoDB 官方文档 中找到有关它的更多详细信息。

可以使用 org.hibernate.ogm.datastore.mongodb.type.GridFS 类型将值存储为 GridFS。

示例 55. 使用 GridFS 映射的字段
import org.hibernate.ogm.datastore.mongodb.type.GridFS;

@Entity
public class Photo {

    @Id
    String name;

    GridFS image;

    // ...
}
> db.Photo.find()
{ "_id" : "photo.jpg", "image" : ObjectId("5bce7202826ce81d7fe7ebe5") }

> db.Photo_bucket.files.find().pretty()
{
        "_id" : ObjectId("5bce7202826ce81d7fe7ebe5"),
        "filename" : "image_photo.jpg",
        "length" : NumberLong(30000000),
        "chunkSize" : 261120,
        "uploadDate" : ISODate("2018-10-22T20:57:37.047Z"),
        "md5" : "a08988fc1e3da0e80d2ba7b55599d1f7"
}

默认桶名称是类的名称,后缀为 _bucket,而文件名是字段名称与文档 ID 的组合。

可以使用 @GridFSBucket 注释选择不同的桶名称。

示例 56. 使用自定义桶名称使用 GridFS 映射的字段
import org.hibernate.ogm.datastore.mongodb.type.GridFS;
import org.hibernate.ogm.datastore.mongodb.options.GridFSBucket;

@Entity
public class Photo {

    @Id
    String name;

    @GridFSBucket("PhotoGallery")
    GridFS image;

    // ...
}
> db.Photo.find()
{ "_id" : "photo.jpg", "image" : ObjectId("5bce7202826ce81d7fe7ebe5") }

> db.PhotoGallery.files.find().pretty()
{
        "_id" : ObjectId("5bce7202826ce81d7fe7ebe5"),
        "filename" : "image_photo.jpg",
        "length" : NumberLong(30000000),
        "chunkSize" : 261120,
        "uploadDate" : ISODate("2018-10-22T20:57:37.047Z"),
        "md5" : "a08988fc1e3da0e80d2ba7b55599d1f7"
}

10.3.3. 实体

实体作为 MongoDB 文档存储,而不是作为 BLOB 存储:每个实体属性都将转换为文档字段。您可以使用 @Table@Column 注释分别重命名存储文档的集合和属性持久化的文档的字段。

示例 57. 实体的默认 JPA 映射
@Entity
public class News {

    @Id
    private String id;
    private String title;

    // getters, setters ...
}
// Stored in the Collection "News"
{
    "_id" : "1234-5678-0123-4567",
    "title": "On the merits of NoSQL",
}
示例 58. 使用 @Table 和 @Column 重命名字段和集合
@Entity
// Overrides the collection name
@Table(name = "News_Collection")
public class News {

    @Id
    private String id;

    // Overrides the field name
    @Column(name = "headline")
    private String title;

    // getters, setters ...
}
// Stored in the Collection "News"
{
    "_id" : "1234-5678-0123-4567",
    "headline": "On the merits of NoSQL",
}
标识符

Hibernate OGM 始终使用 MongoDB 文档的 _id 字段存储标识符,忽略实体中属性的名称。

这是件好事,因为 MongoDB 对 _id 属性有特殊处理和预期。

标识符类型可以是内置类型 之一,也可以是嵌入类表示的更复杂类型。当您使用内置类型时,标识符就像常规属性一样映射。当您使用嵌入类时,_id 表示包含嵌入类属性的嵌套文档。

示例 59. 将标识符定义为基本类型
@Entity
public class Bookmark {

    @Id
    private String id;

    private String title;

    // getters, setters ...
}
{
  "_id" : "bookmark_1"
  "title" : "Hibernate OGM documentation"
}
示例 60. 使用 @EmbeddedId 定义标识符
@Embeddable
public class NewsID implements Serializable {

    private String title;
    private String author;

    // getters, setters ...
}

@Entity
public class News {

    @EmbeddedId
    private NewsID newsId;
    private String content;

    // getters, setters ...
}

MongoDB 中的 News 集合作为 JSON

{
  "_id" : {
      "author" : "Guillaume",
      "title" : "How to use Hibernate OGM ?"
  },
  "content" : "Simple, just like ORM but with a NoSQL database"
}

通常,建议使用 MongoDB 的 object id 数据类型。这将促进与预期该通用 MongoDB id 类型的其他应用程序的集成。为此,您有两个选择

  • 将您的 id 属性定义为 org.bson.types.ObjectId

  • 将您的 id 属性定义为 String 并使用 @Type(type="objectid") 注释它

在这两种情况下,id 都将作为本机 ObjectId 存储在数据存储中。

示例 61. 将 id 定义为 ObjectId
@Entity
public class News {

    @Id
    private ObjectId id;

    private String title;

    // getters, setters ...
}
示例 62. 将 String 类型的 id 定义为 ObjectId
@Entity
public class News {

    @Id
    @Type(type = "objectid")
    private String id;

    private String title;

    // getters, setters ...
}
标识符生成策略

您可以自己分配 id 值,也可以让 Hibernate OGM 使用 @GeneratedValue 注释生成值。

有 4 种不同的策略

  1. IDENTITY(建议)

  2. TABLE

  3. SEQUENCE

  4. AUTO

1) IDENTITY 生成策略

首选策略,Hibernate OGM 将在插入时创建标识符。要应用此策略,id 必须是以下之一

  • 使用 @Type(type="objectid") 注释

  • org.bson.types.ObjectId

如以下示例所示

示例 63. 将 String 类型的 id 定义为 ObjectId
@Entity
public class News {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Type(type = "objectid")
    private String id;

    private String title;

    // getters, setters ...
}
{
    "_id" : ObjectId("5425448830048b67064d40b1"),
    "title" : "Exciting News"
}
示例 64. 将 id 定义为 ObjectId
@Entity
public class News {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private ObjectId id;

    private String title;

    // getters, setters ...
}
{
    "_id" : ObjectId("5425448830048b67064d40b1"),
    "title" : "Exciting News"
}

2) TABLE 生成策略

示例 65. 使用默认值的 TABLE id 生成策略
@Entity
public class GuitarPlayer {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE)
    private Long id;

    private String name;

    // getters, setters ...
}

GuitarPlayer 集合

{
    "_id" : NumberLong(1),
    "name" : "Buck Cherry"
}

hibernate_sequences 集合

{
    "_id" : "GuitarPlayer",
    "next_val" : 101
}
示例 66. 使用自定义表的 TABLE id 生成策略
@Entity
public class GuitarPlayer {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "guitarGen")
    @TableGenerator(
        name = "guitarGen",
        table = "GuitarPlayerSequence",
        pkColumnValue = "guitarPlayer",
        valueColumnName = "nextGuitarPlayerId"
    )
    private long id;

    // getters, setters ...
}

GuitarPlayer 集合

{
    "_id" : NumberLong(1),
    "name" : "Buck Cherry"
}

GuitarPlayerSequence 集合

{
    "_id" : "guitarPlayer",
    "nextGuitarPlayerId" : 2
}

3) SEQUENCE 生成策略

示例 67. 使用默认值的 SEQUENCE id 生成策略
@Entity
public class Song {

  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE)
  private Long id;

  private String title;

  // getters, setters ...
}

Song 集合

{
  "_id" : NumberLong(2),
  "title" : "Flower Duet"
}

hibernate_sequences 集合

{ "_id" : "song_sequence_name", "next_val" : 21 }
示例 68. 使用自定义值的 SEQUENCE id 生成策略
@Entity
public class Song {

  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "songSequenceGenerator")
  @SequenceGenerator(
      name = "songSequenceGenerator",
      sequenceName = "song_seq",
      initialValue = 2,
      allocationSize = 20
  )
  private Long id;

  private String title;

  // getters, setters ...
}

Song 集合

{
  "_id" : NumberLong(2),
  "title" : "Flower Duet"
}

hibernate_sequences 集合

{ "_id" : "song_seq", "next_val" : 42 }

4) AUTO 生成策略

在使用 GenerationType.AUTO 策略时必须小心。当属性 hibernate.id.new_generator_mappings 设置为 false(默认值)时,它将映射到 IDENTITY 策略。如前所述,这要求您的 id 为 ObjectId@Type(type = "objectid") String 类型。如果 hibernate.id.new_generator_mappings 设置为 true,则 AUTO 将映射到 TABLE 策略。这要求您的 id 为数字类型。

我们建议不要使用 AUTO,而是使用明确的策略之一(IDENTITYTABLE)以避免潜在的错误配置。

有关更多详细信息,您可以查看问题OGM-663

如果属性hibernate.id.new_generator_mappings设置为false,则AUTO将表现为IDENTITY策略。

如果属性hibernate.id.new_generator_mappings设置为true,则AUTO将表现为SEQUENCE策略。

示例 69. 使用默认值进行 AUTO 标识生成策略
@Entity
public class DistributedRevisionControl {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;

  private String name;

  // getters, setters ...
}

DistributedRevisionControl 集合

{ "_id" : NumberLong(1), "name" : "Git" }

hibernate_sequences 集合

{ "_id" : "hibernate_sequence", "next_val" : 2 }
示例 70. 使用hibernate.id.new_generator_mappings设置为 false 以及 ObjectId 的 AUTO 标识生成策略
@Entity
public class Comedian {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private ObjectId id;

  private String name;

  // getters, setters ...
}

Comedian 集合

{ "_id" : ObjectId("5458b11693f4add0f90519c5"), "name" : "Louis C.K." }
示例 71. 带有 @EmbeddedId 的实体
@Entity
public class News {

    @EmbeddedId
    private NewsID newsId;

    // getters, setters ...
}

@Embeddable
public class NewsID implements Serializable {

    private String title;
    private String author;

    // getters, setters ...
}

在 MongoDB 中以 JSON 形式呈现

{
    "_id" :{
        "title": "How does Hibernate OGM MongoDB work?",
        "author": "Guillaume"
    }
}
嵌入式对象和集合

Hibernate OGM 将使用@Embedded@ElementCollection注释的元素存储为拥有实体的嵌套文档。

示例 72. 嵌入式对象
@Entity
public class News {

    @Id
    private String id;
    private String title;

    @Embedded
    private NewsPaper paper;

    // getters, setters ...
}

@Embeddable
public class NewsPaper {

    private String name;
    private String owner;

    // getters, setters ...
}
{
    "_id" : "1234-5678-0123-4567",
    "title": "On the merits of NoSQL",
    "paper": {
        "name": "NoSQL journal of prophecies",
        "owner": "Delphy"
    }
}
示例 73. 带有原始类型的 @ElementCollection
@Entity
public class AccountWithPhone {

    @Id
    private String id;

    @ElementCollection
    private List<String> mobileNumbers;

    // getters, setters ...
}

AccountWithPhone 集合

{
    "_id" : "john_account",
    "mobileNumbers" : [ "+1-222-555-0222", "+1-202-555-0333" ]
}
示例 74. 带有单个属性的 @ElementCollection
@Entity
public class GrandMother {

    @Id
    private String id;

    @ElementCollection
    private List<GrandChild> grandChildren = new ArrayList<GrandChild>();

    // getters, setters ...
}

@Embeddable
public class GrandChild {

    private String name;

    // getters, setters ...
}
{
    "_id" : "df153180-c6b3-4a4c-a7da-d5de47cf6f00",
    "grandChildren" : [ "Luke", "Leia" ]
}

GrandChild只有一个属性name,这意味着 Hibernate OGM 不需要存储属性的名称。

如果嵌套文档有两个或多个字段(如以下示例),Hibernate OGM 将存储字段的名称。

示例 75. 带有 @OrderColumn 的 @ElementCollection
@Entity
public class GrandMother {

    @Id
    private String id;

    @ElementCollection
    @OrderColumn( name = "birth_order" )
    private List<GrandChild> grandChildren = new ArrayList<GrandChild>();

    // getters, setters ...
}

@Embeddable
public class GrandChild {

    private String name;

    // getters, setters ...
}
{
    "_id" : "e3e1ed4e-c685-4c3f-9a67-a5aeec6ff3ba",
    "grandChildren" :
        [
            {
                "name" : "Luke",
                "birth_order" : 0
            },
            {
                "name" : "Leia",
                "birthorder" : 1
            }
        ]
}
示例 76. 带有 @Embeddable 映射的 @ElementCollection
@Entity
public class ForumUser {

    @Id
    private String name;

    @ElementCollection
    private Map<String, JiraIssue> issues = new HashMap<>();

    // getters, setters ...
}

@Embeddable
public class JiraIssue {

    private Integer number;
    private String project;

    // getters, setters ...
}
{
        "_id" : "Jane Doe",
        "issues" : {
                "issueWithNull" : {
                },
                "issue2" : {
                    "number" : 2000,
                    "project" : "OGM"
                },
                "issue1" : {
                    "number" : 1253,
                    "project" : "HSEARCH"
                }
        }
}

您可以覆盖用于嵌入式对象属性的列名。但您需要知道默认列名是嵌入属性、.(点)和嵌入属性的串联(对于嵌入式对象的多个级别,递归执行)。

MongoDB 数据存储将点专门视为将它们转换为嵌套文档。如果您想覆盖一个列名并保留嵌套结构,请不要忘记点。

这有点抽象,所以让我们举个例子。

@Entity
class Order {
    @Id String number;
    User user;
    Address shipping;
    @AttributeOverrides({
        @AttributeOverride(name="name", column=@Column(name="delivery.provider"),
        @AttributeOverride(name="expectedDelaysInDays", column=@Column(name="delivery.delays")
    })
    DeliveryProvider deliveryProvider;
    CreditCardType cardType;
}

// default columns
@Embedded
class User {
    String firstname;
    String lastname;
}

// override one column
@Embeddable
public Address {
    String street;
    @Column(name="shipping.dest_city")
    String city;
}

// both columns overridden from the embedding side
@Embeddable
public DeliveryProvider {
    String name;
    Integer expectedDelaysInDays;
}

// do not use dots in the overriding
// and mix levels (bad form)
@Embedded
class CreditCardType {
    String merchant;
    @Column(name="network")
    String network;
}
{
    "_id": "123RF33",
    "user": {
        "firstname": "Emmanuel",
        "lastname": "Bernard"
    },
    "shipping": {
        "street": "1 av des Champs Elysées",
        "dest_city": "Paris"
    },
    "delivery": {
        "provider": "Santa Claus Inc.",
        "delays": "1"
    }
    "network": "VISA",
    "cardType: {
        "merchant": "Amazon"
    }
}

如果您在不同位置共享相同的嵌入式对象,您可以使用 JPA 的@AttributeOverride从嵌入方面覆盖列。这是我们示例中DeliveryProvider的情况。

如果您省略了一个列中的点,则该列将不会成为嵌套文档的一部分。这由CreditCardType演示。我们建议您不要这样做。就像穿越河流一样,这是一个不好的形式。这种方法在将来可能不受支持。

10.3.4. 关联

Hibernate OGM MongoDB 提出三种策略来存储关联的导航信息。三种可能的策略是

要在这三种策略之间切换,请使用以下三种方法来设置选项

  • 使用@AssocationStorage@AssociationDocumentStorage注释对您的实体进行注释(参见基于注释的配置),

  • 使用 API 进行程序化配置(参见程序化配置

  • 或通过hibernate.ogm.datastore.document.association_storagehibernate.ogm.mongodb.association_document_storage配置属性指定默认策略。

在实体策略中

在此策略中,Hibernate OGM 将关联实体的标识存储到实体文档本身中。该字段存储对一关联的标识值,以及对多关联的标识值数组。嵌入式标识将由嵌套文档表示。对于索引集合(即ListMap),索引将与标识一起存储。

使用此策略时,将忽略注释@JoinTable,因为没有为关联创建集合。

您可以使用@JoinColumn更改存储外键的字段的名称(例如,参见使用 @JoinColumn 的单向一对一)。

对一关联
示例 77. 单向一对一
@Entity
public class Vehicle {

    @Id
    private String id;
    private String brand;

    // getters, setters ...
}


@Entity
public class Wheel {

    @Id
    private String id;
    private double diameter;

    @OneToOne
    private Vehicle vehicle;

    // getters, setters ...
}
{
  "_id" : "V_01",
  "brand" : "Mercedes"
}

Wheel 集合以 JSON 形式显示在 MongoDB 中

{
  "_id" : "W001",
  "diameter" : 0,
  "vehicle_id" : "V_01"
}
示例 78. 使用 @JoinColumn 的单向一对一
@Entity
public class Vehicle {

    @Id
    private String id;
    private String brand;

    // getters, setters ...
}


@Entity
public class Wheel {

    @Id
    private String id;
    private double diameter;

    @OneToOne
    @JoinColumn( name = "part_of" )
    private Vehicle vehicle;

    // getters, setters ...
}
{
  "_id" : "V_01",
  "brand" : "Mercedes"
}

Wheel 集合以 JSON 形式显示在 MongoDB 中

{
  "_id" : "W001",
  "diameter" : 0,
  "part_of" : "V_01"
}

在真正的“一对一”关联中,可以在这两个实体之间共享相同的标识,因此不需要外键。您可以在以下示例中看到如何映射这种类型的关联

示例 79. 使用 @MapsId 和 @PrimaryKeyJoinColumn 的单向一对一
@Entity
public class Vehicle {

    @Id
    private String id;
    private String brand;

    // getters, setters ...
}

@Entity
public class Wheel {

    @Id
    private String id;
    private double diameter;

    @OneToOne
    @PrimaryKeyJoinColumn
    @MapsId
    private Vehicle vehicle;

    // getters, setters ...
}

Vehicle 集合以 JSON 形式显示在 MongoDB 中

{
  "_id" : "V_01",
  "brand" : "Mercedes"
}

Wheel 集合以 JSON 形式显示在 MongoDB 中

{
  "_id" : "V_01",
  "diameter" : 0,
}
示例 80. 双向一对一
@Entity
public class Husband {

    @Id
    private String id;
    private String name;

    @OneToOne
    private Wife wife;

    // getters, setters ...
}

@Entity
public class Wife {

    @Id
    private String id;
    private String name;

    @OneToOne
    private Husband husband;

    // getters, setters ...
}

Husband 集合以 JSON 形式显示在 MongoDB 中

{
  "_id" : "alex",
  "name" : "Alex",
  "wife" : "bea"
}

Wife 集合以 JSON 形式显示在 MongoDB 中

{
  "_id" : "bea",
  "name" : "Bea",
  "husband" : "alex"
}
示例 81. 双向一对一,一对多(映射到同一个实体)
@Entity
public class Person {

    @Id
    private String name;
    @OneToOne
    private Person spouse;
    @OneToMany
    private List<Person> children;

    // getters, setters ...
}

Person 集合以 JSON 形式显示在 MongoDB 中

{ "_id" : "Nick", "spouse_name" : "Kate", "children" : [ "Louis", "Tom", "Alex" ] }
{ "_id" : "Tom", "spouse_name" : "Mary" }
{ "_id" : "Louis" }
{ "_id" : "Kate", "spouse_name" : "Nick", "children" : [ "Louis", "Tom", "Alex" ] }
{ "_id" : "Mary", "spouse_name" : "Tom" }
{ "_id" : "Alex" }
示例 82. 单向多对一
@Entity
public class JavaUserGroup {

    @Id
    private String jugId;
    private String name;

    // getters, setters ...
}

@Entity
public class Member {

    @Id
    private String id;
    private String name;

    @ManyToOne
    private JavaUserGroup memberOf;

    // getters, setters ...
}

JavaUserGroup 集合以 JSON 形式显示在 MongoDB 中

{
    "_id" : "summer_camp",
    "name" : "JUG Summer Camp"
}

Member 集合以 JSON 形式显示在 MongoDB 中

{
    "_id" : "jerome",
    "name" : "Jerome"
    "memberOf_jugId" : "summer_camp"
}
{
    "_id" : "emmanuel",
    "name" : "Emmanuel Bernard"
    "memberOf_jugId" : "summer_camp"
}
示例 83. 双向多对一
@Entity
public class SalesForce {

    @Id
    private String id;
    private String corporation;

    @OneToMany(mappedBy = "salesForce")
    private Set<SalesGuy> salesGuys = new HashSet<SalesGuy>();

    // getters, setters ...
}

@Entity
public class SalesGuy {
    private String id;
    private String name;

    @ManyToOne
    private SalesForce salesForce;

    // getters, setters ...
}

SalesForce 集合

{
    "_id" : "red_hat",
    "corporation" : "Red Hat",
    "salesGuys" : [ "eric", "simon" ]
}

SalesGuy 集合

{
    "_id" : "eric",
    "name" : "Eric"
    "salesForce_id" : "red_hat",
}
{
    "_id" : "simon",
    "name" : "Simon",
    "salesForce_id" : "red_hat"
}
示例 84. 具有嵌入式标识的实体之间的双向多对一
@Entity
public class Game {

    @EmbeddedId
    private GameId id;

    private String name;

    @ManyToOne
    private Court playedOn;

    // getters, setters ...
}


public class GameId implements Serializable {

    private String category;

    @Column(name = "id.gameSequenceNo")
    private int sequenceNo;

    // getters, setters ...
    // equals / hashCode
}

@Entity
public class Court {

    @EmbeddedId
    private CourtId id;

    private String name;

    @OneToMany(mappedBy = "playedOn")
    private Set<Game> games = new HashSet<Game>();

    // getters, setters ...
}

public class CourtId implements Serializable {

    private String countryCode;
    private int sequenceNo;

    // getters, setters ...
    // equals / hashCode
}
Court 集合
{
    "_id" : {
        "countryCode" : "DE",
        "sequenceNo" : 123
    },
    "name" : "Hamburg Court",
    "games" : [
        { "gameSequenceNo" : 457, "category" : "primary" },
        { "gameSequenceNo" : 456, "category" : "primary" }
    ]
}
Game 集合
{
    "_id" : {
        "category" : "primary",
        "gameSequenceNo" : 456
    },
    "name" : "The game",
    "playedOn_id" : {
        "countryCode" : "DE",
        "sequenceNo" : 123
    }
}
{
    "_id" : {
        "category" : "primary",
        "gameSequenceNo" : 457
    },
    "name" : "The other game",
    "playedOn_id" : {
        "countryCode" : "DE",
        "sequenceNo" : 123
    }
}

这里我们看到嵌入式标识被表示为嵌套文档,并被关联直接引用。

对多关联
示例 85. 单向一对多
@Entity
public class Basket {

    @Id
    private String id;

    private String owner;

    @OneToMany
    private List<Product> products = new ArrayList<Product>();

    // getters, setters ...
}

@Entity
public class Product {

    @Id
    private String name;

    private String description;

    // getters, setters ...
}

Basket 集合

{
  "_id" : "davide_basket",
  "owner" : "Davide",
  "products" : [ "Beer", "Pretzel" ]
}

Product 集合

{
  "_id" : "Pretzel",
  "description" : "Glutino Pretzel Sticks"
}
{
  "_id" : "Beer",
  "description" : "Tactical nuclear penguin"
}
示例 86. 使用 @OrderColumn 的单向一对多
@Entity
public class Basket {

    @Id
    private String id;

    private String owner;

    @OneToMany
    private List<Product> products = new ArrayList<Product>();

    // getters, setters ...
}

@Entity
public class Product {

    @Id
    private String name;

    private String description;

    // getters, setters ...
}

Basket 集合

{
  "_id" : "davide_basket",
  "owner" : "Davide",
  "products" : [
    {
      "products_name" : "Pretzel",
      "products_ORDER" : 1
    },
    {
      "products_name" : "Beer",
      "products_ORDER" : 0
    }
  ]
}

Product 集合

{
  "_id" : "Pretzel",
  "description" : "Glutino Pretzel Sticks"
}
{
  "_id" : "Beer",
  "description" : "Tactical nuclear penguin"
}

可以使用映射来表示关联,在这种情况下,Hibernate OGM 将存储映射的键和关联的标识。

示例 87. 使用默认值的单向一对多映射
@Entity
public class User {

    @Id
    private String id;

    @OneToMany
    private Map<String, Address> addresses = new HashMap<String, Address>();

    // getters, setters ...
}

@Entity
public class Address {

    @Id
    private String id;
    private String city;

    // getters, setters ...
}

User 集合以 JSON 形式显示在 MongoDB 中

{
  "_id" : "user_001",
  "addresses" : [
    {
      "work" : "address_001",
      "home" : "address_002"
    }
  ]
}

Address 集合以 JSON 形式显示在 MongoDB 中

{ "_id" : "address_001", "city" : "Rome" }
{ "_id" : "address_002", "city" : "Paris" }

如果映射值不能由单个字段表示(例如,当引用具有复合标识的类型或使用嵌入式类型作为映射值类型时),将存储一个包含所有必需字段的子文档作为值。

如果映射键不是String类型,或者它由多个列组成(复合映射键),则示例中显示的优化结构无法使用,因为 MongoDB 只允许使用字符串作为字段名。在这种情况下,关联将由子文档列表表示,其中还包含映射键列。您可以使用@MapKeyColumn重命名包含映射键的字段,否则它将默认为“<%COLLECTION_ROLE%>_KEY”,例如“addresses_KEY”。

示例 88. 使用 @MapKeyColumn 的单向一对多映射
@Entity
public class User {

    @Id
    private String id;

    @OneToMany
    @MapKeyColumn(name = "addressType")
    private Map<Long, Address> addresses = new HashMap<Long, Address>();

    // getters, setters ...
}

@Entity
public class Address {

    @Id
    private String id;
    private String city;

    // getters, setters ...
}

User 集合以 JSON 形式显示在 MongoDB 中

{
  "_id" : "user_001",
  "addresses" : [
    {
      "addressType" : 1,
      "addresses_id" : "address_001"
    },
    {
      "addressType" : 2,
      "addresses_id" : "address_002"
    }
  ]
}

Address 集合以 JSON 形式显示在 MongoDB 中

{ "_id" : "address_001", "city" : "Rome" }
{ "_id" : "address_002", "city" : "Paris" }

如果您想即使对于具有单个键列的String类型映射(例如,当读取由早期版本的 Hibernate OGM 持续保存的数据时),也要强制使用列表样式的表示,您可以通过将选项hibernate.ogm.datastore.document.map_storage设置为值AS_LIST来实现。

示例 89. 使用实体策略的单向多对多
@Entity
public class Student {

    @Id
    private String id;
    private String name;

    // getters, setters ...
}

@Entity
public class ClassRoom {

    @Id
    private long id;
    private String lesson;

    @ManyToMany
    private List<Student> students = new ArrayList<Student>();

    // getters, setters ...
}

Student 集合

{
  "_id" : "john",
  "name" :"John Doe" }
{
  "_id" : "mario",
  "name" : "Mario Rossi"
}
{
  "_id" : "kate",
  "name" : "Kate Doe"
}

ClassRoom 集合

{
  "_id" : NumberLong(1),
  "lesson" : "Math"
  "students" : [
     "mario",
     "john"
  ]
}
{
  "_id" : NumberLong(2),
  "lesson" : "English"
  "students" : [
     "mario",
     "kate"
  ]
}
示例 90. 双向多对多
@Entity
public class AccountOwner {

    @Id
    private String id;

    private String SSN;

    @ManyToMany
    private Set<BankAccount> bankAccounts;

    // getters, setters ...
}

@Entity
public class BankAccount {

    @Id
    private String id;

    private String accountNumber;

    @ManyToMany( mappedBy = "bankAccounts" )
    private Set<AccountOwner> owners = new HashSet<AccountOwner>();

    // getters, setters ...
}

AccountOwner 集合

{
    "_id" : "owner_1",
    "SSN" : "0123456"
    "bankAccounts" : [ "account_1" ]
}

BankAccount 集合

{
    "_id" : "account_1",
    "accountNumber" : "X2345000"
    "owners" : [ "owner_1", "owner2222" ]
}
示例 91. 带有嵌入式标识的有序列表
@Entity
public class Race {
    @EmbeddedId
    private RaceId raceId;

    @OrderColumn(name = "ranking")
    @OneToMany @JoinTable(name = "Race_Runners")
    private List<Runner> runnersByArrival = new ArrayList<Runner>();

    // getters, setters ...
}

public class RaceId implements Serializable {
    private int federationSequence;
    private int federationDepartment;

    // getters, setters, equals, hashCode
}

@Entity
public class Runner {
    @EmbeddedId
    private RunnerId runnerId;
    private int age;

    // getters, setters ...
}

public class RunnerId implements Serializable {
    private String firstname;
    private String lastname;

    // getters, setters, equals, hashCode
}
Race 集合
{
    "_id": {
        "federationDepartment": 75,
        "federationSequence": 23
    },
    "runnersByArrival": [{
        "firstname": "Pere",
        "lastname": "Noel",
        "ranking": 1
    }, {
        "firstname": "Emmanuel",
        "lastname": "Bernard",
        "ranking": 0
    }]
}
Runner 集合
{
    "_id": {
        "firstname": "Pere",
        "lastname": "Noel"
    },
    "age": 105
} {
    "_id": {
        "firstname": "Emmanuel",
        "lastname": "Bernard"
    },
    "age": 37
}
每个关联一个集合策略

在此策略中,Hibernate OGM 为每个关联创建一个 MongoDB 集合,其中将存储该特定关联的所有导航信息。

这是最接近关系模型的策略。如果实体 A 与 B 和 C 相关,则将创建 2 个集合。此集合的名称是由关联表和associations_串联而成。

例如,如果BankAccountOwner相关,则用于存储的集合将命名为associations_Owner_BankAccount。您可以重命名此前缀,以便可以快速区分实体集合中的关联集合。您还可以决定使用@JoinTable重命名表示关联的集合(参见示例

关联集合的每个文档都具有以下结构

  • _id包含关系所有者的标识

  • rows包含所有相关实体的标识

首选方法是使用在实体策略中,但这可以缓解文档过大的问题。

示例 92. 单向关系
{
    "_id" : { "owners_id" : "owner0001" },
    "rows" : [
        "accountABC",
        "accountXYZ"
    ]
}
示例 93. 双向关系
{
    "_id" : { "owners_id" : "owner0001" },
    "rows" : [ "accountABC", "accountXYZ" ]
}
{
    "_id" : { "bankAccounts_id" : "accountXYZ" },
    "rows" : [ "owner0001" ]
}

此策略不会影响 *- 对一关联或嵌入式集合。

示例 94. 使用每个策略一个集合的单向一对多
@Entity
public class Basket {

    @Id
    private String id;

    private String owner;

    @OneToMany
    private List<Product> products = new ArrayList<Product>();

    // getters, setters ...
}

@Entity
public class Product {

    @Id
    private String name;

    private String description;

    // getters, setters ...
}

Basket 集合

{
  "_id" : "davide_basket",
  "owner" : "Davide"
}

Product 集合

{
  "_id" : "Pretzel",
  "description" : "Glutino Pretzel Sticks"
}
{
  "_id" : "Beer",
  "description" : "Tactical nuclear penguin"
}

associations_Basket_Product 集合

{
  "_id" : { "Basket_id" : "davide_basket" },
  "rows" : [ "Beer", "Pretzel" ]
}

可以使用 @OrderColumn 保留列表中元素的顺序。Hibernate OGM 将存储顺序,并在包含关联的文档中添加一个额外的字段。

示例 95. 使用每个策略一个集合以及 @OrderColumn 的单向一对多
@Entity
public class Basket {

    @Id
    private String id;

    private String owner;

    @OneToMany
    @OrderColumn
    private List<Product> products = new ArrayList<Product>();

    // getters, setters ...
}

@Entity
public class Product {

    @Id
    private String name;

    private String description;

    // getters, setters ...
}

Basket 集合

{
  "_id" : "davide_basket",
  "owner" : "Davide"
}

Product 集合

{
  "_id" : "Pretzel",
  "description" : "Glutino Pretzel Sticks"
}
{
  "_id" : "Beer",
  "description" : "Tactical nuclear penguin"
}

associations_Basket_Product 集合

{
  "_id" : { "Basket_id" : "davide_basket" },
  "rows" : [
    {
      "products_name" : "Pretzel",
      "products_ORDER" : 1
    },
    {
      "products_name" : "Beer",
      "products_ORDER" : 0
    }
  ]
}
示例 96. 使用每个关联一个集合策略的单向多对多
@Entity
public class Student {

    @Id
    private String id;
    private String name;

    // getters, setters ...
}

@Entity
public class ClassRoom {

    @Id
    private long id;
    private String lesson;

    @ManyToMany
    private List<Student> students = new ArrayList<Student>();

    // getters, setters ...
}

Student 集合

{
  "_id" : "john",
  "name" : "John Doe"
}
{
  "_id" : "mario",
  "name" : "Mario Rossi"
}
{
  "_id" : "kate",
  "name" : "Kate Doe"
}

ClassRoom 集合

{
  "_id" : NumberLong(1),
  "lesson" : "Math"
}
{
  "_id" : NumberLong(2),
  "lesson" : "English"
}

associations_ClassRoom_Student

{
  "_id" : {
    "ClassRoom_id" : NumberLong(1),
  },
  "rows" : [ "john", "mario" ]
}
{
  "_id" : {
    "ClassRoom_id" : NumberLong(2),
  },
  "rows" : [ "mario", "kate" ]
}
示例 97. 使用每个关联一个集合策略的双向多对多
@Entity
public class AccountOwner {

    @Id
    private String id;

    private String SSN;

    @ManyToMany
    private Set<BankAccount> bankAccounts;

    // getters, setters ...
}

@Entity
public class BankAccount {

    @Id
    private String id;

    private String accountNumber;

    @ManyToMany(mappedBy = "bankAccounts")
    private Set<AccountOwner> owners = new HashSet<AccountOwner>();

    // getters, setters ...
}

AccountOwner 集合

{
  "_id" : "owner_1",
  "SSN" : "0123456"
}

BankAccount 集合

{
  "_id" : "account_1",
  "accountNumber" : "X2345000"
}

associations_AccountOwner_BankAccount 集合

{
  "_id" : {
    "bankAccounts_id" : "account_1"
  },
  "rows" : [ "owner_1" ]
}
{
  "_id" : {
    "owners_id" : "owner_1"
  },
  "rows" : [ "account_1" ]
}

您可以使用@JoinTable注释更改包含关联的集合的名称。在以下示例中,包含关联的集合的名称为OwnerBankAccounts(而不是默认的associations_AccountOwner_BankAccount

示例 98. 使用每个关联一个集合策略以及 @JoinTable 的双向多对多
@Entity
public class AccountOwner {

    @Id
    private String id;

    private String SSN;

    @ManyToMany
    @JoinTable( name = "OwnerBankAccounts" )
    private Set<BankAccount> bankAccounts;

    // getters, setters ...
}

@Entity
public class BankAccount {

    @Id
    private String id;

    private String accountNumber;

    @ManyToMany(mappedBy = "bankAccounts")
    private Set<AccountOwner> owners = new HashSet<AccountOwner>();

    // getters, setters ...
}

AccountOwner 集合

{
  "_id" : "owner_1",
  "SSN" : "0123456"
}

BankAccount 集合

{
  "_id" : "account_1",
  "accountNumber" : "X2345000"
}

OwnerBankAccount

{
  "_id" : {
    "bankAccounts_id" : "account_1"
  },
  "rows" : [ "owner_1" ]
}
{
  "_id" : {
    "owners_id" : "owner_1"
  },
  "rows" : [ "account_1" ]
}
全局集合策略

使用此策略,Hibernate OGM 会创建一个名为Associations的单个集合,其中将存储所有关联的所有导航信息。此集合的每个文档都包含两部分。第一部分是_id字段,其中包含关联所有者的标识信息和关联表的名称。第二部分是rows字段,其中存储(到嵌入式集合中)当前实例相关的所有标识。

此策略不会影响 *- 对一关联或嵌入式集合。

通常,您不应该使用此策略,除非将关联信息嵌入证明对您的文档来说太大了,并且您希望将它们分开。

示例 99. 包含单向关联的 Associations 集合
{
    "_id": {
        "owners_id": "owner0001",
        "table": "AccountOwner_BankAccount"
    },
    "rows": [ "accountABC", "accountXYZ" ]
}

对于双向关系,将创建另一个文档,其中标识将反转。不用担心,Hibernate OGM 会负责同步它们

示例 100. 包含双向关联的 Associations 集合
{
    "_id": {
        "owners_id": "owner0001",
        "table": "AccountOwner_BankAccount"
    },
    "rows": [ "accountABC", "accountXYZ" ]
}
{
    "_id": {
        "bankAccounts_id": "accountXYZ",
        "table": "AccountOwner_BankAccount"
    },
    "rows": [ "owner0001" ]
}
示例 101. 使用全局集合策略的单向一对多
@Entity
public class Basket {

    @Id
    private String id;

    private String owner;

    @OneToMany
    private List<Product> products = new ArrayList<Product>();

    // getters, setters ...
}

@Entity
public class Product {

    @Id
    private String name;

    private String description;

    // getters, setters ...
}

Basket 集合

{
  "_id" : "davide_basket",
  "owner" : "Davide"
}

Product 集合

{
  "_id" : "Pretzel",
  "description" : "Glutino Pretzel Sticks"
}
{
  "_id" : "Beer",
  "description" : "Tactical nuclear penguin"
}

Associations 集合

{
  "_id" : {
    "Basket_id" : "davide_basket",
    "table" : "Basket_Product"
  },
  "rows" : [
    {
      "products_name" : "Pretzel",
      "products_ORDER" : 1
    },
    {
      "products_name" : "Beer",
    "products_ORDER" : 0
    }
  ]
}
示例 102. 使用全局集合策略以及@JoinTable的单向一对多
@Entity
public class Basket {

    @Id
    private String id;

    private String owner;

    @OneToMany
    // It will change the value stored in the field table in the Associations collection
    @JoinTable( name = "BasketContent" )
    private List<Product> products = new ArrayList<Product>();

    // getters, setters ...
}

@Entity
public class Product {

    @Id
    private String name;

    private String description;

    // getters, setters ...
}

Basket 集合

{
  "_id" : "davide_basket",
  "owner" : "Davide"
}

Product 集合

{
  "_id" : "Pretzel",
  "description" : "Glutino Pretzel Sticks"
}
{
  "_id" : "Beer",
  "description" : "Tactical nuclear penguin"
}

Associations 集合

{
  "_id" : {
    "Basket_id" : "davide_basket",
    "table" : "BasketContent"
  },
  "rows" : [ "Beer", "Pretzel" ]
}
示例 103. 使用全局集合策略的单向多对多
@Entity
public class Student {

    @Id
    private String id;
    private String name;

    // getters, setters ...
}

@Entity
public class ClassRoom {

    @Id
    private long id;
    private String lesson;

    @ManyToMany
    private List<Student> students = new ArrayList<Student>();

    // getters, setters ...
}

Student 集合

{
  "_id" : "john",
  "name" : "John Doe"
}
{
  "_id" : "mario",
  "name" : "Mario Rossi"
}
{
  "_id" : "kate",
  "name" : "Kate Doe"
}

ClassRoom 集合

{
  "_id" : NumberLong(1),
  "lesson" : "Math"
}
{
  "_id" : NumberLong(2),
  "lesson" : "English"
}

Associations 集合

{
  "_id" : {
    "ClassRoom_id" : NumberLong(1),
    "table" : "ClassRoom_Student"
  },
  "rows" : [ "john", "mario" ]
}
{
  "_id" : {
    "ClassRoom_id" : NumberLong(2),
    "table" : "ClassRoom_Student"
  },
  "rows" : [ "mario", "kate" ]
}
示例 104. 使用全局集合策略的双向多对多
@Entity
public class AccountOwner {

    @Id
    private String id;

    private String SSN;

    @ManyToMany
    private Set<BankAccount> bankAccounts;

    // getters, setters ...
}

@Entity
public class BankAccount {

    @Id
    private String id;

    private String accountNumber;

    @ManyToMany(mappedBy = "bankAccounts")
    private Set<AccountOwner> owners = new HashSet<AccountOwner>();

    // getters, setters ...
}

AccountOwner 集合

{
  "_id" : "owner0001",
  "SSN" : "0123456"
}

BankAccount 集合

{
  "_id" : "account_1",
  "accountNumber" : "X2345000"
}

Associations 集合

{
  "_id" : {
    "bankAccounts_id" : "account_1",
    "table" : "AccountOwner_BankAccount"
    },

  "rows" : [ "owner0001" ]
}
{
  "_id" : {
    "owners_id" : "owner0001",
    "table" : "AccountOwner_BankAccount"
  },

  "rows" : [ "account_1" ]
}

10.4. 索引和唯一约束

10.4.1. 标准索引和唯一约束

您可以使用标准 JPA 注释在 MongoDB 中创建索引和唯一约束。

示例 105. 使用 JPA 注释创建索引和唯一约束
@Entity
@Table(indexes = {
    @Index(columnList = "author, name", name = "author_name_idx", unique = true),
    @Index(columnList = "name DESC", name = "name_desc_idx")
})
public class Poem {

    @Id
    private String id;
    private String name;
    private String author;

    @Column(unique = true)
    private String url;

   // getters, setters ...
}

MongoDB 通过唯一索引支持唯一约束。它将null视为唯一值:每个唯一索引中只能有一个null值。这与 JPA 世界中普遍接受的唯一约束定义不符。因此,默认情况下,我们会将唯一索引创建为sparse:它只对定义的值进行索引,以便唯一约束接受多个null值。

10.4.2. 使用 MongoDB 特定索引选项

可以使用 @IndexOption 注解定义这些选项。

示例 106. 创建使用 MongoDB 特定选项的索引
@Entity
@Table(indexes = {
    @Index(columnList = "author", name = "author_idx")
})
@IndexOptions(
    @IndexOption(forIndex = "author_idx", options = "{ background : true, sparse : true, partialFilterExpression : { author: 'Verlaine' } }")
)
public class Poem {

    @Id
    private String id;
    private String name;
    private String author;

   // getters, setters ...
}

@IndexOption 只是在索引创建时将选项传递给 MongoDB:您可以使用 MongoDB 中的任何可用选项。

10.4.3. 全文索引

MongoDB 支持为每个集合创建一个(并且只能创建一个)全文索引。

由于 JPA 不支持在 @Index 注解中将 text 定义为排序顺序(只支持 ASCDESC),因此该功能已包含在 @IndexOption 机制中。您只需要将 text: true 添加到传递给 MongoDB 的选项中,Hibernate OGM 会解释它并将索引转换为全文索引。

示例 107. 创建全文索引
@Entity
@Table(indexes = {
    @Index(columnList = "author, name", name = "author_name_text_idx")
})
@IndexOptions(
    @IndexOption(forIndex = "author_name_text_idx", options = "{ text: true, default_language : 'fr', weights : { author: 2, name: 5 } }")
)
public class Poem {

    @Id
    private String id;
    private String name;
    private String author;

   // getters, setters ...
}

10.5. 事务

MongoDB 不支持事务。只有应用于同一文档的更改才会原子地执行。应用于多个文档的更改不会原子地执行。Hibernate OGM 在刷新时间之前对所有更改进行排队,这在一定程度上缓解了这个问题。因此,写入 MongoDB 的时间窗口比手动操作要短。

我们建议您在使用 Hibernate OGM 时仍然使用事务分隔符以透明地触发刷新操作(在提交时)。但不要考虑回滚,这将不起作用。

10.6. 乐观锁

MongoDB 没有提供用于检测对同一文档的并发更新的内置机制,但它提供了一种执行原子查找和更新操作的方法。通过利用此命令,Hibernate OGM 可以检测对同一文档的并发修改。

您可以使用 @Version 注解启用乐观锁检测。

示例 108. 通过 @Version 进行乐观锁检测
@Entity
public class Planet implements Nameable {

    @Id
    private String id;
    private String name;

    @Version
    private int version;

   // getters, setters ...
}
{
  "_id" : "planet-1",
  "name" : "Pluto",
  "version" : 0
}

@Version 注解定义哪个属性将跟踪文档的版本,Hibernate OGM 将在需要时更新该字段,如果来自两个不同会话(例如)的两个更改应用于同一个文档,则会抛出 org.hibernate.StaleObjectStateException

您可以使用 @Column 更改在 MongoDB 上创建的字段的名称。

示例 109. 通过 @Version 使用 @Column 进行乐观锁检测
@Entity
public class Planet implements Nameable {

    @Id
    private String id;
    private String name;

    @Version
    @Column(name="OPTLOCK")
    private int version;

   // getters, setters ...
}
{
  "_id" : "planet-1",
  "name" : "Pluto",
  "OPTLOCK" : 0
}

10.7. 查询

您可以通过几种不同的方式表达查询

  • 使用 JPQL

  • 使用本机 MongoQL 查询

  • 使用 Hibernate Search 查询(提供高级全文和地理空间查询)

虽然您可以将 JPQL 用于简单的查询,但可能会遇到限制。如果您的查询涉及嵌套(列表)元素,则当前推荐的方法是使用本机 MongoQL。

MongoDB 不需要 Hibernate Search 来运行查询。

为了反映在当前会话中执行的更改,在查询执行之前,所有受给定查询影响的实体都将刷新到数据存储(Hibernate ORM 以及 Hibernate OGM 都是如此)。

对于 MongoDB 等非完全事务性存储,这会导致更改作为运行查询的副作用写入,这些更改不能被可能的后续回滚撤销。

根据您的具体用例和需求,您可能更喜欢禁用自动刷新,例如通过调用 query.setFlushMode( FlushMode.MANUAL )。但请记住,在这种情况下,查询结果将不会反映当前会话中应用的更改。

10.7.1. JPQL 查询

Hibernate OGM 正在开发中,因此在使用 JPQL 查询支持时,只有一部分 JPQL 结构可用。这包括

  • 使用“<”、“<=”、“=”、“>=” 和“>” 的简单比较

  • IS NULLIS NOT NULL

  • 布尔运算符 ANDORNOT

  • LIKEINBETWEEN

  • ORDER BY

  • 嵌入集合上的内部 JOIN

  • 常规和嵌入属性的投影

使用这些结构的查询将转换为等效的本机 MongoDB 查询。

请告诉我们 通过打开问题或发送电子邮件 您希望执行的查询。扩展我们在该领域的支持是我们优先事项中的首要任务。

10.7.2. 本机 MongoDB 查询

Hibernate OGM 还支持 MongoDB 的某些形式的本机查询。目前,通过 MongoDB 后端提供了两种形式的本机查询

  • 仅指定搜索条件的查找查询

  • 使用 MongoDB CLI 语法指定的查询(CLI 语法

前者始终将结果映射到实体类型。后者要么将结果映射到实体类型,要么映射到某些支持的投影形式。请注意,MongoDB 不支持参数化查询,因此不要期望 Query#setParameter() 工作。

您可以按以下示例所示执行本机查询

示例 110. 使用 JPA API
@Entity
public class Poem {

    @Id
    private Long id;

    private String name;

    private String author;

   // getters, setters ...
}

...

javax.persistence.EntityManager em = ...

// criteria-only find syntax
String query1 = "{ $and: [ { name : 'Portia' }, { author : 'Oscar Wilde' } ] }";
Poem poem = (Poem) em.createNativeQuery( query1, Poem.class ).getSingleResult();

// criteria-only find syntax with order-by
String query2 = "{ $query : { author : 'Oscar Wilde' }, $orderby : { name : 1 } }";
List<Poem> poems = em.createNativeQuery( query2, Poem.class ).getResultList();

// projection via CLI-syntax
String query3 = "db.WILDE_POEM.find(" +
    "{ '$query' : { 'name' : 'Athanasia' }, '$orderby' : { 'name' : 1 } }" +
    "{ 'name' : 1 }" +
    ")";

// will contain name and id as MongoDB always returns the id for projections
List<Object[]> poemNames = (List<Object[]>)em.createNativeQuery( query3 ).getResultList();

// projection via CLI-syntax
String query4 = "db.WILDE_POEM.count({ 'name' : 'Athanasia' })";

Object[] count = (Object[])em.createNativeQuery( query4 ).getSingleResult();

查询的结果是一个托管实体(或其列表)或属性的投影,以对象数组的形式,就像您从 JPQL 查询中获得的一样。

示例 111. 使用 Hibernate 本机 API
OgmSession session = ...

String query1 = "{ $and: [ { name : 'Portia' }, { author : 'Oscar Wilde' } ] }";
Poem poem = session.createNativeQuery( query1 )
                      .addEntity( "Poem", Poem.class )
                      .uniqueResult();

String query2 = "{ $query : { author : 'Oscar Wilde' }, $orderby : { name : 1 } }";
List<Poem> poems = session.createNativeQuery( query2 )
                      .addEntity( "Poem", Poem.class )
                      .list();

本机查询也可以使用 @NamedNativeQuery 注解创建

示例 112. 使用 @NamedNativeQuery
@Entity
@NamedNativeQuery(
   name = "AthanasiaPoem",
   query = "{ $and: [ { name : 'Athanasia' }, { author : 'Oscar Wilde' } ] }",
   resultClass = Poem.class )
public class Poem { ... }

...

// Using the EntityManager
Poem poem1 = (Poem) em.createNamedQuery( "AthanasiaPoem" )
                     .getSingleResult();

// Using the Session
Poem poem2 = (Poem) session.getNamedQuery( "AthanasiaPoem" )
                     .uniqueResult();

Hibernate OGM 以自然的方式存储数据,因此您仍然可以使用 MongoDB 驱动程序执行查询,主要缺点是结果将是原始的 MongoDB 文档,而不是托管实体。

CLI 语法

Hibernate OGM 可以使用 MongoDB CLI 语法执行本机查询,但有一些限制。目前支持 find()findOne()findAndModify()count() 查询。此外,通过 CLI 语法支持三种类型的写入查询:insert()remove()update()。其他查询类型可能会在将来的版本中得到支持。

正如预期的那样,find()findOne()findAndModify()aggregatedistinct()count() 可以使用 javax.persistence.Query.getSingleResult()javax.persistence.Query.getResultList() 执行,而 insert()remove()update() 则需要使用 javax.persistence.Query.executeUpdate() 执行。还要注意,如果未确认与所使用的写入关注度相关的查询执行,则 javax.persistence.Query.executeUpdate() 可能会返回 -1。通过 javax.persistence.Query.executeUpdate(),还可以运行 db.Collection.drop() 查询。

db.Collection.drop() 将始终返回 1。这是因为我们正在使用的底层驱动程序在操作执行后不会返回任何值。

以下函数可以在提供的 JSON 中使用:BinDataDateHexDataISODateNumberLongObjectIdTimestampRegExpDBPointerUUIDGUIDCSUUIDCSGUIDJUUIDJGUIDPYUUIDPYGUID

NumberInt 不受支持,因为它目前不受 MongoDB Java 驱动程序支持。

不支持诸如 sort() 之类的游标操作。而是使用相应的 MongoDB 查询修饰符,例如 $orderby,在条件参数内。

您可以使用 setMaxResults(…​) 方法限制查询的结果。

通过 CLI 语法传递的 JSON 参数必须使用 严格模式 指定。具体来说,键需要放在引号内;唯一的放松是,在指定属性名称/值时可以使用单引号,以便在 Java 字符串中嵌入查询。

请注意,投影的结果目前是从 MongoDB 驱动程序中检索到的,并且(尚未)使用合适的 Hibernate OGM 类型实现进行转换。此要求在 OGM-1031 中跟踪。

示例 113. CLI 语法示例
// Valid syntax
String valid = "db.Poem.find({ \"name\" : \"Athanasia\" })";

String alsoValid = "db.Poem.find({ '$or' : [{'name': 'Athanasia' }, {'name': 'Portia' }]})";

String validAggregation = "db.Poem.aggregate([{ '$match': {'author': { '$regex': 'oscar.*', '$options': 'i' } } }, { '$sort': {'name': -1 } } ])";

// NOT Valid syntax, it will throw an exception: com.mongodb.util.JSONParseException
String notValid =  "db.Poem.find({ name : \"Athanasia\" })";

String alsoNotValid = "db.Poem.find({ $or : [{name: 'Athanasia' }, {name: 'Portia' }]})";
示例 114. CLI 语法排序和限制结果的替代方法
String nativeQuery = "db.Poem.find({ '$query': { 'author': 'Oscar Wilde' }, '$orderby' : { 'name' : 1 } })";

// Using hibernate session
List<Poem> result = session.createNativeQuery( nativeQuery )
    .addEntity( Poem.class )
    .setMaxResults( 2 )
    .list();

// Using JPA entity manager
List<Poem> results = em.createNativeQuery( nativeQuery, Poem.class )
    .setMaxResults( 2 )
    .getResultList();
示例 115. CLI 语法更新示例
String updateQuery = "db.Poem.findAndModify({ 'query': {'_id': 1}, 'update': { '$set': { 'author': 'Oscar Wilde' } }, 'new': true })";
List<Poem> updated = session.createNativeQuery( updateQuery ).addEntity( Poem.class ).list();

String insertQuery = "db.Poem.insert({ '_id': { '$numberLong': '11' }, 'author': 'Oscar Wilder', 'name': 'The one and wildest', 'rating': '1' } )";
int inserted = session.createNativeQuery( insertQuery ).executeUpdate();

String removeQuery = "db.Poem.remove({ '_id': { '$numberLong': '11' } })";
int removed = session.createNativeQuery( removeQuery ).executeUpdate();

$regexp 运算符的支持仅限于字符串语法。我们不支持 /pattern/ 语法,因为它目前不受 MongoDB Java 驱动程序支持。

// Valid syntax
String nativeQuery = "{ $query : { author : { $regex : '^Oscar' } }, $orderby : { name : 1 } }";
List<Poem> result = session.createNativeQuery( nativeQuery ).addEntity( Poem.class ).list();

10.7.3. 服务器端 JavaScript 和存储过程

这是一个实验性功能。

在 MongoDB 中,可以像调用存储过程一样调用服务器端 JavaScript。您可以使用 JPA 中的现有方法

示例 116. 使用位置参数调用服务器端 JavaScript
  @Entity
  public class Car {
    @Id
    private Integer id;

    private String brand;

    ...
  }

  EntityManager em = ...
  StoredProcedureQuery storedProcedureQuery = em.createStoredProcedureQuery( "findMostExpensiveCars", Car.class );
  storedProcedureQuery.registerStoredProcedureParameter( "year", Integer.class, ParameterMode.IN );
  storedProcedureQuery.setParameter( "year", 1995 );
  List<Car> cars = storedProcedureQuery.getResultList();

此示例将假设在 MongoDB 中有一个 findMostExpensiveCars JavaScript 函数,并且该函数的结果是一个可以映射到 Car 实体的汽车列表。

示例 117. 使用 Hibernate OGM 使用位置参数调用服务器端 JavaScript
{
  "result" : [
    { "id":1, "brand":"Bentley" },
    { "id":2, "brand":"Maserati" },
  ]
}

有关服务器端函数的更多详细信息,请参阅 MongoDB 参考文档

您可以使用 Hibernate Search 对实体进行索引。这样,Hibernate Search 会维护一组独立于 MongoDB 的辅助索引,您可以在这些索引之上运行 Lucene 查询。这种方法的好处是在 JPA/Hibernate API 级别很好地集成(查询将返回托管实体)。缺点是您需要将 Lucene 索引存储在某个地方(文件系统、infinispan 网格等)。请查看 Infinispan 部分(在 Infinispan 中存储 Lucene 索引)以获取有关如何使用 Hibernate Search 的更多信息。

10.8. 地理空间支持

10.8.1. 地理空间字段

我们的 MongoDB 集成支持通过使用特定 Java 类型来声明地理空间字段,这些类型将自动转换为存储在 MongoDB 中的 GeoJSON 对象。

我们目前支持以下类型

  • GeoPoint,存储为 GeoJSON 点

  • GeoMultiPoint,存储为 GeoJSON 多点

  • GeoLineString,存储为 GeoJSON 线字符串

  • GeoMultiLineString,存储为 GeoJSON 多线字符串

  • GeoPolygon,存储为 GeoJSON 多边形

  • GeoMultiPolygon,存储为 GeoJSON 多边形

您可以在 MongoDB 文档 中找到有关这些类型及其约束的更多信息。

示例 118. 声明地理空间字段
@Entity
public class Restaurant {

    // [...]

    private GeoPoint location;
}

这些 Java 类型附带方便的构造函数和帮助器,可帮助操作它们。

示例 119. 实例化多边形
GeoPolygon polygon = new GeoPolygon(
        new GeoPoint( 4.814922, 45.7753612 ),
        new GeoPoint( 4.8160825, 45.7327172 ),
        new GeoPoint( 4.9281299, 45.7211302 ),
        new GeoPoint( 4.8706127, 45.786724 ),
        new GeoPoint( 4.814922, 45.7753612 )
);

10.8.2. 地理空间索引和查询

要能够对地理空间字段运行优化查询,您需要声明空间索引。

您可以利用您通常的注解直接在您的实体上声明索引。

示例 120. 声明地理空间索引
@Entity
@Table(indexes = {
        @Index(columnList = "location", name = "location_spatial_idx")
})
@IndexOptions(
        @IndexOption(forIndex = "location_spatial_idx", options = "{ _type: '2dsphere' }")
)
public class Restaurant {

    // [...]

    private GeoPoint location;
}

请注意,您需要使用 @IndexOption 注解来精确指定索引的类型。

下一步是使用本机查询执行地理空间查询。

示例 121. 查找点周围的实体
GeoPoint geoPoint = new GeoPoint( 4.8520035, 45.7498209 );

Query query = session
        .createNativeQuery( "{ location: { $near: { $geometry: " + geoPoint.toBsonDocument() + ", $maxDistance: 500 } } }" )
        .addEntity( Restaurant.class );
List<Restaurant> result = query.list();
示例 122. 查找多边形内的实体
GeoPolygon geoPolygon = new GeoPolygon(
        new GeoPoint( 4.814922, 45.7753612 ),
        new GeoPoint( 4.8160825, 45.7327172 ),
        new GeoPoint( 4.9281299, 45.7211302 ),
        new GeoPoint( 4.8706127, 45.786724 ),
        new GeoPoint( 4.814922, 45.7753612 )
);

Query query = session
        .createNativeQuery( "{ location: { $geoWithin: { $geometry: " + geoPolygon.toBsonDocument() + " } } }" )
        .addEntity( Restaurant.class );
List<Restaurant> result = query.list();

要了解有关 MongoDB 空间索引和查询的更多信息,请参阅 MongoDB 文档

11. Neo4j

Neo4j 是一款功能强大的(完全 ACID)事务性属性图数据库。这种数据库适合那些可以用图表示的问题,例如社交关系或路线图。

Hibernate OGM 可以连接到嵌入式 Neo4j 或远程服务器。与远程服务器的连接可以通过 Bolt 协议或 HTTP API 进行。

11.1. 如何添加 Neo4j 集成

1. 将依赖项添加到您的项目中

如果您的项目使用 Maven,您可以将此添加到 pom.xml 中

<dependency>
    <groupId>org.hibernate.ogm</groupId>
    <artifactId>hibernate-ogm-neo4j</artifactId>
    <version>5.4.1.Final</version>
</dependency>

或者,您可以在 SourceForge 上的发布包中找到所需的库

2. 使用以下属性

如果您想在嵌入模式下使用 Neo4j

hibernate.ogm.datastore.provider = neo4j_embedded
hibernate.ogm.neo4j.database_path = C:\example\mydb

如果您想连接到本地主机上的未经身份验证的远程服务器,您可以使用

hibernate.ogm.datastore.provider = neo4j_bolt

hibernate.ogm.datastore.provider = neo4j_http

neo4j_boltneo4j_http 之间的区别在于,在第一种情况下,Hibernate OGM 将使用 Bolt 协议连接到服务器,在第二种情况下,它将使用 HTTP 接口。

您可以选择不同的主机、端口并使用以下附加属性启用身份验证

hibernate.ogm.datastore.provider = neo4j_bolt # or neo4j_http
hibernate.ogm.datastore.host = example.com:8989
hibernate.ogm.datastore.username = example_username
hibernate.ogm.datastore.password = example_password

Neo4j 方言不需要 Hibernate Search 来运行 JPQL 或 HQL 查询。

11.2. 配置 Neo4j

以下属性可用于配置 Neo4j 支持

Neo4j 数据存储配置属性
hibernate.ogm.neo4j.database_path

表示 Neo4j 数据库位置的绝对路径。示例:C:\neo4jdb\mydb

hibernate.ogm.neo4j.configuration_resource_name (可选)

Neo4j 嵌入属性文件的路径。它可以是 URL、类路径资源的名称或文件系统路径。

hibernate.schema_update.unique_constraint_strategy (可选)

如果设置为 SKIP,Hibernate OGM 不会在表示实体的节点上创建任何唯一约束。此属性不会影响为序列生成的唯一约束。其他可能的值(在 org.hibernate.tool.hbm2ddl.UniqueConstraintSchemaUpdateStrategy 枚举中定义)是 DROP_RECREATE_QUIETLYRECREATE_QUIETLY,但效果将相同(因为 Neo4j 约束没有名称):保留现有约束并创建缺失的约束。默认值为 DROP_RECREATE_QUIETLY

hibernate.ogm.datastore.host (可选)

连接到远程服务器时使用的主机名和端口。HTTP 的默认值为 localhost:7474。Bolt 的默认值为 localhost:7687

hibernate.ogm.datastore.username (可选)

远程服务器的身份验证用户名。只有在设置此属性时,Hibernate OGM 才会尝试进行身份验证。

hibernate.ogm.datastore.password (可选)

远程连接时使用的密码。

hibernate.ogm.neo4j.client.connection_pool_size (可选)

使用 http 协议时客户端连接池的大小。默认值为 10。

hibernate.connection.resource

如果您使用 Bolt 接口,您可以查找数据存储客户端。请参阅 与 WildFly NoSQL 集成

在以编程方式引导会话工厂或实体管理器工厂时,您应该在指定上面列出的配置属性时使用 org.hibernate.ogm.datastore.neo4j.Neo4jProperties 上定义的常量。

存储之间共享的通用属性在 OgmPropertiesNeo4jProperties 的超接口)上声明。

为了在存储之间实现最大程度的可移植性,请使用尽可能通用的接口。

11.3. 存储原理

Hibernate OGM 试图使对底层数据存储的映射尽可能自然,以便不使用 Hibernate OGM 的第三方应用程序仍然可以读取和更新相同的数据存储。

为了简单起见,每个实体都由一个节点表示,每个嵌入对象也由一个节点表示。实体之间的链接(无论是单向关联还是多向关联)都由节点之间的关系表示。实体和嵌入节点分别标记为 ENTITYEMBEDDED

11.3.1. 属性和内置类型

每个实体都由一个节点表示。每个属性或更准确地说是列都由该节点的一个属性表示。

以下类型(以及相应的原始类型)在没有任何转换的情况下传递给 Neo4j

  • java.lang.Boolean; 可选地,可以使用方法 @Type(type = "true_false")@Type(type = "yes_no")@Type(type = "numeric_boolean") 来将布尔属性分别映射到字符 'T'/'F'、'Y'/'N' 或整数值 0/1。

  • java.lang.Character

  • java.lang.Byte

  • java.lang.Short

  • java.lang.Integer

  • java.lang.Long

  • java.lang.Float

  • java.lang.Double

  • java.lang.String

以下类型将转换为 java.lang.String

  • java.math.BigDecimal

  • java.math.BigInteger

  • java.util.Calendar

    stored as `String` with the format "yyyy/MM/dd HH:mm:ss:SSS Z"
  • java.util.Date

    stored as `String` with the format "yyyy/MM/dd HH:mm:ss:SSS Z"
  • java.util.UUID

  • java.util.URL

Hibernate OGM 不会在 Neo4J 中存储空值,将值设置为 null 与从 Neo4J 中删除相应条目相同。

这可能对空值查询产生影响。

11.3.2. 实体

实体存储为 Neo4j 节点,这意味着每个实体属性将转换为节点的属性。映射实体的表的名称用作标签。

您可以使用 @Table@Column 注释的名称属性来重命名标签和节点的属性。

在节点中添加了一个额外的标签 ENTITY

示例 123. 实体的默认 JPA 映射
@Entity
public class News {

    @Id
    private String id;
    private String title;

    // getters, setters ...
}
neo4j single node example
示例 124. 使用 @Table 和 @Column 重命名节点标签和属性
@Entity
@Table(name="ARTICLE")
public class News {

    @Id
    private String id;

    @Column(name = "headline")
    private String title;

    // getters, setters ...
}
neo4j @Column @Table example
标识符和唯一约束

Neo4j 不支持跨多个属性的约束。因此,Hibernate OGM 仅在跨越单个属性时创建唯一约束,并且会忽略跨越多个属性的约束。

节点属性上缺少唯一约束可能会导致创建具有相同标识符的多个节点。

Hibernate OGM 将为实体的标识符以及使用以下注释的属性创建唯一约束

  • @Id

  • @EmbeddedId

  • @NaturalId

  • @Column( unique = true )

  • @Table( uniqueConstraints = @UniqueConstraint(columnNames = { "column_name" } ) )

嵌入式标识符当前存储为点分隔属性。

示例 125. 带有 @EmbeddedId 的实体
@Entity
public class News {

    @EmbeddedId
    private NewsID newsId;

    private String content

    // getters, setters ...
}

@Embeddable
public class NewsID implements Serializable {

    private String title;
    private String author;

    // getters, setters ...
}
neo4j @EmbeddedId example
嵌入式对象和集合

嵌入式元素存储为用 EMBEDDED 标记的独立节点。

连接实体节点到嵌入节点的关系类型是 java 类中表示嵌入的属性名称。

示例 126. 嵌入式对象
@Entity
public class News {

    @EmbeddedId
    private NewsID newsId;

    @Embedded
    private NewsPaper paper;

    // getters, setters ...
}

@Embeddable
public class NewsID implements Serializable {

    private String title;
    private String author;

    // getters, setters ...
}

@Embeddable
public class NewsPaper {

    private String name;
    private String owner;

    // getters, setters ...
}
neo4j @Embedded example
示例 127. @ElementCollection
@Entity
public class GrandMother {

    @Id
    private String id;

    @ElementCollection
    private List<GrandChild> grandChildren = new ArrayList<GrandChild>();

    // getters, setters ...
}

@Embeddable
public class GrandChild {

    private String name;

    // getters, setters ...
}
neo4j @ElementCollection example

请注意,在前面的示例中,没有向关系添加属性;在下面的示例中,添加了一个属性来跟踪列表中元素的顺序。

示例 128. 带有 @OrderColumn 的 @ElementCollection
@Entity
public class GrandMother {

    @Id
    private String id;

    @ElementCollection
    @OrderColumn( name = "birth_order" )
    private List<GrandChild> grandChildren = new ArrayList<GrandChild>();

    // getters, setters ...
}

@Embeddable
public class GrandChild {

    private String name;

    // getters, setters ...
}
neo4j @ElementCollection @OrderColumn example

也可以在 Map 中使用嵌入式,如以下示例所示

示例 129. 带有嵌入式 Map 的 @ElementCollection
@Entity
public class ForumUser {

    @Id
    private String name;

    @ElementCollection
    private Map<String, JiraIssue> issues = new HashMap<>();

    // getters, setters ...
}

@Embeddable
public class JiraIssue {

    private String project;

    private Integer number;

    // getters, setters ...
}
neo4j @ElementCollection map example

但是,这种映射存在一些问题。它将为 Map 中的每个元素创建一个额外的节点,并且嵌入式值将存储在只有一个 EMBEDDED 标签的节点中。我们认为这不是一种自然的映射,预计它将在即将发布的版本中发生变化。

11.3.3. 关联

双向关联或单向关联始终使用一种关系进行映射,从关联的拥有方开始。这是可能的,因为在 Neo4j 中关系可以双向导航。

关系的类型取决于关联的类型,但通常是主侧的关联角色。存储在关系上的唯一属性将是关联的索引(如果需要),例如,当关联使用 @OrderColumn 注释或使用 java.util.Map 时。

在 Neo4j 中,节点通过关系连接,这意味着我们不需要创建存储外键的属性。这意味着 @JoinColumn 之类的注释不会有任何影响。

示例 130. 单向一对一
@Entity
public class Vehicle {

    @Id
    private String id;
    private String brand;

    // getters, setters ...
}


@Entity
public class Wheel {

    @Id
    private String id;
    private String company;
    private double diameter;

    @OneToOne
    private Vehicle vehicle;

    // getters, setters ...
}
neo4j uni one to one example
示例 131. 双向一对一
@Entity
public class Husband {

    @Id
    private String id;
    private String name;

    @OneToOne
    private Wife wife;

    // getters, setters ...
}

@Entity
public class Wife {

    @Id
    private String id;
    private String name;

    @OneToOne(mappedBy = "wife")
    private Husband husband;

    // getters, setters ...
}
neo4j bi one to one example
示例 132. 单向一对多
@Entity
public class Basket {

    @Id
    private String id;

    private String owner;

    @OneToMany
    private List<Product> products = new ArrayList<Product>();

    // getters, setters ...
}

@Entity
public class Product {

    @Id
    private String name;

    private String description;

    // getters, setters ...
}
neo4j uni one to many example
示例 133. 使用默认值映射的单向一对多
@Entity
public class User {

    @Id
    private String id;

    @OneToMany
    private Map<String, Address> addresses = new HashMap<String, Address>();

    // getters, setters ...
}

@Entity
public class Address {

    @Id
    private String id;
    private String city;

    // getters, setters ...
}
neo4j uni one to many with map example
示例 134. 使用 @MapKeyColumn 映射的单向一对多
@Entity
public class User {

    @Id
    private String id;

    @OneToMany
    @MapKeyColumn(name = "addressType")
    private Map<String, Address> addresses = new HashMap<String, Address>();

    // getters, setters ...
}

@Entity
public class Address {

    @Id
    private String id;
    private String city;

    // getters, setters ...
}
neo4j uni one to many with @MapKeyColumn example
示例 135. 单向多对一
@Entity
public class JavaUserGroup {

    @Id
    private String jug_id;
    private String name;

    // getters, setters ...
}

@Entity
public class Member {

    @Id
    private String id;
    private String name;

    @ManyToOne
    private JavaUserGroup memberOf;

    // getters, setters ...
}
neo4j uni many to one example
示例 136. 双向多对一
@Entity
public class SalesForce {

    @Id
    private String id;
    private String corporation;

    @OneToMany(mappedBy = "salesForce")
    private Set<SalesGuy> salesGuys = new HashSet<SalesGuy>();

    // getters, setters ...
}

@Entity
public class SalesGuy {
    private String id;
    private String name;

    @ManyToOne
    private SalesForce salesForce;

    // getters, setters ...
}
neo4j bi many to one example
示例 137. 单向多对多
@Entity
public class Student {

    @Id
    private String id;
    private String name;

    // getters, setters ...
}

@Entity
public class ClassRoom {

    @Id
    private long id;
    private String lesson;

    @ManyToMany
    private List<Student> students = new ArrayList<Student>();

    // getters, setters ...
}
neo4j uni many to many example
示例 138. 双向多对多
@Entity
public class AccountOwner {

    @Id
    private String id;

    private String SSN;

    @ManyToMany
    private Set<BankAccount> bankAccounts;

    // getters, setters ...
}

@Entity
public class BankAccount {

    @Id
    private String id;

    private String accountNumber;

    @ManyToMany( mappedBy = "bankAccounts" )
    private Set<AccountOwner> owners = new HashSet<AccountOwner>();

    // getters, setters ...
}
neo4j bi many to many example
示例 139. 双向一对一,一对多(映射到同一个实体)
@Entity
public class Person {

    @Id
    private String name;
    @OneToOne
    private Person spouse;
    @OneToMany
    private List<Person> children;

    // getters, setters ...
}
neo4j bi one to one many to many same entity example

11.3.4. 自动生成的值

Hibernate OGM 支持表生成策略和序列生成策略,并与 Neo4j 一起使用。通常建议使用后者,因为它允许对下一个序列值进行更有效的查询。

基于序列的生成器以以下形式的节点表示

示例 140. GenerationType.SEQUENCE
@Entity
public class Song {

    ...

    @Id
    @GeneratedValue( strategy = GenerationType.SEQUENCE, generator = "songSequenceGenerator" )
    @SequenceGenerator(
            name = "songSequenceGenerator",
            sequenceName = "song_sequence",
            initialValue = INITIAL_VALUE,
            allocationSize = 10)
    public Long getId() {
        return id;
    }

    ...
neo4j sequence example

每个序列生成器节点都用 SEQUENCE 标记。序列名称可以通过 @SequenceGenerator#sequenceName() 指定。对属性 sequence_name 应用唯一约束,以确保序列的唯一性。

如果需要,您可以通过 @SequenceGenerator#initialValue()@SequenceGenerator#allocationSize() 分别设置序列的初始值和增量大小。选项 @SequenceGenerator#catalog()@SequenceGenerator#schema() 不受支持。

基于表的生成器以以下形式的节点表示

示例 141. GenerationType.TABLE
@Entity
public class Video {

    ...

    @Id
    @GeneratedValue( strategy = GenerationType.TABLE, generator = "video" )
    @TableGenerator(
         name = "video",
         table = "Sequences",
         pkColumnName = "key",
         pkColumnValue = "video",
         valueColumnName = "seed"
    )
    public Integer getId() {
        return id;
    }

    ...
neo4j table based sequence example

每个表生成器节点都用 TABLE_BASED_SEQUENCE 和通过 @TableGenerator#table() 指定的表名称标记。序列名称应通过 @TableGenerator#pkColumnValue() 给出。保存序列名称和值的节点属性可以通过 @TableGenerator#pkColumnName()@TableGenerator#valueColumnName() 分别配置。对属性 sequence_name 应用唯一约束,以避免在同一个“表”中两次使用相同的序列名称。

如果需要,您可以通过 @TableGenerator#initialValue()@TableGenerator#allocationSize() 分别设置序列的初始值和增量大小。选项 @TableGenerator#catalog()@TableGenerator#schema()@TableGenerator#uniqueConstraints()@TableGenerator#indexes() 不受支持。

11.3.5. 标签摘要

数据库可以包含的标签的最大数量大约为 20 亿。

以下摘要将帮助您跟踪分配给新节点的标签

表 55. 分配给新节点的标签摘要
节点类型 标签

实体

ENTITY, <实体类名>

可嵌入

EMBEDDED, <可嵌入类名>

GenerationType.SEQUENCE

SEQUENCE

GenerationType.TABLE

TABLE_BASED_SEQUENCE, <表名称>

11.4. 事务

在 Neo4j 中,操作必须在事务中执行。当您以 Neo4J 为目标时,请确保您与 Hibernate OGM 的交互在事务中。

示例 142. 启动和提交事务的示例
Session session = factory.openSession();
Transaction tx = session.beginTransaction();

Account account = new Account();
account.setLogin( "myAccount" );
session.persist( account );

tx.commit();

...

tx = session.beginTransaction();
Account savedAccount =  (Account) session.get( Account.class, account.getId() );
tx.commit();

在 JTA 的情况下,Hibernate OGM 将 Neo4J 内部事务附加到 JTA 事务生命周期。这样,当 JTA 事务提交或回滚(例如,由 EJB CMT 或显式地)时,Neo4J 事务也会提交或回滚。这使得 Java EE 容器中的集成非常方便。

这不是真正的 JTA/XA 集成,而是一种生命周期对齐:对多个数据源的更改不会作为单个原子事务执行。

特别是,如果 JTA 事务涉及多个资源,Neo4J 可能会在另一个资源出现故障之前提交。在这种情况下,即使 JTA 事务将回滚,Neo4J 也无法回滚。

11.5. 查询

您可以通过几种不同的方式表达查询

  • 使用 JPQL

  • 使用 Cypher 查询语言

虽然您可以将 JPQL 用于简单查询,但您可能会遇到限制。如果您需要嵌套(列表)元素,则当前推荐的方法是使用本机 Cypher 查询。

您不需要在类路径上使用 Hibernate Search 来运行查询。

11.5.1. JPQL 查询

Hibernate OGM 正在开发中,因此在使用 JPQL 查询支持时,只有一部分 JPQL 结构可用。这包括

  • 使用“<”、“<=”、“=”、“>=” 和“>” 的简单比较

  • IS NULLIS NOT NULL

  • 布尔运算符 ANDORNOT

  • LIKEINBETWEEN

  • ORDER BY

  • 嵌入集合上的内部 JOIN

  • 常规和嵌入属性的投影

使用这些结构的查询将被转换为等效的 Cypher 查询

请告诉我们 通过打开问题或发送电子邮件 您希望执行的查询。扩展我们在该领域的支持是我们优先事项中的首要任务。

11.5.2. Cypher 查询

Hibernate OGM 也支持 Cypher 查询,用于 Neo4j。您可以在以下示例中看到如何执行 Cypher 查询

示例 143. 使用 JPA API
@Entity
public class Poem {

    @Id
    private Long id;

    private String name;

    private String author;

   // getters, setters ...

}

...

javax.persistence.EntityManager em = ...

// a single result query
String query1 = "MATCH ( n:Poem { name:'Portia', author:'Oscar Wilde' } ) RETURN n";
Poem poem = (Poem) em.createNativeQuery( query1, Poem.class ).getSingleResult();

// query with order by
String query2 = "MATCH ( n:Poem { name:'Portia', author:'Oscar Wilde' } ) " +
                "RETURN n ORDER BY n.name";
List<Poem> poems = em.createNativeQuery( query2, Poem.class ).getResultList();

// query with projections
String query3 = MATCH ( n:Poem ) RETURN n.name, n.author ORDER BY n.name";
List<Object[]> poemNames = (List<Object[]>) em.createNativeQuery( query3 )
                               .getResultList();

查询的结果是一个托管实体(或其列表)或属性的投影,以对象数组的形式,就像您从 JPQL 查询中获得的一样。

示例 144. 使用 Hibernate 原生 API
OgmSession session = ...

String query1 = "MATCH ( n:Poem { name:'Portia', author:'Oscar Wilde' } ) " +
                "RETURN n";
Poem poem = session.createNativeQuery( query1 )
                      .addEntity( "Poem", Poem.class )
                      .uniqueResult();

String query2 = "MATCH ( n:Poem { name:'Portia', author:'Oscar Wilde' } ) " +
                "RETURN n ORDER BY n.name";
List<Poem> poems = session.createNativeQuery( query2 )
                      .addEntity( "Poem", Poem.class )
                      .list();

本机查询也可以使用 @NamedNativeQuery 注解创建

示例 145. 使用 @NamedNativeQuery
@Entity
@NamedNativeQuery(
   name = "AthanasiaPoem",
   query = "MATCH ( n:Poem { name:'Athanasia', author:'Oscar Wilde' } ) RETURN n",
   resultClass = Poem.class )
public class Poem { ... }

...

// Using the EntityManager
Poem poem1 = (Poem) em.createNamedQuery( "AthanasiaPoem" )
                     .getSingleResult();

// Using the Session
Poem poem2 = (Poem) session.getNamedQuery( "AthanasiaPoem" )
                     .uniqueResult();

Hibernate OGM 以自然的方式存储数据,因此您仍然可以使用您喜欢的工具执行查询,主要缺点是结果将是原始的 Neo4j 元素,而不是管理的实体。