JAVA SQL 查询结果映射之基础篇
通常,JPQL的功能不足以执行我们在现实项目中需要的查询。一般来说,这不是一个问题,因为 JPA 被设计为一个有漏洞的抽象,我们可以通过使用原生查询或调用存储过程来充分利用 SQL 的潜力。
唯一的缺点是,这些查询返回的是 Object[]
的 List
,而不是我们习惯使用的映射实体和值对象。每个 Object[]
都包含一条由数据库返回的记录。然后,我们需要遍历数组,将每个 Object
强制转换为其特定类型,并将它们映射到我们的域模型。这会创建大量重复的代码和类型转换,正如下面的示例中看到的那样。
List<Object[]> results = this.em.createNativeQuery("SELECT a.id, a.firstName, a.lastName, a.version FROM Author a").getResultList();
results.stream().forEach((record) -> {
Long id = ((BigInteger) record[0]).longValue();
String firstName = (String) record[1];
String lastName = (String) record[2];
Integer version = (Integer) record[3];
});
如果我们可以告诉 EntityManager
将查询的结果映射到实体或值对象中,就像 JPQL 语句的情况一样,那会更方便。好消息是,JPA 提供了这一功能。它被称为 SQL 结果集映射,我们将在本系列中详细介绍它。
示例
本文我们只需要一个带有 id、版本号、姓氏、名字的简单 Author
实体。
如何使用默认映射
将查询结果映射到实体最简单的方式是将实体类作为参数提供给 EntityManager
的 createNativeQuery(String sqlString, Class resultClass)
方法,并使用默认映射。下面代码显示了如何使用简单查询完成此操作。在真实的项目中,你可以将其用于存储过程或非常复杂的 SQL 查询。
List<Author> results = this.em.createNativeQuery("SELECT a.id, a.firstName, a.lastName, a.version FROM Author a", Author.class).getResultList();
查询需要返回实体的所有属性,JPA 实现(例如 Hibernate)将尝试根据返回的字段的名称和类型将其映射到实体属性。如果成功,EntityManager
将返回由当前持久化上下文管理的完全初始化的 Author
实体的列表。因此,结果与使用 JPQL 查询的结果相同,但我们并不局限于 JPQL 的小特性集。
如何自定义映射
虽然这种自动映射很有用,而且很容易定义,但它通常是不够的。如果我们执行更复杂的查询或调用存储过程,则返回的列的名称可能与实体定义不匹配。在这些情况下,我们需要定义一个自定义的结果映射。这需要定义所有实体属性的映射,即使默认映射不能仅应用于一个属性。
让我们看看我们的例子,并更改我们之前使用的查询,并将 id
字段重命名为 authorId
:
SELECT a.id as authorId, a.firstName, a.lastName, a.version FROM Author a
到 Author 实体的默认映射将不适用于此查询结果,因为所选字段的名称和实体属性不匹配。我们需要为它定义一个自定义映射。这可以通过注释或映射文件(例如 orm.xml
)来完成。以下代码显示了如何使用 @SqlResultSetMapping
注释定义结果映射。映射由名称和 @EntityResult
定义组成。映射的名称,在本例中为 AuthorMapping
,稍后将用于告诉 EntityManager
要使用哪个映射。@EntityResult
定义了结果应映射到的实体类,以及定义字段名和实体属性之间映射的 @FieldResult
数组。每个 @FieldResult
获取属性的名称和字段名作为参数。
@SqlResultSetMapping(
name = "AuthorMapping",
entities = @EntityResult(
entityClass = Author.class,
fields = {
@FieldResult(name = "id", column = "authorId"),
@FieldResult(name = "firstName", column = "firstName"),
@FieldResult(name = "lastName", column = "lastName"),
@FieldResult(name = "version", column = "version")}))
自 Hibernate 5 和 JPA 2.2 起,@SqlResultMapping
注释是可重复的。因此,如果要在一个实体中定义多个映射,则不再需要将 @SqlResultSetMapping
注释放置在 @SqlResultMappings
注释中。
如果你不喜欢向实体添加大量注释,可以在 XML 映射文件中定义映射。默认的映射文件名为 orm.xml
,如果它被添加到 jar 文件的 META-INF
目录中,它将被自动使用。
如下所示,该映射与我们之前讨论的基于注释的映射非常相似。我将其命名为 AuthorMappingXml
,以避免与基于注释的映射发生名称冲突。在实际的项目中,你不需要担心这一点,因为你通常只使用所描述的两个映射中的一个。
<sql-result-set-mapping name="AuthorMappingXml">
<entity-result entity-class="org.thoughts.on.java.jpa.model.Author">
<field-result name="id" column="authorId"/>
<field-result name="firstName" column="firstName"/>
<field-result name="lastName" column="lastName"/>
<field-result name="version" column="version"/>
</entity-result>
</sql-result-set-mapping>
好了,现在我们定义了查询结果和 Author
实体之间的映射。我们现在可以提供映射的名称,而不是将实体类作为 createNativeQuery(String sqlString, String resultSetMapping)
方法的参数。在下面的代码中,我使用了注释定义的映射。
List<Author> results = this.em.createNativeQuery("SELECT a.id as authorId, a.firstName, a.lastName, a.version FROM Author a", "AuthorMapping").getResultList();
结论
本系列的第一篇文章中,我们了解了将查询结果映射到实体的两种基本方法:
- 如果查询结果的名称和类型与实体属性匹配,我们只需要将实体类提供给
EntityManager
的createNativeQuery(String sqlString, Class resultClass)
方法即可使用默认映射。 - 如果默认映射不能应用于查询结果,我们可以使用 XML 或
@SqlResultSetMapping
注释来定义查询结果的字段与实体属性之间的自定义映射。然后可以将映射的名称提供给createNativeQuery(String sqlString, String resultSetMapping)
方法。
本文中描述的映射非常简单。本系列的后续文章中,我们将了解更复杂的映射,这些映射可以处理多个实体和其他列,或者可以映射到值对象而不是实体。