Le message est conçu pour les programmeurs novices et ne contient pas de choses super délicates.
J'invite tout le monde à la journée de démonstration du cours en ligne «Java Developer. Professionnel " . Dans le cadre de l'événement, je vous parlerai en détail du programme du cours, ainsi que de répondre à vos questions.
Une partie de la solution comme Hibernate est utilisée, car c'est trÚs pratique pour travailler avec des objets imbriqués.
Par exemple, il existe une classe RecordPackage, l'un des champs de cette classe est une collection d'objets enfants (ou imbriqués): les enregistrements.
Si vous utilisez Jdbc, vous devrez Ă©crire beaucoup de code de routine. Peu de gens l'aiment, ce qui explique en partie pourquoi ils utilisent Hiberhate.
Avec Hibernate, vous pouvez appeler une méthode à la fois pour obtenir un RecordPackage avec tous ses enregistrements enfants.
D'une part, je veux utiliser une méthode pour obtenir l'objet entier, et d'autre part, je ne veux pas jouer avec le monstre Hibernate.
Spring Data Jdbc vous permet de tirer le meilleur parti de ces deux mondes (ou du moins quelque chose d'acceptable).
Prenons deux cas:
- relation un-Ă -plusieurs
- relation individuelle
Ce sont ces connexions qui sont le plus souvent rencontrées dans la pratique.
Le code complet des exemples peut ĂȘtre trouvĂ© sur GitHub, ici je ne donnerai que le minimum.
Tout d'abord, il convient de noter que Spring Data Jdbc n'est pas un outil magique pour résoudre un problÚme. Il a certainement ses inconvénients et ses limites.
Cependant, pour un certain nombre de tùches typiques, c'est une solution parfaitement adaptée.
Relation un-Ă -plusieurs
Comme exemple concret, vous pouvez considĂ©rer: l'en-tĂȘte d'un paquet de certaines donnĂ©es et les lignes de donnĂ©es incluses dans ce paquet. Par exemple, fichier est un package et les lignes de fichier sont des lignes de donnĂ©es qui entrent dans ce package.
La structure des tableaux est la suivante:
create table record_package
(
record_package_id bigserial not null
constraint record_package_pk primary key,
name varchar(256) not null
);
create table record
(
record_id bigserial not null
constraint record_pk primary key,
record_package_id bigint not null,
data varchar(256) not null
);
alter table record
add foreign key (record_package_id) references record_package;
deux tableaux:
record_package
(en-tĂȘte d'un certain paquet) et record
(enregistrements inclus dans le paquet).
Comment cette relation est affichée dans le code java:
@Table("record_package")
public class RecordPackage {
@Id
private final Long recordPackageId;
private final String name;
@MappedCollection(idColumn = "record_package_id")
private final Set<Record> records;
âŠ.
}
Ici, nous nous intéressons à la définition d'une relation un-à -plusieurs. Ceci est codé à l'aide d'une annotation
@MappedCollection
.
Cette annotation a deux paramĂštres:
idColumn - le champ par lequel la connexion est Ă©tablie
; keyColumn - le champ par lequel les enregistrements de la table enfant sont classés.
Cette commande mĂ©rite d'ĂȘtre mentionnĂ©e sĂ©parĂ©ment. Dans cet exemple, peu nous importe dans quel ordre les enregistrements enfants seront insĂ©rĂ©s dans la table des enregistrements, mais dans certains cas, cela peut ĂȘtre important. Pour un tel classement, la table record aura un champ comme record_no, c'est ce champ qui devra ĂȘtre Ă©crit dans la keyColumn de l'annotation MappedCollection. Lorsque vous exĂ©cutez l'insertion, Spring Data Jdbc gĂ©nĂšre les valeurs de ce champ. En plus de l'annotation, Set
<
Record >
devra ĂȘtre remplacĂ© par List<
Record >
, ce qui est assez logique et compréhensible. La séquence de lignes enfants explicitement spécifiée sera prise en compte lors de la formation de la sélection, mais nous y reviendrons plus tard.
Nous avons donc identifiĂ© les connexions et sommes prĂȘts Ă l'essayer.
Nous créons des entités associées et les récupérons depuis la base:
var record1 = new Record("r1");
var record2 = new Record("r2");
var record3 = new Record("r3");
var recordPackage = new RecordPackage( "package", Set.of(record1, record2, record3));
var recordPackageSaved = repository.save(recordPackage);
var recordPackageLoaded = repository.findById(recordPackageSaved.getRecordPackageId());
Notez que nous n'avons besoin d'appeler qu'une seule méthode
repository.findById
pour obtenir une instance RecordPackage
avec une collection d'enregistrements remplie.
Bien sĂ»r, nous nous intĂ©ressons au type de requĂȘte SQL qui a Ă©tĂ© exĂ©cutĂ©e pour obtenir la collection imbriquĂ©e d'enregistrements.
ComparĂ© Ă Hibernate, Spring Data Jdbc est bon pour sa simplicitĂ©. Il peut ĂȘtre facilement dĂ©boguĂ© pour rĂ©vĂ©ler les principaux points.
AprĂšs une petite enquĂȘte dans le package org.springframework.data.jdbc.core.convert , nous trouvons la classe DefaultDataAccessStrategy . Cette classe est chargĂ©e de gĂ©nĂ©rer des requĂȘtes SQL basĂ©es sur les informations de classe. Maintenant, dans cette classe, nous nous intĂ©ressons Ă la mĂ©thode
Iterable <Object> findAllByPath
Et plus précisément la ligne:
String findAllByProperty = sql(actualType)
.getFindAllByProperty(identifier, path.getQualifierColumn(), path.isOrdered());
Ici, la requĂȘte SQL requise est extraite du cache interne.
Dans notre cas, cela ressemble Ă ceci:
SELECT "record"."data" AS "data", "record"."record_id" AS "record_id", "record"."record_package_id" AS "record_package_id"
FROM "record"
WHERE "record"."record_package_id" = :record_package_id
Tout est clair et prévisible.
Ă quoi cela ressemblerait-il si nous utilisions l'ordre des enregistrements dans la table enfant? De toute Ă©vidence, une commande serait requise.
Passons Ă la classe BasicRelationalPersistentProperty du package org.springframework.data.relational.core.mapping. Cette classe a une mĂ©thode qui dĂ©termine s'il faut ajouter un ordre par Ă la requĂȘte ou non.
public boolean isOrdered() {
return isListLike();
}
et
private boolean isListLike() {
return isCollectionLike() && !Set.class.isAssignableFrom(this.getType());
}
isCollectionLike vérifie que nous avons vraiment une "collection" (y compris un tableau).
Et à partir de la condition ! Set.class.isAssignableFrom (this.getType ()); il devient clair que Set n'est pas utilisé par hasard, mais pour exclure le tri inutile. Et un jour, nous utiliserons intentionnellement List pour activer le tri.
Je pense qu'un-Ă -plusieurs est plus ou moins clair, passons au cas suivant.
Relation individuelle
Disons que nous avons une telle structure.
create table info_main
(
info_main_id bigserial not null
constraint info_pk primary key,
main_data varchar(256) not null
);
create table info_additional
(
info_additional_id bigserial not null
constraint additional_pk primary key,
info_main_id bigint not null,
additional_data varchar(256) not null
);
alter table info_additional
add foreign key (info_main_id) references info_main;
Il existe une table avec des informations de base sur un certain objet (info_main) et des informations supplémentaires (info_additional).
Comment cela peut-il ĂȘtre reprĂ©sentĂ© dans le code:
@Table("info_main")
public class InfoMain {
@Id
private final Long infoMainId;
private final String mainData;
@MappedCollection(idColumn = "info_main_id")
private final InfoAdditional infoAdditional;
âŠ
}
à premiÚre vue, cela ressemble au premier cas un-à -plusieurs, mais il y a une différence. Cette fois, l'enfant est vraiment un objet, pas une collection comme dans le cas précédent.
Le code de test ressemble Ă ceci:
var infoAdditional = new InfoAdditional("InfoAdditional");
var infoMain = new InfoMain("mainData", infoAdditional);
var infoMainSaved = repository.save(infoMain);
var infoMainLoaded = repository.findById(infoMainSaved.getInfoMainId());
Voyons quelle expression sql est générée cette fois. Pour ce faire, nous allons dénicher la méthode findById à l'endroit:
Package org.springframework.data.jdbc.core.convert class DefaultDataAccessStrategy . Nous connaissons déjà cette classe, maintenant nous nous intéressons à la méthode.
public <T> T findById(Object id, Class<T> domainType)
On voit que la requĂȘte suivante est rĂ©cupĂ©rĂ©e du cache:
SELECT "info_main"."main_data" AS "main_data", "info_main"."info_main_id" AS "info_main_id", "infoAdditional"."info_main_id" AS "infoadditional_info_main_id", "infoAdditional"."additional_data" AS "infoadditional_additional_data", "infoAdditional"."info_additional_id" AS "infoadditional_info_additional_id"
FROM "info_main"
LEFT OUTER JOIN "info_additional" "infoAdditional"
ON "infoAdditional"."info_main_id" = "info_main"."info_main_id"
WHERE "info_main"."info_main_id" = :id
à l'heure actuelle, la jointure externe gauche nous convient, mais que faire sinon. Comment obtenir une jointure intérieure?
La fonctionnalité de création de jointures se trouve dans le package org.springframework.data.jdbc.core.convert , classe SqlGenerator , méthode:
private SelectBuilder.SelectWhere selectBuilder(Collection<SqlIdentifier> keyColumns)
Nous sommes intéressés par ce fragment:
for (Join join : joinTables) {
baseSelect = baseSelect.leftOuterJoin(join.joinTable).on(join.joinColumn).equals(join.parentId);
}
Si vous avez besoin de joindre des tables, il existe une option uniquement avec une jointure externe gauche.
Il semble que la jointure interne ne puisse pas encore ĂȘtre rĂ©alisĂ©e.
Conclusion
Nous avons couvert deux cas les plus typiques de la façon dont vous pouvez joindre des tables dans Spring Data Jdbc.
En principe, la fonctionnalité qui est maintenant disponible est tout à fait appropriée pour résoudre des problÚmes pratiques, bien qu'il y ait des limitations non critiques.
Le texte intĂ©gral de l'exemple peut ĂȘtre trouvĂ© ici .
Et voici une version vidéo de ce post.