Nous allons aborder un chapître assez simple, où nous allons revenir plus en détail sur les relations. Pour cela, assurez-vous d’avoir bien compris les notions de relations et de contraintes. Si ce n’est pas le cas, vous pouvez consulter les articles qui en parlent :
- Les bases – Création de relations et de links
- Avancé – Utiliser les contraintes de Generic System
- Avancé – Les contraintes dans Generic System
Avant de commencer, précisons que le thème que nous allons traiter dans ce chapître sera présenté pour les relations binaires, mais qu’il peut également être étendu aux relations n-aires.
Préparons le terrain
Avant d’aborder plus en profondeur les relations, il vous faut apprendre une nouvelle notion. Rassurez-vous, celle-ci est simple à comprendre.
Savez-vous ce qu’on appelle l’axe d’une relation dans Generic System ?
Il s’agit du type situé à la position i de la relation, i étant le numéro de l’axe. Appliquons cela à notre relation bien connue VehicleColor.
L’axe 0 de
VehicleColor est
Vehicle puisqu’il est à la position 0 dans la relation.
L’axe 1 est quand à lui
Color puisque situé à la position 1 de la relation.
Enfin, retenez bien 2 termes : la base désigne l’axe 0 d’une relation, les targets désignent les autres axes de la relation.
Ainsi, la target 1 est l’axe 1 de la relation, la target 2 désigne l’axe 2 de la relation, et ainsi de suite.
Les différents types de relation
Il existe 4 types de relation : OneToOne, OneToMany, ManyToOne, ManyToMany.
Nous allons voir chacune d’elles et utiliser pour cela la
SingularConstraint.
Relation OneToOne
La relation OneToOne n’est pas la plus utilisée, mais ce n’est pas pour autant que vous ne devez pas la connaître.
Cette relation spécifie que la base est liée à une seule target et qu’inversement une target est liée à une seule base.
Dans notre exemple de
Vehicle, cela signifierait qu’un
Vehicle ne peut avoir qu’une seule
Color et qu’une
Color ne peut appartenir qu’à un seul
Vehicle.
Voyons comment la mettre en place :
1 2 3 4 5 6 7 8 9 10 11 12 |
Engine engine = new Engine(); // Create the types Generic vehicle = engine.addInstance("Vehicle"); Generic color = engine.addInstance("Color"); // Create the relation Generic vehicleColor = vehicle.addRelation("VehicleColor", color); // Add two instances of Vehicle Generic myVehicle = vehicle.addInstance("myVehicle"); Generic yourVehicle = vehicle.addInstance("yourVehicle"); // Add two instances of Color Generic red = color.addInstance("red"); Generic yellow = color.addInstance("yellow"); |
Pour faire une relation OneToOne, il suffit d’activer la SingularConstraint sur ses 2 axes :
1 2 3 |
// Make VehicleColor a OneToOne relation vehicleColor.enableSingularConstraint(ApiStatics.BASE_POSITION); vehicleColor.enableSingularConstraint(ApiStatics.TARGET_POSITION); |
Nous pouvons ensuite déclarer le lien entre myVehicle et red :
1 2 3 4 5 |
// Create the link between myVehicle and red from the relation VehicleColor myVehicle.addLink(vehicleColor, "myVehicleRed", red); // OK // Persist changes engine.getCurrentCache().flush(); |
Une fois un lien établi, toute tentative de création d’un nouveau lien (sur base ou target) sera rejetée par le système car un lien existe déjà.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
try { // Create the link between myVehicle and yellow from the relation VehicleColor myVehicle.addLink(vehicleColor, "myVehicleYellow", yellow); } catch (RollbackException e) { assert e.getCause() instanceof SingularConstraintViolationException; } try { // Create the link between yourVehicle and red from the relation VehicleColor yourVehicle.addLink(vehicleColor, "yourVehicleRed", red); } catch (RollbackException e) { assert e.getCause() instanceof SingularConstraintViolationException; } |
Continuons sur notre lancée avec les relations ManyToOne.
Relation ManyToOne
La relation ManyToOne est certainement la plus utilisée. Si vous repensez aux bases de données relationnelles, la plupart des relations que vous avez rencontrées étaient des relations ManyToOne. Cette relation spécifie que la base est liée à une seule target et qu’une target peut être liée à plusieurs bases.
Dans notre exemple de
Vehicle, cela signifierait qu’un
Vehicle ne peut avoir qu’une seule
Color et qu’une
Color peut appartenir à plusieurs
Vehicle.
Voyons comment mettre en place ce type de relation :
1 2 3 4 5 6 7 8 9 10 |
Engine engine = new Engine(); Generic vehicle = engine.addInstance("Vehicle"); Generic color = engine.addInstance("Color"); Generic vehicleColor = vehicle.addRelation("VehicleColor", color); Generic myVehicle = vehicle.addInstance("myVehicle"); Generic yourVehicle = vehicle.addInstance("yourVehicle"); Generic red = color.addInstance("red"); Generic yellow = color.addInstance("yellow"); |
Pour faire une relation ManyToOne, il suffit d’activer la SingularConstraint sur la base uniquement, c’est-à-dire sur l’axe 0 :
1 2 |
// Make VehicleColor a ManyToOne relation vehicleColor.enableSingularConstraint(ApiStatics.BASE_POSITION); |
Il suffit ensuite de créer le lien entre myVehicle et red :
1 2 3 4 5 |
// Create the link between myVehicle and red from the relation VehicleColor myVehicle.addLink(vehicleColor, "myVehicleRed", red); // OK // Persist changes engine.getCurrentCache().flush(); |
Une fois un lien établi, il devient impossible d’ajouter un nouveau à la base puisqu’un lien existe déjà. Par contre, vous pouvez en rajouter autant que vous voulez à la target :
1 2 3 4 5 6 7 8 9 10 11 12 |
try { // Create the link between myVehicle and yellow from the relation VehicleColor myVehicle.addLink(vehicleColor, "myVehicleYellow", yellow); } catch (RollbackException e) { assert e.getCause() instanceof SingularConstraintViolationException; } // Create the link between yourVehicle and red from the relation VehicleColor yourVehicle.addLink(vehicleColor, "yourVehicleRed", red); // OK // Persist changes engine.getCurrentCache().flush(); |
Continuons sur notre lancée avec les relations OneToMany qui sont très similaires.
En effet, il suffit de tourner la tête !
Relation OneToMany
La relation OneToMany étant l’exacte opposée de la ManyToOne, elle est tout aussi fréquente. Cette relation spécifie que la base peut être liée à plusieurs targets, mais qu’une target est liée à une seule base.
Dans notre exemple de
Vehicle, cela signifierait qu’un
Vehicle peut avoir plusieurs
Color et qu’une
Color ne peut appartenir qu’à un seul
Vehicle.
Voyons comment mettre en place ce type de relation :
1 2 3 4 5 6 7 8 9 10 |
Engine engine = new Engine(); Generic vehicle = engine.addInstance("Vehicle"); Generic color = engine.addInstance("Color"); Generic vehicleColor = vehicle.addRelation("VehicleColor", color); Generic myVehicle = vehicle.addInstance("myVehicle"); Generic yourVehicle = vehicle.addInstance("yourVehicle"); Generic red = color.addInstance("red"); Generic yellow = color.addInstance("yellow"); |
Pour faire une relation OneToMany, il suffit d’activer la SingularConstraint sur la target, c’est-à-dire sur l’axe 1 :
1 2 |
// Make VehicleColor a OneToMany relation vehicleColor.enableSingularConstraint(ApiStatics.TARGET_POSITION); |
Il suffit ensuite de créer le lien entre myVehicle et red :
1 2 3 4 5 |
// Create the link between myVehicle and red from the relation VehicleColor myVehicle.addLink(vehicleColor, "myVehicleRed", red); // OK // Persist changes engine.getCurrentCache().flush(); |
Une fois un lien établi, il est possible d’ajouter autant de lien que l’on souhaite à la base, mais il devient impossible d’ajouter un nouveau à la target puisqu’un lien existe déjà.
1 2 3 4 5 6 7 8 9 10 11 12 |
// Create the link between myVehicle and yellow from the relation VehicleColor myVehicle.addLink(vehicleColor, "myVehicleYellow", yellow); // OK // Persist changes engine.getCurrentCache().flush(); try { // Create the link between yourVehicle and red from the relation VehicleColor yourVehicle.addLink(vehicleColor, "yourVehicleRed", red); } catch (RollbackException e) { assert e.getCause() instanceof SingularConstraintViolationException; } |
Voyons le dernier cas avec les relations ManyToMany.
Relation ManyToMany
Contrairement à ces consoeurs, la relation ManyToMany est très peu utilisée.
Cette relation spécifie que la base peut être liée à plusieurs targets et qu’une target peut être liée à plusieurs bases.
Dans notre exemple de
Vehicle, cela signifierait qu’un
Vehicle peut avoir plusieurs
Color et qu’inversement une
Color peut appartenir à plusieurs
Vehicle.
Voyons comment mettre en place ce type de relation :
1 2 3 4 5 6 7 8 9 10 |
Engine engine = new Engine(); Generic vehicle = engine.addInstance("Vehicle"); Generic color = engine.addInstance("Color"); Generic vehicleColor = vehicle.addRelation("VehicleColor", color); Generic myVehicle = vehicle.addInstance("myVehicle"); Generic yourVehicle = vehicle.addInstance("yourVehicle"); Generic red = color.addInstance("red"); Generic yellow = color.addInstance("yellow"); |
Puisque la SingularConstraint n’est pas activée par défaut dans Generic System, il n’y a rien à faire pour déclarer une relation ManyToMany ! On peut donc directement déclarer le lien entre myVehicle et red :
1 2 3 4 5 6 |
// Make VehicleColor a ManyToMany relation : nothing to do, it is the case by default // Create the link between myVehicle and red from the relation VehicleColor myVehicle.addLink(vehicleColor, "myVehicleRed", red); // OK // Persist changes engine.getCurrentCache().flush(); |
Une fois un lien établi, il est possible d’ajouter autant de lien que l’on souhaite à la base et et à la target, le système ne lèvera pas d’exception.
1 2 3 4 5 6 7 8 |
// Create the link between myVehicle and yellow from the relation VehicleColor myVehicle.addLink(vehicleColor, "myVehicleYellow", yellow); // OK // Create the link between yourVehicle and red from the relation VehicleColor yourVehicle.addLink(vehicleColor, "yourVehicleRed", red); // OK // Persist changes engine.getCurrentCache().flush(); |
Terminons sur un point particulier à propos des relations ManyToMany.
Relations n-m et Snapshot
Reprenons notre exemple de notre relation ManyToMany VehicleColor :
1 2 3 4 5 6 7 8 9 10 |
Engine engine = new Engine(); Generic vehicle = engine.addInstance("Vehicle"); Generic color = engine.addInstance("Color"); Generic vehicleColor = vehicle.addRelation("VehicleColor", color); Generic myVehicle = vehicle.addInstance("myVehicle"); Generic yourVehicle = vehicle.addInstance("yourVehicle"); Generic red = color.addInstance("red"); Generic yellow = color.addInstance("yellow"); |
Ajoutons maintenant des liens depuis la base ( myVehicle) et la target ( red) :
1 2 3 4 |
// Create a link from the base between myVehicle and yellow Generic myVehicleYellow = myVehicle.addLink(vehicleColor, "myVehicleYellow", yellow); // Create a link from the target between yourVehicle and red Generic yourVehicleRed = red.addLink(vehicleColor, "yourVehicleRed", yourVehicle); |
Il est alors facile de récupérer les liens créés, que ce soit depuis la base ( myVehicle) ou la target ( red) :
1 2 3 4 5 6 7 8 9 |
// Find links from the base (myVehicle) Snapshot linksFromTheBase = myVehicle.getLinks(vehicleColor); assert linksFromTheBase.size() == 1; assert linksFromTheBase.containsAll(Arrays.asList(myVehicleYellow)); // Find links from the target (red) Snapshot linksFromTheTarget = red.getLinks(vehicleColor); assert linksFromTheTarget.size() == 1; assert linksFromTheTarget.containsAll(Arrays.asList(yourVehicleRed)); |
Jusque là, rien de bien nouveau. Mais nous allons maintenant aborder un point fort de Generic System par rapport aux bases de données relationnelles, plus particulièrement par rapport aux ORM.
Pour les ORM, la déclaration d’une relation ManyToMany impose de choisir une extrémité de la relation comme étant le maître, et l’autre l’esclave. Si des modifications sont apportées, elle ne peuvent se faire que sur le maître. De plus, si des lectures avaient été faites sur l’esclave avant ces modifications, elles ne seront plus actualisées.
Dans le cas de Generic System, il n’y a pas de maître ni d’esclave : les modifications peuvent être effectuées depuis la base ou la target, et chacune connaîtra les modifications effectuées. Generic System reste cohérent.
Ajoutons un lien à notre base myVehicle :
1 2 |
// Add a link from the base Generic myVehicleRed = myVehicle.addLink(vehicleColor, "myVehicleRed", red); |
Lorsque l’on interroge le snapshot linksFromTheBase, il contient bien les deux liens que nous avons créés depuis myVehicle :
1 2 3 |
// Links from the base have been modified and are aware of these changes... assert linksFromTheBase.size() == 2; assert linksFromTheBase.containsAll(Arrays.asList(myVehicleYellow, myVehicleRed)); |
Mais ce qui est plus intéressant, c’est que le nouveau lien apparaît également dans le snapshot linksFromTheTarget :
1 2 3 |
// ...and links from the base have also been modified and made aware of these changes! assert linksFromTheTarget.size() == 2; assert linksFromTheTarget.containsAll(Arrays.asList(yourVehicleRed, myVehicleRed)); |
Bien que le lien ait été créé à partir de la base, la target a bien été informée de la création.
Nous pouvons aussi vérifier que l’inverse est vrai. Commençons par supprimer le lien qu’on vient d’ajouter, et vérifions que sa suppression a bien été enregistrée :
1 2 3 4 5 6 7 8 9 |
// Remove the last link myVehicleRed.remove(); assert !myVehicleRed.isAlive(); assert linksFromTheBase.size() == 1; assert linksFromTheBase.containsAll(Arrays.asList(myVehicleYellow)); assert linksFromTheTarget.size() == 1; assert linksFromTheTarget.containsAll(Arrays.asList(yourVehicleRed)); |
Ajoutons alors des links depuis la target et récupérons-les depuis la target puis la base :
1 2 |
// Add a link from the target myVehicleRed = red.addLink(vehicleColor, "myVehicleRed", myVehicle); |
Nous pouvons ensuite vérifier que le lien apparaît bien dans nos deux Snapshot :
1 2 3 4 5 6 7 8 9 10 |
// Links from the target have been modified and are aware of these changes... assert linksFromTheTarget.size() == 2; assert linksFromTheTarget.containsAll(Arrays.asList(yourVehicleRed, myVehicleRed)); // ...and links from the base have also been modified and made aware of these changes! assert linksFromTheBase.size() == 2; assert linksFromTheBase.containsAll(Arrays.asList(myVehicleYellow, myVehicleRed)); // Persist changes engine.getCurrentCache().flush(); |
En résumé
- L’axe i d’une relation désigne le type à la position i de ladite relation.
- L’axe 0 est appelée base, les autres axes étant appelés targets.
- Il existe 4 types de relations : OneToOne, OneToMany, ManyToOne, ManyToMany.
Toutes peuvent être mises en oeuvre en utilisant la SingularConstraint. - Les relations n-m peuvent être modifiées et lues depuis la base comme la target et chacune restera informée des modifications effectuées par l’autre.
- Toutes les notions présentées ici concernaient les relations binaires.
Elles peuvent bien évidemment être étendues aux relations n-aires.
Nous allons ensuite découvrir ensemble le fabuleux monde des Tree !