A Guide to SqlResultSetMapping – SqlResultSetMapping指南

最后修改: 2018年 7月 23日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

In this guide, we’ll take a look at SqlResultSetMapping, out of the Java Persistence API (JPA).

在本指南中,我们将看一下SqlResultSetMapping,出自Java Persistence API(JPA)。

The core functionality here involves mapping result sets from database SQL statements into Java objects.

这里的核心功能涉及将数据库SQL语句的结果集映射到Java对象。

2. Setup

2.设置

Before we look at its usage, let’s do some setup.

在我们看它的用法之前,让我们做一些设置。

2.1. Maven Dependency

2.1.Maven的依赖性

Our required Maven dependencies are Hibernate and H2 Database. Hibernate gives us the implementation of the JPA specification.  We use H2 Database for an in-memory database.

我们需要依赖的Maven是Hibernate和H2数据库。Hibernate为我们提供了JPA规范的实现。 我们使用H2数据库作为内存数据库。

2.2. Database

2.2. 数据库

Next, we’ll create two tables as seen here:

接下来,我们将创建两个表,如图所示。

CREATE TABLE EMPLOYEE
(id BIGINT,
 name VARCHAR(10));

The EMPLOYEE table stores one result Entity object. SCHEDULE_DAYS contains records linked to the EMPLOYEE table by the column employeeId:

EMPLOYEE表存储一个结果Entity对象。SCHEDULE_DAYS包含通过列employeeId:链接到EMPLOYEE表的记录。

CREATE TABLE SCHEDULE_DAYS
(id IDENTITY,
 employeeId BIGINT,
 dayOfWeek  VARCHAR(10));

A script for data creation can be found in the code for this guide.

本指南的代码中可以找到一个用于创建数据的脚本。

2.3. Entity Objects

2.3.实体对象

Our Entity objects should look similar:

我们的实体对象应该看起来类似。

@Entity
public class Employee {
    @Id
    private Long id;
    private String name;
}

Entity objects might be named differently than database tables. We can annotate the class with @Table to explicitly map them:

实体对象的命名方式可能与数据库表不同。我们可以用@Table来注解该类,以便明确地映射它们。

@Entity
@Table(name = "SCHEDULE_DAYS")
public class ScheduledDay {

    @Id
    @GeneratedValue
    private Long id;
    private Long employeeId;
    private String dayOfWeek;
}

3. Scalar Mapping

3.标量映射

Now that we have data we can start mapping query results.

现在我们有了数据,我们可以开始对查询结果进行映射。

3.1. ColumnResult

3.1. ColumnResult.

While SqlResultSetMapping and Query annotations work on Repository classes as well, we use the annotations on an Entity class in this example.

虽然SqlResultSetMappingQuery注解也适用于Repository类,但在本例中我们在Entity类上使用注解。

Every SqlResultSetMapping annotation requires only one property, name. However, without one of the member types, nothing will be mapped. The member types are ColumnResult, ConstructorResult, and EntityResult.

每个SqlResultSetMapping注解只需要一个属性,name。但是,如果没有一个成员类型,就不会有任何东西被映射。这些成员类型是 ColumnResult, ConstructorResult, 和 EntityResult

In this case, ColumnResult maps any column to a scalar result type:

在这种情况下, ColumnResult 将任何列映射到一个标量结果类型。

@SqlResultSetMapping(
  name="FridayEmployeeResult",
  columns={@ColumnResult(name="employeeId")})

The ColumnResult property name identifies the column in our query:

ColumnResult属性name标识了我们查询中的列。

@NamedNativeQuery(
  name = "FridayEmployees",
  query = "SELECT employeeId FROM schedule_days WHERE dayOfWeek = 'FRIDAY'",
  resultSetMapping = "FridayEmployeeResult")

Note that the value of resultSetMapping in our NamedNativeQuery annotation is important because it matches the name property from our ResultSetMapping declaration.

请注意,我们的NamedNativeQuery注解中的resultSetMapping的值很重要,因为它与我们的ResultSetMapping声明中的name属性匹配。

As a result, the NamedNativeQuery result set is mapped as expected. Likewise, StoredProcedure API requires this association.

因此,NamedNativeQuery结果集按照预期被映射。同样地,StoredProcedureAPI也需要这种关联。

3.2. ColumnResult Test

3.2.ColumnResult测试

We’ll need some Hibernate specific objects to run our code:

我们需要一些Hibernate特定对象来运行我们的代码。

@BeforeAll
public static void setup() {
    emFactory = Persistence.createEntityManagerFactory("java-jpa-scheduled-day");
    em = emFactory.createEntityManager();
}

Finally, we call the named query to run our test:

最后,我们调用命名的查询来运行我们的测试。

@Test
public void whenNamedQuery_thenColumnResult() {
    List<Long> employeeIds = em.createNamedQuery("FridayEmployees").getResultList();
    assertEquals(2, employeeIds.size());
}

4. Constructor Mapping

4.构造函数映射

Let’s take a look at when we need to map a result set to an entire object.

让我们看一下,当我们需要将一个结果集映射到整个对象时。

4.1. ConstructorResult

4.1. ConstructorResult

Similarly to our ColumnResult example, we will add the SqlResultMapping annotation on our Entity class, ScheduledDay. However, in order to map using a constructor, we need to create one:

与我们的ColumnResult例子类似,我们将在我们的EntityScheduledDay上添加SqlResultMapping注释。然而,为了使用构造函数进行映射,我们需要创建一个。

public ScheduledDay (
  Long id, Long employeeId, 
  Integer hourIn, Integer hourOut, 
  String dayofWeek) {
    this.id = id;
    this.employeeId = employeeId;
    this.dayOfWeek = dayofWeek;
}

Also, the mapping specifies the target class and columns (both required):

