编程

JAVA SQL 查询结果映射之 Hibernate 特有的映射

174 2024-07-21 02:39:00

这是 SQL 查询结果集映射系列的第四篇,也是最后最后一篇。

在第一篇文章中,我们了解了一些将查询结果映射到实体的基本映射定义。在第二篇文章中,映射定义变得更加复杂,因为我们将查询结果映射到多个实体并处理额外的字段。在第三篇文中,我们了解了 JPA 2.1 中引入的一个新特性,即构造函数-结果映射。

而本文,我们来看看一些 Hibernate 特有的特性,它们不是 JPA 规范的一部分。Hibernate 提供了自己的 API 来映射查询结果。虽然这会造成供应商锁定并使迁移到另一个框架变得困难,但它也提供了一些有趣的功能。你需要自己权衡取舍。

示例

在开始之前,让我们看看将用于示例的实体模型。如果你阅读了本系列的第二篇文章,那么你已经熟悉了 AuthorBook实体。这两个实体都非常简单。Author实体有一个 id、一个版本、一个名字和一个姓氏。Book 实体有一个 id、一个版本、一个标题和对作者的引用。为了避免不必要的复杂性,每本书都只有一位作者。

如何使用 Hibernate 特有的功能

在本系列的前几篇文章中,我们使用了 JPA 标准功能,因此使用了 EntityManager 来执行原生查询。这一次我们将使用一些特定于 Hibernate 的功能,因此我们需要使用 Hibernate 会话实例。在 Java EE 环境中,可以通过 EntityManager.getDelegate() 方法访问,如以下代码所示:

@PersistenceContext

private EntityManager em;

...

public void queryWithAuthorBookCountHibernateMapping() {

  Session session = (Session)this.em.getDelegate();

  ...

}

别名使得映射更简单

Hibernate 提供了自己的 API,它支持与 JPA 标准类似的一组功能。但是使用 Hibernate API 有时更方便,因为我们在前面的文章中创建了结果映射。一个例子是下面的代码,其中从数据库中选择所有书籍(Book)和作者(Author),并将其映射到相应的实体。在现实世界的项目中,你可能不会使用原生查询来进行如此简单的选择。但这足以解释结果映射。而真实项目中非常复杂的查询则留给你和你的 DBA ;-) 。

List<Object[]> results = ((Session)this.em.getDelegate()).createSQLQuery("SELECT {b.*}, {a.*} FROM Book b JOIN Author a ON b.author_id = a.id").addEntity("b", Book.class).addEntity("a", Author.class).list();

results.stream().forEach((record) -> {

    Book book = (Book) record[0];

    Author author = (Author) record[1];

    System.out.println("Author: ID [" + author.getId() + "] firstName [" + author.getFirstName() + "] lastName [" + author.getLastName() + "]");

    System.out.println("Book: ID [" + book.getId() + "] title[" + book.getTitle() + "]");

});

该查询的语法一开始可能看起来很奇怪,但它提供了一种非常简单的方法来选择实体的所有属性。现在我们使用 {a.*}{b.*} 来检索它们;而不是像在本系列的第 2 部分中所做的那样,在查询的检索(Select)部分检索所有属性并将它们一一映射到实体属性。别名 a 和 b 到实体类之间的映射是通过调用 addEntity(String tableAlias, Class entityType) 来完成的。

下面的代码显示了类似的结果映射。这一次,我们检索一个 Author 实体和她/他的书的数量作为标量值。我们在本系列的第二篇中使用了相同的查询,当时我们使用 JPA 标准的 @SqlResultSetMapping 注释来映射结果。

List<Object[]> results = ((Session)this.em.getDelegate()).createSQLQuery("SELECT {a.*}, count(b.id) as bookCount FROM Book b JOIN Author a ON b.author_id = a.id GROUP BY a.id, a.firstName, a.lastName, a.version").addEntity(Author.class).addScalar("bookCount", StandardBasicTypes.LONG).list();

results.stream().forEach((record) -> {

    Author author = (Author) record[0];

    Long bookCount = (Long) record[1];

    System.out.println("Author: ID [" + author.getId() + "] firstName [" + author.getFirstName() + "] lastName [" + author.getLastName() + "] number of books [" + bookCount + "]");

});

目前为止,我们创建了两个结果映射,这些映射也可以使用 JPA 完成。从我的角度来看,如果结果映射是针对一个查询的,那么 Hibernate API 更容易使用。但是,如果没有其他原因创建对 Hibernate 而不是 JPA 的依赖,我仍然会使用 JPA。此外,JPA 标准的结果映射注释(或 XML 配置)可以用于映射多个查询的结果。.

ResultTransformer 提供更大的灵活性

另一种更强大的转换查询结果的方法是 ResultTransformer。它为在 Java 代码中定义结果映射提供了选项。好吧,你可能会说这是我们一开始试图避免的,是的。但是,正如你在 JavaDoc 中看到的,Hibernate 提供了该接口的不同实现的列表。所以在大多数情况下,没有必要自己实现映射。否则,与使用 Streams API 的编程映射相比,ResultTransformer 只提供了最小的好处。提供的 ResultTransformer 其中之一是 AliasToBeanResultTransformer,它将查询结果映射到 Java Bean。但是,转换器(transformer )没有像我们在第三篇那样使用构造函数调用,而是使用 setter 方法或字段来填充对象。如果类有很多字段,并且我们需要为每个字段创建一个带有参数的构造函数,或者因为需要将多个查询结果映射到同一个类而需要多个构造函数,那么这可能是有益的。以下代码显示了 AliasToBeanResultTransformer 的示例:

List<BookValue> results = ((Session)this.em.getDelegate()).createSQLQuery("SELECT b.id, b.title, b.version, a.firstName || ' ' || a.lastName as authorName FROM Book b JOIN Author a ON b.author_id = a.id")

    .addScalar("id", StandardBasicTypes.LONG).addScalar("title").addScalar("version", StandardBasicTypes.LONG).addScalar("authorName")

    .setResultTransformer(new AliasToBeanResultTransformer(BookValue.class)).list();

results.stream().forEach((book) -> {

    System.out.println("Book: ID [" + book.getId() + "] title [" + book.getTitle() + "] authorName [" + book.getAuthorName() + "]");

});

AliasToBeanResultTransformer 使用 BookValue 的默认构造函数来实例化对象,并根据返回字段的别名和类型搜索getter 方法。因此,我们需要使用 addScalar() 方法来重命名字段,并更改 idversion 字段的类型。

结论

这是结果映射系列的最后一篇文章。在前几篇文章中,我们了解了 JPA 标准中定义结果映射的不同选项后,现在我们了解了一些 Hibernate 特定的功能。Hibernate 提供了一个 API,并支持别名来定义查询结果与 Java 实体或值对象之间的映射。除了更容易使用之外,这还提供了一个优点,即所有信息都在同一个位置。不需要在某些注释或 XML 文件中搜索映射定义。另一方面,它需要更多的工作来定义映射,而且它不像 JPA 标准方法那样易于重用。

另一方面,与标准映射相比,ResultTransformer 可以提供一些真正的好处。这些可以用于执行更复杂的映射,Hibernate 已经提供了 ResultTransformer 实现的列表。如果现有的转换实现都不提供所需的功能,那么也可以选择实现自己的功能。但在这种情况下,我更愿意使用 Streams API 来映射业务代码中的查询结果。