Un guide pour SqlResultSetMapping

Un guide pour SqlResultSetMapping

1. introduction

Dans ce guide, nous allons examinerSqlResultSetMapping, hors de l'API Java Persistence (JPA).

La fonctionnalité principale consiste ici à mapper des ensembles de résultats à partir d'instructions SQL de base de données dans des objets Java.

2. Installer

Avant d'examiner son utilisation, procédons à la configuration.

2.1. Dépendance Maven

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

2.2. Base de données

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

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

Le stableEMPLOYEE tocke un objet résultatEntity. SCHEDULE_DAYS ne contient pas les enregistrements liés à la tableEMPLOYEE par la colonneemployeeId:

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

Un script de création de données se trouve dansthe code for this guide.

2.3. Objets d'entité

Nos sobjectsEntity devraient se ressembler:

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

Les sobjectsEntity 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 les données, nous pouvons commencer à mapper les résultats de la requête.

3.1. ColumnResult

Alors que les annotationsSqlResultSetMapping etQuery fonctionnent également sur les classesRepository, nous utilisons les annotations sur une classeEntity dans cet exemple.

Chaque annotationSqlResultSetMapping ne nécessite qu'une seule propriété,name.  Cependant, sans l'un des types de membres, rien ne sera mappé. Les types de membres sontColumnResult,ConstructorResult etEntityResult.

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

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

LeColumnResult propertyname identifie la colonne dans notre requête:

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

Notez quethe value of resultSetMapping dans notre annotationNamedNativeQueryis important because it matches the name property from our ResultSetMapping declaration.

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

3.2. TestColumnResult

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 du constructeur

Examinons à quel moment nous devons mapper un jeu de résultats à un objet entier.

4.1. ConstructorResult

Comme pour notre exempleColumnResult, nous ajouterons l'annotationSqlResultMapping sur notre classeEntity,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 (les deux sont obligatoires):

@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. 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, de sorte que cela n'est en fait pas requis.

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

Une autre différence unique pourConstructorResult  est que l'instanciation d'objet résultante est «nouvelle» ou «détachée». LesEntity mappés seront à l'état détaché lorsqu'une clé primaire correspondante existe dans lesEntityManager, 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 avectype.

4.2. TestConstructorResult

Testons lesConstructorResult 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. Cartographie d'entité

Enfin, pour un mappage d'entité simple avec moins de code, examinonsEntityResult.

5.1. Entité unique

EntityResult nous oblige à spécifier la classe d'entité,Employee. Nous utilisons la propriété sproperty facultativefields pour plus de contrôle. Combiné avecFieldResult,, nous pouvons mapper des alias et des champs qui ne correspondent pas:

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

Notre requête devrait maintenant inclure la colonne aliasée:

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

De la même manière queConstructorResult,EntityResult nécessite un constructeur. Cependant, un par défaut fonctionne ici.

5.2. Entités multiples

Mapper plusieurs entités est assez simple une fois que nous avons mappé une seule entité:

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

5.3. TestsEntityResult

Jetons un œil àEntityResult en 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));
}

Etant donné que les résultats de plusieurs entités joignent deux entités, l’annotation de requête sur une seule des classes est source de confusion.

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 pour utiliser l'annotationSqlResultSetMapping. SqlResultSetMapping  est un élément clé de l'API Java Persistence.

Des extraits de code peuvent être trouvésover on GitHub.