此外,映射还指定了目标类和列(都是必须的)。

@SqlResultSetMapping(
    name="ScheduleResult",
    classes={
      @ConstructorResult(
        targetClass=com.baeldung.sqlresultsetmapping.ScheduledDay.class,
        columns={
          @ColumnResult(name="id", type=Long.class),
          @ColumnResult(name="employeeId", type=Long.class),
          @ColumnResult(name="dayOfWeek")})})

The order of the ColumnResults is very important. If columns are out of order the constructor will fail to be identified. In our example, the ordering matches the table columns, so it would actually not be required.

ColumnResults的顺序非常重要。如果列的顺序不对,构造函数将无法被识别。在我们的例子中,排序与表的列相匹配,所以实际上不需要这样做。

@NamedNativeQuery(name = "Schedules",
  query = "SELECT * FROM schedule_days WHERE employeeId = 8",
  resultSetMapping = "ScheduleResult")

Another unique difference for ConstructorResult is that the resulting object instantiation as “new” or “detached”.  The mapped Entity will be in the detached state when a matching primary key exists in the EntityManager otherwise it will be new.

ConstructorResult的另一个独特区别是,所产生的对象实例化为 “新 “或 “分离”。 当EntityManager中存在匹配的主键时,映射的Entity将处于分离状态,否则它将是新的。

Sometimes we may encounter runtime errors because of mismatching SQL datatypes to Java datatypes. Therefore, we can explicitly declare it with type.

有时我们可能会遇到运行时的错误,因为SQL数据类型与Java数据类型不匹配。因此,我们可以用type.明确地声明它。

4.2. ConstructorResult Test

4.2.ConstructorResult测试

Let’s test the ConstructorResult in a unit test:

让我们在单元测试中测试一下ConstructorResult

@Test
public void whenNamedQuery_thenConstructorResult() {
  List<ScheduledDay> scheduleDays
    = Collections.checkedList(
      em.createNamedQuery("Schedules", ScheduledDay.class).getResultList(), ScheduledDay.class);
    assertEquals(3, scheduleDays.size());
    assertTrue(scheduleDays.stream().allMatch(c -> c.getEmployeeId().longValue() == 3));
}

5. Entity Mapping

5.实体映射

Finally, for a simple entity mapping with less code, let’s have a look at EntityResult.

最后,对于一个代码较少的简单实体映射,让我们看看EntityResult

5.1. Single Entity

5.1.单一实体

EntityResult requires us to specify the entity class, Employee. We use the optional fields property for more control. Combined with FieldResult, we can map aliases and fields that do not match:

EntityResult要求我们指定实体类,Employee。我们使用可选的fields属性来进行更多的控制。结合FieldResult,我们可以映射别名和不匹配的字段。

@SqlResultSetMapping(
  name="EmployeeResult",
  entities={
    @EntityResult(
      entityClass = com.baeldung.sqlresultsetmapping.Employee.class,
        fields={
          @FieldResult(name="id",column="employeeNumber"),
          @FieldResult(name="name", column="name")})})

Now our query should include the aliased column:

现在我们的查询应该包括别名的列。

@NamedNativeQuery(
  name="Employees",
  query="SELECT id as employeeNumber, name FROM EMPLOYEE",
  resultSetMapping = "EmployeeResult")

Similarly to ConstructorResult, EntityResult requires a constructor. However, a default one works here.

ConstructorResult类似,EntityResult需要一个构造函数。然而,一个默认的构造函数在这里也可以使用。

5.2. Multiple Entities

5.2.多个实体

Mapping multiple entities is pretty straightforward once we have mapped a single Entity:

一旦我们对单个实体进行了映射,对多个实体的映射就非常简单了。

@SqlResultSetMapping(
  name = "EmployeeScheduleResults",
  entities = {
    @EntityResult(entityClass = com.baeldung.sqlresultsetmapping.Employee.class),
    @EntityResult(entityClass = com.baeldung.sqlresultsetmapping.ScheduledDay.class)

5.3. EntityResult Tests

5.3.EntityResult测试

Let’s have a look at EntityResult in action:

让我们看看EntityResult的运作。

@Test
public void whenNamedQuery_thenSingleEntityResult() {
    List<Employee> employees = Collections.checkedList(
      em.createNamedQuery("Employees").getResultList(), Employee.class);
    assertEquals(3, employees.size());
    assertTrue(employees.stream().allMatch(c -> c.getClass() == Employee.class));
}

Since the multiple entity results join two entities, the query annotation on only one of the classes is confusing.

由于多个实体的结果连接了两个实体,所以只对其中一个类的查询注释是混乱的。

For that reason, we define the query in the test:

出于这个原因,我们在测试中定义查询。

@Test
public void whenNamedQuery_thenMultipleEntityResult() {
    Query query = em.createNativeQuery(
      "SELECT e.id, e.name, d.id, d.employeeId, d.dayOfWeek "
        + " FROM employee e, schedule_days d "
        + " WHERE e.id = d.employeeId", "EmployeeScheduleResults");
    
    List<Object[]> results = query.getResultList();
    assertEquals(4, results.size());
    assertTrue(results.get(0).length == 2);

    Employee emp = (Employee) results.get(1)[0];
    ScheduledDay day = (ScheduledDay) results.get(1)[1];

    assertTrue(day.getEmployeeId() == emp.getId());
}

6. Conclusion

6.结论

In this guide, we looked at different options for using the SqlResultSetMapping annotationSqlResultSetMapping is a key part to the Java Persistence API.

在本指南中,我们研究了使用SqlResultSetMappingannotation的不同选项。SqlResultSetMapping是Java Persistence API的一个关键部分。

Code snippets can be found over on GitHub.

代码片段可以在GitHub上找到over