Un guide de SqlResultSetMapping

1. Introduction

Dans ce guide, nous examinerons SqlResultSetMapping , à partir de l'API Java Persistence (JPA).

La fonctionnalité principale consiste ici à mapper les ensembles de résultats des instructions SQL de base de données vers des objets Java.

2. Configuration

Avant de regarder son utilisation, faisons quelques réglages.

2.1. Dépendance de Maven

Nos dépendances Maven requises sont Hibernate et H2 Database. Hibernate nous donne l'implémentation de la spécification JPA. Nous utilisons la base de données H2 pour une base de données en mémoire.

2.2. Base de données

Ensuite, nous allons créer deux tables comme indiqué ici:

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

La table EMPLOYEE stocke un objet Entity de résultat . SCHEDULE_DAYS contient des enregistrements liés à la table EMPLOYEE par la colonne employeeId:

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

Un script pour la création de données se trouve dans le code de ce guide.

2.3. Objets d'entité

Nos objets Entity doivent se ressembler:

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

Les objets d' entité peuvent être nommés différemment des tables de base de données. Nous pouvons annoter la classe avec @ Table pour les mapper explicitement:

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

3. Cartographie scalaire

Maintenant que nous avons des données, nous pouvons commencer à mapper les résultats de la requête.

3.1. ColonneRésultat

Alors que les annotations SqlResultSetMapping et Query fonctionnent également sur les classes Repository , nous utilisons les annotations sur une classe Entity dans cet exemple.

Chaque annotation SqlResultSetMapping ne nécessite qu'une seule propriété, nom. Cependant, sans l'un des types de membres, rien ne sera mappé. Les types de membres sont ColumnResult , ConstructorResult et EntityResult .

Dans ce cas, ColumnResult mappe n'importe quelle colonne à un type de résultat scalaire:

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

Le nom de la propriété ColumnResult identifie la colonne dans notre requête:

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

Notez que la valeur de resultSetMapping dans notre annotation NamedNativeQuery est importante car elle correspond à la propriété name de notre déclaration ResultSetMapping .

Par conséquent, le jeu de résultats NamedNativeQuery est mappé comme prévu. De même, l' API StoredProcedure nécessite cette association.

3.2. Test de la colonne

Nous aurons besoin d'objets spécifiques à Hibernate pour exécuter notre code:

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

Enfin, nous appelons la requête nommée pour exécuter notre test:

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

4. Cartographie des constructeurs

Jetons un coup d'œil au moment où nous devons mapper un jeu de résultats à un objet entier.

4.1. ConstructeurRésultat

De la même manière que notre exemple ColumnResult , nous ajouterons l' annotation SqlResultMapping sur notre classe Entity , ScheduledDay . Cependant, pour mapper à l'aide d'un constructeur, nous devons en créer un:

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

En outre, le mappage spécifie la classe cible et les colonnes (toutes deux obligatoires):

@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'ordre des ColumnResults est très important. Si les colonnes sont dans le désordre, le constructeur ne pourra pas être identifié. Dans notre exemple, l'ordre correspond aux colonnes de la table, il ne serait donc pas nécessaire.

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

Une autre différence unique pour ConstructorResult est que l'instanciation d'objet résultante est «nouvelle» ou «détachée». L' entité mappée sera dans l'état détaché lorsqu'une clé primaire correspondante existe dans EntityManager, sinon elle sera nouvelle.

Parfois, nous pouvons rencontrer des erreurs d'exécution en raison de la non-correspondance des types de données SQL avec les types de données Java. Par conséquent, nous pouvons le déclarer explicitement avec type.

4.2. ConstructorResult Test

Testons le ConstructorResult dans un test unitaire:

@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. Mappage d'entités

Enfin, pour un mappage d'entité simple avec moins de code, jetons un œil à EntityResult .

5.1. Entité unique

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.

Pour cette raison, nous définissons la requête dans le 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. Conclusion

Dans ce guide, nous avons examiné différentes options d'utilisation de l' annotation SqlResultSetMapping . SqlResultSetMapping est un élément clé de l'API Java Persistence.

Des extraits de code sont disponibles à l'adresse over sur GitHub.