Eine Anleitung zu SqlResultSetMapping

1. Einleitung

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

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

2. Setup

Bevor wir uns die Verwendung ansehen, lassen Sie uns einige Einstellungen vornehmen.

2.1. Maven-Abhängigkeit

Unsere erforderlichen Maven-Abhängigkeiten sind Hibernate und H2 Database. Im Ruhezustand erhalten wir die Implementierung der JPA-Spezifikation. Wir verwenden die H2-Datenbank 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));

Das EMPLOYEE Tabelle speichert ein Ergebnis Entity - Objekt. SCHEDULE_DAYS enthält verknüpften Datensätze an die EMPLOYEE Tabelle durch die Spalte employeeId:

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

Ein Skript zur Datenerstellung finden Sie im Code für dieses Handbuch.

2.3. Entitätsobjekte

Unsere Entity- Objekte sollten ähnlich aussehen:

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

Entitätsobjekte 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. Skalare Zuordnung

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

3.1. ColumnResult

Während SqlResultSetMapping- und Query- Annotationen auch für Repository- Klassen funktionieren , verwenden wir in diesem Beispiel die Annotationen für eine Entity- Klasse.

Jede SqlResultSetMapping- Annotation erfordert nur eine Eigenschaft, den Namen. Ohne einen der Elementtypen wird jedoch nichts zugeordnet. Die Elementtypen sind ColumnResult , ConstructorResult und EntityResult .

In diesem Fall ordnet ColumnResult jede Spalte einem skalaren Ergebnistyp zu:

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

Die ColumnResult Eigenschaft Name identifiziert die Spalte in unserer Abfrage:

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

Beachten Sie, dass der Wert von resultSetMapping in unserer Annotation NamedNativeQuery wichtig ist, da er mit der Eigenschaft name aus unserer ResultSetMapping- Deklaration übereinstimmt .

Infolgedessen wird die NamedNativeQuery- Ergebnismenge wie erwartet zugeordnet. Ebenso erfordert die StoredProcedure- API diese 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 benannte Abfrage auf, um unseren Test auszuführen:

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

4. Konstruktorzuordnung

Lassen Sie uns einen Blick darauf werfen, wann wir eine Ergebnismenge einem gesamten Objekt zuordnen müssen.

4.1. ConstructorResult

Ähnlich wie in unserem ColumnResult- Beispiel fügen wir die Annotation SqlResultMapping zu unserer Entity- Klasse ScheduledDay 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.baeldung.sqlresultsetmapping.ScheduledDay.class, columns={ @ColumnResult(name="id", type=Long.class), @ColumnResult(name="employeeId", type=Long.class), @ColumnResult(name="dayOfWeek")})})

Die Reihenfolge der ColumnResults ist sehr wichtig. 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 sie tatsächlich nicht erforderlich wäre.

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

Ein weiterer einzigartiger Unterschied für ConstructorResult besteht darin, dass die resultierende Objektinstanziierung als "neu" oder "getrennt" erfolgt. Die zugeordnete Entität befindet sich im getrennten Zustand, wenn im EntityManager 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 mit type deklarieren .

4.2. ConstructorResult Test

Testen wir das ConstructorResult 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. Entity Mapping

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

5.1. Einzelperson

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:

@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.

5.2. Multiple Entities

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

Let's have a look at EntityResult in action:

@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)); }

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

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 Annotation SqlResultSetMapping angesehen . SqlResultSetMapping ist ein wichtiger Bestandteil der Java Persistence API.

Code-Schnipsel finden Sie auf GitHub.