编程

JAVA SQL 查询结果映射之基础篇

189 2024-07-16 05:07:00

通常,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 实体。

如何使用默认映射

将查询结果映射到实体最简单的方式是将实体类作为参数提供给 EntityManagercreateNativeQuery(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();

结论

本系列的第一篇文章中,我们了解了将查询结果映射到实体的两种基本方法:

  1. 如果查询结果的名称和类型与实体属性匹配,我们只需要将实体类提供给 EntityManagercreateNativeQuery(String sqlString, Class resultClass) 方法即可使用默认映射。
  2. 如果默认映射不能应用于查询结果,我们可以使用 XML 或 @SqlResultSetMapping 注释来定义查询结果的字段与实体属性之间的自定义映射。然后可以将映射的名称提供给 createNativeQuery(String sqlString, String resultSetMapping) 方法。

本文中描述的映射非常简单。本系列的后续文章中,我们将了解更复杂的映射,这些映射可以处理多个实体和其他列,或者可以映射到值对象而不是实体。