Una guida a SqlResultSetMapping

1. Introduzione

In questa guida, daremo un'occhiata a SqlResultSetMapping , fuori dall'API Java Persistence (JPA).

La funzionalità principale in questo caso implica la mappatura dei set di risultati dalle istruzioni SQL del database in oggetti Java.

2. Configurazione

Prima di esaminare il suo utilizzo, eseguiamo alcune impostazioni.

2.1. Dipendenza da Maven

Le nostre dipendenze Maven richieste sono Hibernate e H2 Database. Hibernate ci fornisce l'implementazione della specifica JPA. Usiamo H2 Database per un database in memoria.

2.2. Banca dati

Successivamente, creeremo due tabelle come mostrato qui:

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

La tabella EMPLOYEE memorizza un oggetto Entity risultato . SCHEDULE_DAYS contiene i record collegati alla tabella EMPLOYEE dalla colonna EmployeeId:

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

Uno script per la creazione dei dati può essere trovato nel codice di questa guida.

2.3. Oggetti entità

I nostri oggetti Entity dovrebbero essere simili:

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

Gli oggetti entità potrebbero essere denominati in modo diverso rispetto alle tabelle del database. Possiamo annotare la classe con @ Table per mapparli esplicitamente:

@Entity @Table(name = "SCHEDULE_DAYS") public class ScheduledDay { @Id @GeneratedValue private Long id; private Long employeeId; private String dayOfWeek; }

3. Mappatura scalare

Ora che abbiamo i dati possiamo iniziare a mappare i risultati della query.

3.1. ColumnResult

Mentre le annotazioni SqlResultSetMapping e Query funzionano anche sulle classi Repository , in questo esempio usiamo le annotazioni su una classe Entity .

Ogni annotazione SqlResultSetMapping richiede solo una proprietà, nome. Tuttavia, senza uno dei tipi di membro, non verrà mappato nulla. I tipi di membro sono ColumnResult , ConstructorResult e EntityResult .

In questo caso, ColumnResult mappa qualsiasi colonna a un tipo di risultato scalare:

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

Il nome della proprietà ColumnResult identifica la colonna nella nostra query:

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

Si noti che il valore di resultSetMapping nella nostra annotazione NamedNativeQuery è importante perché corrisponde alla proprietà name dalla nostra dichiarazione ResultSetMapping .

Di conseguenza, il set di risultati NamedNativeQuery viene mappato come previsto. Allo stesso modo, l' API StoredProcedure richiede questa associazione.

3.2. ColumnResult Test

Avremo bisogno di alcuni oggetti specifici di Hibernate per eseguire il nostro codice:

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

Infine, chiamiamo la query denominata per eseguire il nostro test:

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

4. Constructor Mapping

Diamo un'occhiata a quando è necessario mappare un set di risultati su un intero oggetto.

4.1. ConstructorResult

Analogamente al nostro esempio ColumnResult , aggiungeremo l' annotazione SqlResultMapping alla nostra classe Entity , ScheduledDay . Tuttavia, per mappare utilizzando un costruttore, dobbiamo crearne uno:

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

Inoltre, la mappatura specifica la classe di destinazione e le colonne (entrambe obbligatorie):

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

L'ordine di ColumnResults è molto importante. Se le colonne sono fuori uso, il costruttore non verrà identificato. Nel nostro esempio, l'ordinamento corrisponde alle colonne della tabella, quindi in realtà non sarebbe richiesto.

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

Un'altra differenza unica per ConstructorResult è che l'istanza dell'oggetto risultante come "nuovo" o "staccato". L' entità mappata sarà nello stato scollegato quando una chiave primaria corrispondente esiste in EntityManager, altrimenti sarà nuova.

A volte possiamo riscontrare errori di runtime a causa della mancata corrispondenza dei tipi di dati SQL con i tipi di dati Java. Pertanto, possiamo dichiararlo esplicitamente con type.

4.2. ConstructorResult Test

Testiamo ConstructorResult in uno 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. Mappatura delle entità

Infine, per un semplice mapping di entità con meno codice, diamo un'occhiata a EntityResult .

5.1. Singola entità

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.

Per questo motivo, definiamo la query nel 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. Conclusione

In questa guida, abbiamo esaminato diverse opzioni per l' utilizzo dell'annotazione SqlResultSetMapping . SqlResultSetMapping è una parte fondamentale dell'API Java Persistence.

Gli snippet di codice possono essere trovati su GitHub.