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