Ein Leitfaden für SqlResultSetMapping

Eine Anleitung zu SqlResultSetMapping

1. Einführung

In diesem Handbuch werfen wir einen Blick aufSqlResultSetMapping aus der Java Persistence API (JPA).

Die Hauptfunktionalität besteht darin, Ergebnismengen aus SQL-Anweisungen der Datenbank in Java-Objekte abzubilden.

2. Konfiguration

Bevor wir uns die Verwendung ansehen, nehmen wir einige Einstellungen vor.

2.1. Maven-Abhängigkeit

Unsere erforderlichen Maven-Abhängigkeiten sind Hibernate und H2 Database. Hibernate gibt uns die Implementierung der JPA-Spezifikation. Wir verwendenH2 Database für eine In-Memory-Datenbank.

2.2. Datenbank

Als Nächstes erstellen wir zwei Tabellen, wie hier dargestellt:

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

Der stabileEMPLOYEE peichert ein ErgebnisEntity Objekt. SCHEDULE_DAYS enthält Datensätze, die durch die SpalteemployeeId: mit der TabelleEMPLOYEEverknüpft sind

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

Ein Skript zur Datenerstellung finden Sie inthe code for this guide.

2.3. Entitätsobjekte

UnsereEntity -Objekte sollten ähnlich aussehen:

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

Entity -Objekte können anders benannt sein als Datenbanktabellen. Wir können die Klasse mit @Table kommentieren, um sie explizit zuzuordnen:

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

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

3. Skalarkartierung

Jetzt, da wir Daten haben, können wir mit der Zuordnung der Abfrageergebnisse beginnen.

3.1. ColumnResult

Während die Annotationen vonSqlResultSetMapping undQueryauch für die Klassen vonRepositoryfunktionieren, verwenden wir in diesem Beispiel die Annotationen für eineEntity -Skala.

Für jede Annotation vonSqlResultSetMappingist nur eine Eigenschaft erforderlich,name. . Ohne einen der Elementtypen wird jedoch nichts zugeordnet. Die Elementtypen sindColumnResult,ConstructorResult undEntityResult.

In diesem Fall verknüpft ColumnResult jede Spalte mit einem skalaren Ergebnistyp:

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

DieColumnResult propertyname identifiziert die Spalte in unserer Abfrage:

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

Beachten Sie, dassthe value of resultSetMapping in unsererNamedNativeQuery-Annotationis important because it matches the name property from our ResultSetMapping declaration. enthalten ist

Infolgedessen wird die Ergebnismenge vonNamedNativeQuerywie erwartet zugeordnet. Ebenso erfordert die API vonStoredProcedurediese Zuordnung.

3.2. ColumnResult Test

Wir benötigen einige Hibernate-spezifische Objekte, um unseren Code auszuführen:

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

Schließlich rufen wir die genannte Abfrage auf, um unseren Test auszuführen:

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

4. Konstruktorzuordnung

Schauen wir uns an, wann wir eine Ergebnismenge einem gesamten Objekt zuordnen müssen.

4.1. ConstructorResult

Ähnlich wie in unserem BeispielColumnResultfügen wir die AnnotationSqlResultMapping zu unserer KlasseEntityScheduledDay hinzu. Um jedoch mit einem Konstruktor abzubilden, müssen wir einen erstellen:

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

Das Mapping gibt auch die Zielklasse und die Spalten an (beide erforderlich):

@SqlResultSetMapping(
    name="ScheduleResult",
    classes={
      @ConstructorResult(
        targetClass=com.example.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. Wenn die Spalten nicht in der richtigen Reihenfolge sind, kann der Konstruktor nicht identifiziert werden. In unserem Beispiel stimmt die Reihenfolge mit den Tabellenspalten überein, sodass dies eigentlich nicht erforderlich wäre.

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

Ein weiterer eindeutiger Unterschied fürConstructorResult besteht darin, dass die resultierende Objektinstanziierung als "neu" oder "getrennt" erfolgt. Die zugeordnetenEntity befinden sich im getrennten Zustand, wenn in denEntityManager ein übereinstimmender Primärschlüssel vorhanden ist, andernfalls ist er neu.

Manchmal können Laufzeitfehler auftreten, weil SQL-Datentypen nicht mit Java-Datentypen übereinstimmen. Daher können wir es explizit mittype. deklarieren

4.2. ConstructorResult Test

Testen wir dieConstructorResult in einem Unit-Test:

@Test
public void whenNamedQuery_thenConstructorResult() {
  List 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. Mandantenzuordnung

Schauen wir uns für eine einfache Entitätszuordnung mit weniger CodeEntityResult an.

5.1. Einzelne Einheit

EntityResult erfordert die Angabe der EntitätsklasseEmployee. Wir verwenden die optionalefields -Eigenschaft für mehr Kontrolle. In Kombination mitFieldResult, können Aliase und Felder zugeordnet werden, die nicht übereinstimmen:

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

Jetzt sollte unsere Abfrage die Alias-Spalte enthalten:

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

Ähnlich wieConstructorResult benötigtEntityResult einen Konstruktor. Hier funktioniert jedoch eine Standardeinstellung.

5.2. Mehrere Entitäten

Das Zuordnen mehrerer Entitäten ist ziemlich einfach, sobald wir eine einzelne Entität zugeordnet haben:

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

5.3. EntityResult Tests

Schauen wir unsEntityResult in Aktion an:

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

Da die Ergebnisse mehrerer Entitäten zwei Entitäten verbinden, ist die Abfragekommentierung nur für eine der Klassen verwirrend.

Aus diesem Grund definieren wir die Abfrage im 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 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. Fazit

In diesem Handbuch haben wir uns verschiedene Optionen für die Verwendung der AnnotationSqlResultSetMapping. SqlResultSetMapping angesehen, die ein Schlüsselelement der Java-Persistenz-API ist.

Code-Schnipsel könnenover on GitHub gefunden werden.