Hibernate OGM 已不再维护

欢迎来到《Hibernate OGM NoSQL 101教程》系列的第二部分。在第一部分中,您已经了解了如何将Hibernate OGM包含到Java项目中,以及如何轻松持久化实体并在不同的NoSQL数据存储之间切换。

现在,您的数据存储中已经有了一些数据,您可能想要对其进行一些查询。不必担心,Hibernate OGM将允许您以多种不同的方式获取数据

  • 使用Java持久化查询语言(JP-QL)
  • 使用您选择的数据存储的NoSQL原生查询语言(如果有的话)
  • 使用Hibernate Search查询 - 主要全文查询

所有这些选项都允许您对数据存储运行查询,并将结果作为管理实体的列表返回。

准备测试类

我们将添加一个新的类HikeQueryTest。它将使用关于远足的信息填充数据存储

public class HikeQueryTest {

    private static EntityManagerFactory entityManagerFactory;

    @BeforeClass
    public static void setUpEntityManagerFactoryAndPopulateTheDatastore() {
        entityManagerFactory = Persistence.createEntityManagerFactory( "hikePu" );

            EntityManager entityManager = entityManagerFactory.createEntityManager();

            entityManager.getTransaction().begin();

            // create a Person
            Person bob = new Person( "Bob", "McRobb" );

            // and two hikes
            Hike cornwall = new Hike(
                "Visiting Land's End", new Date(), new BigDecimal( "5.5" ),
                new HikeSection( "Penzance", "Mousehole" ),
                new HikeSection( "Mousehole", "St. Levan" ),
                new HikeSection( "St. Levan", "Land's End" )
            );
            Hike isleOfWight = new Hike(
                "Exploring Carisbrooke Castle", new Date(), new BigDecimal( "7.5" ),
                new HikeSection( "Freshwater", "Calbourne" ),
                new HikeSection( "Calbourne", "Carisbrooke Castle" )
            );

            // let Bob organize the two hikes
            cornwall.setOrganizer( bob );
            bob.getOrganizedHikes().add( cornwall );

            isleOfWight.setOrganizer( bob );
            bob.getOrganizedHikes().add( isleOfWight );

            // persist organizer (will be cascaded to hikes)
            entityManager.persist( bob );

            entityManager.getTransaction().commit();
           entityManager.close();
    }

    @AfterClass
    public static void closeEntityManagerFactory() {
        entityManagerFactory.close();
    }
}

此方法将确保在运行测试之前创建实体管理工厂,并且数据存储中包含一些数据。这些数据与第一部分中存储的数据相同。

现在我们已经有了数据,我们可以开始编写一些测试来搜索它们。

使用Java持久化查询语言(JP-QL)

JP-QL是作为Java持久化API(JPA)规范的一部分定义的查询语言。它旨在与实体一起使用,并且是数据库无关的。

以实体Hike为例

@Entity
public class Hike {

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

    private String description;
    private Date date;
    private BigDecimal difficulty;

    @ManyToOne
    private Person organizer;

    @ElementCollection
    @OrderColumn(name = "sectionNo")
    private List<HikeSection> sections;
       
      // constructors, getters, setters, ...
}

一个用于按难度顺序获取可用徒步旅行列表的JP-QL查询看起来是这样的

SELECT h FROM Hike h ORDER BY h.difficulty ASC

Hibernate OGM将解析此查询并将其转换为所选数据存储的原生查询语言等价查询。例如,在Neo4j中,它会创建并执行一个如下所示的Cypher查询

MATCH (h:Hike) RETURN h ORDER BY h.difficulty

在MongoDB中,使用MongoDB JavaScript API作为查询符号,它看起来像这样

db.Hike.find({}, { "difficulty": 1})

如果您在您的应用程序中使用JP-QL,您将能够在不更新查询的情况下切换数据存储。

现在,您已经了解了发生了什么,我们可以开始查询我们持久化的数据。例如,我们可以获取可用徒步旅行的列表

@Test
public void canSearchUsingJPQLQuery() {
    // Get a new entityManager
    EntityManager entityManager = entityManagerFactory.createEntityManager();

    // Start transaction
    entityManager.getTransaction().begin();

    // Find all the available hikes ordered by difficulty
    List<Hike> hikes = entityManager
        .createQuery( "SELECT h FROM Hike h ORDER BY h.difficulty ASC" , Hike.class )
        .getResultList();

    assertThat( hikes.size() ).isEqualTo( 2 );
    assertThat( hikes ).onProperty( "description" ).containsExactly( "Visiting Land's End", "Exploring Carisbrooke Castle" );

    entityManager.getTransaction().commit();
    entityManager.close();
}

如果您以前使用过JPA规范,您会发现这段代码非常熟悉:它就是您在关系型数据库中使用JPA编写的代码。

您可以通过切换Neo4j和MongoDB之间的配置和依赖关系来测试此功能:测试将仍然通过,而代码没有任何更改。

酷的地方在于,您可以使用没有自己的查询引擎的数据存储的JP-QL查询。在这种情况下,Hibernate OGM的查询解析器将创建全文查询,这些查询通过Hibernate Search和Lucene执行。我们稍后将会更详细地了解如何实现。

