Hibernate 查询结果映射 Result Set Mapping 处理

这是我的系列中第四篇也是最后一篇关于 SQL 结果映射设置:

在第一篇中,我们看到一些简单的映射定义把查询结果映射到一个实体中. 映射定义在第二篇中增加了复杂度,由于我们映射了查询结果到多个实体类并处理了额外的字段. 然后在第三篇中,我们看到了JPA 2.1 的新特征之一,构造结果映射。

这一次我们来看一些 Hibernate 具体映射特征,它们不属于 JPA 的标准规范. Hibernate 提供了它自己的 API 来映射查询结果. 当使用了后会和 Hibernate 绑定紧密意味着迁移到另一个平台会很困难,此特性同样提供了一些有趣的特性. 像往常一样,你需要决定并权衡你使用后所面临之后的问题。
IMAGE

示例

我们开始之前,让我们看下示例中我们将会使用的实体模型. 如果你读了这个系列的第二部分, 你应该已经比较熟悉 AuthorBook 实体类. 这两个实体类很简单. Author实体类有id,version,first name,与last name. Book实体类有idversion,title与关联到Author. 为了避免不必要的复杂性,每本Book只能被一个Author编写。
IMAGE
我使用 Wildfly 8.2 与 Hibernate 4.3.7 来进行测试示例. 再次重申:根据我们在此文中所使用的特性属于 Hibernate 的特性而不是 JPA 标准,你不能在其他 JPA2.1 实现框架中使用。
你可以从这里获取源码信息:我的 github 账户

如何使用 Hibernate 此特性

在这个系列之前的篇章中,我们使用 JPA 标准特性并使用 EntityManager 来执行 SQL 语句. 这一次我们使用 Hibernate 特性,所以我们需要使用 Hibernate Session 实例来进行操作. 在一个 Java EE 环境中,Session 实例可以采用 EntityManager.getDelegate() 方法来获取,如下面代码片段:

1
2
3
4
5
6
7
8
9
@PersistenceContext
private EntityManager em;

...

public void queryWithAuthorBookCountHibernateMapping() {
Session session = (Session)this.em.getDelegate();
...
}

别名让映射更简单

Hibernate 提供了一组与 JPA 标准类似的 API 接口. 但在前面章节中使用 Hibernate API 有时能够更加方便来实现我们创建结果映射. 下面一个代码片段就是一个例子,从数据库查询的哪些所有的 Books 与 Authors 映射到对应的实体类中. 在真实项目环境中, 也许你不会直接使用 SQL 查询来做一个简单的 select. 但这个可以足够说明了结果映射了. 为了让你更方便查看,我去掉了非常复杂的真实项目中的查询。

1
2
3
4
5
6
7
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.} 来选择他们. 映射别名 a 与 b 到实体类可以通过调用 addEntity(String tableAlias, Class entityType)) 来完成.

下列代码片段显示了一个相似的结果映射. 这一次, 我们选择了一个 Author 实体 并与他/她的数量来作为新统计字段. 我们在系列第二部分使用了相同的查询方式, 主要采用其 JPA 标准的 @SqlResultSetMapping 注解方式进行映射,有兴趣可以点进去查看。

1
2
3
4
5
6
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的注解或XML配置结果映射标准可以被用来映射多查询结果。

更灵活的映射方式 ResultTransformer

另一个更强大的方式是 ResultTransformer 可以更灵活转换查询结果,它提供了我们可以在 Java 代码中进行结果映射. 现在也许你会说这就是我开始就想避免的情况,确实你是对的. 但是你可以看 JavaDoc, Hibernate 提供了大量不同的此接口的实现. 那么在大多数情况下, 我们没有必要自己来实现映射实现, 否则 ResultTransformer 大只可以提供最简单的使用方式. 其中一个好用的实现类就是 AliasToBeanResultTransformer, 它会把结果映射到一个 Java Bean. 但除了使用构造器调用外, 根据我们在 第三方结果映射 中, 转换器使用setter方法或属性来进行填充对象. 这个有好处的, 如果类存在大量的属性我们需要创建一个或多个带参构造器, 因为多查询结果需要被映射到同一个类. 下列代码展示了 AliasToBeanResultTransformer 的使用。

1
2
3
4
5
6
7
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 来进行映射查询结果。

SEO

Result Set Mapping: Hibernate Specific Mappings
SQL result set mappings

原文出处

https://www.thoughts-on-java.org/result-set-mapping-hibernate-specific-mappings/