查询的结果是一个管理实体列表。这意味着对象的更改将自动应用到数据库中的数据。您还可以遍历结果对象图,按需加载延迟关联。

JP-QL语言的支持并不完整,并且可能会根据后端而有所不同。我们将细节留给官方Hibernate OGM文档。目前所支持的是

  • 简单比较
  • IS NULLIS NOT NULL
  • 布尔运算符 ANDORNOT
  • LIKEINBETWEEN
  • ORDER BY

如果JP-QL不适合您的用例,我们将看到如何使用所选后端的本机语言执行查询。

使用本机后端查询语言

有时您可能决定牺牲可移植性以换取底层本机查询语言的力量。例如,您可能想利用Neo4j的Cypher语言运行分层/递归查询的能力。使用MongoDB,让我们获取通过“彭赞斯”的徒步旅行

// Search for the hikes with a section that start from "Penzace" in MongoDB
List<Hike> hikes = entityManager.createNativeQuery("{ $query : { sections : { $elemMatch : { start: 'Penzance' } } } }", Hike.class ).getResultList();

在Neo4j中的相同代码看起来像这样

// Search for the hikes with a section that start from "Penzace" in Neo4j
List<Hike> hikes = entityManager.createNativeQuery( "MATCH (h:Hike) -- (:Hike_sections {start: 'Penzance'} ) RETURN h", 
Hike.class ).getResultList();

重要的是要注意,与JPA查询一样,查询返回的对象是管理实体。

您还可以使用javax.persistence.NamedNativeQuery注解定义查询

@Entity
@NamedNativeQuery(
name = "PenzanceHikes",
query = "{ $query : { sections : { $elemMatch : { start: 'Penzance' } } } }", resultClass = Hike.class )
public class Hike {  }

然后按如下方式执行它

List<Hike> hikes = entityManager.createNamedQuery( "PenzanceHikes" ).getResultList();

使用Hibernate Search查询

Hibernate Search提供了一种将Java对象索引到Lucene索引的方法,并在它们上执行全文查询。索引确实存在于您的数据存储之外。这意味着即使它们没有本机支持,您也可以拥有查询功能。它还在功能集和可扩展性方面提供了一些有趣的属性。特别是,使用Hibernate Search,您可以将查询执行卸载到单独的节点,并独立于实际数据存储节点进行扩展。

在这个例子中,我们将使用MongoDB。首先,您需要将Hibernate Search添加到您的应用程序中。在一个Maven项目中,您需要在pom.xml中添加以下依赖项

<dependencies>
    ...
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-search-orm</artifactId>
    </dependency>
    ...
</dependencies>

现在,您可以选择您想要索引的内容

@Entity
@Indexed
public class Hike {

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

    @Field
    private String description;

    private Date date;
    private BigDecimal difficulty;

    @ManyToOne
    private Person organizer;

    @ElementCollection
    @OrderColumn(name = "sectionNo")
    private List<HikeSection> sections;
       
    // constructors, getters, setters, ...
}

@Indexed注解标识了我们要索引的类,而@Field注解指定了我们要索引的类的哪些属性。每次通过实体管理器使用Hibernate OGM持久化新的Hike实体时,Hibernate Search都会自动将其添加到索引,并跟踪管理实体的更改。这样,索引和数据存储保持同步。

现在您可以使用 Lucene 查询来查找前往 Carisbrooke 的徒步旅行。在这个例子中,我们将使用 Hibernate Search 提供的查询构建器。

@Test
public void canSearchUsingFullTextQuery() {
    EntityManager entityManager = entityManagerFactory.createEntityManager();

    entityManager.getTransaction().begin();

    //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( Hike.class ).get();

    // A Lucene query to search for hikes to the Carisbrooke castle:
    Query lq = b.keyword().onField("description").matching("Carisbrooke castle").createQuery();

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

    //This is a requirement when using Hibernate OGM instead of ORM:
    ftQuery.initializeObjectsWith( ObjectLookupMethod.SKIP, DatabaseRetrievalMethod.FIND_BY_ID );

    // List matching hikes
    List<Hike> hikes = ftQuery.getResultList();
    assertThat( hikes ).onProperty( "description" ).containsOnly( "Exploring Carisbrooke Castle" );

    entityManager.getTransaction().commit();
    entityManager.close();
}

代码的结果将是一个列表,其中提到了“Carisbrooke 城堡”的描述。

Hibernate Search 是一个非常强大的工具,具有许多不同的选项,在此教程中描述所有这些选项会花费太多时间。您可以查看参考文档以了解更多。

总结

这就是现在的全部内容。

如您所见,Hibernate OGM 为您提供了一系列选项来运行对数据存储的查询,这应该涵盖了您的大部分典型查询需求:JP-QL、原生 NoSQL 查询以及通过 Hibernate Search / Apache Lucene 的全文查询。即使您之前从未使用过 NoSQL 数据存储,您也可以轻松地实验。

您可以在 GitHub 上找到这篇博客文章(以及之前的文章)的完整示例代码。只需将其分叉并按您喜欢的样子进行实验。

现在您已经知道了如何存储和查找您的实体,在系列的下一部分中,我们将看到如何将所有内容放入应用程序容器(如 WildFly)中。

我们非常期待您的意见,请随时联系我们或发表评论,我们将回答您的问题并听取您的反馈。


回到顶部