Colonnes définies à l’exécution avec asentinel-orm

Asentinel-orm est un outil ORM léger construit sur Spring JDBC, en particulier JdbcTemplate. Ainsi, il possède la plupart des fonctionnalités que l’on attend d’un ORM de base, telles que la génération de SQL, le chargement paresseux, etc.

En exploitant le JdbcTemplate, cela signifie qu’il permet de participer aux transactions gérées par Spring et peut être facilement intégré dans tout projet utilisant déjà JdbcTemplate pour interagir avec la base de données.

Depuis 2015, asentinel-orm a été utilisé avec succès dans plusieurs applications et continuellement amélioré selon les besoins de l’entreprise. À l’été 2024, il est officiellement devenu un projet open-source, ce qui, selon nous, accélérera son évolution et augmentera le nombre de contributeurs.

Dans cet article, une application d’exemple est construite pour mettre en évidence plusieurs fonctionnalités clés de l’ORM :

  • Configuration simple
  • Modélisation simple des entités de domaine via des annotations personnalisées
  • Écriture facile et exécution sécurisée de requêtes SQL brutes
  • Génération automatique de requêtes SQL
  • Schéma dynamique (les entités sont enrichies d’attributs supplémentaires à l’exécution, persistées et lues sans modifications de code)

Application

Configuration

  • Java 21
  • Spring Boot 3.4.0
  • asentinel-orm 1.70.0
  • Base de données H2

Configuration

Pour interagir avec asentinel-orm et tirer parti de ses fonctionnalités, une instance de OrmOperations est requise.

Comme indiqué dans la JavaDoc, ce est l’interface centrale pour effectuer des opérations ORM, et il n’est ni prévu ni nécessaire d’être spécifiquement implémenté dans le code client.

L’application d’exemple inclut le code de configuration pour créer un bean de ce type.

Java

 

OrmOperations a deux super interfaces :

  • SqlBuilderFactory – crée des instances de SqlBuilder qui peuvent ensuite être utilisées pour créer des requêtes SQL. SqlBuilder est capable de générer automatiquement des parties de la requête, par exemple, celle qui sélectionne les colonnes. La clause where, la clause order by, d’autres conditions et les colonnes réelles peuvent également être ajoutées à l’aide des méthodes de la classe SqlBuilder. Dans la prochaine partie de cette section, un exemple de requête générée par un SqlBuilder est présenté.
  • Updater – utilisé pour sauvegarder des entités dans leurs tables de base de données respectives. Il peut effectuer des insertions ou des mises à jour selon que l’entité est nouvellement créée ou déjà existante. Une interface de stratégie appelée NewEntityDetector existe, qui est utilisée pour déterminer si une entité est nouvelle. Par défaut, le SimpleNewEntityDetector est utilisé.

Toutes les requêtes générées par l’ORM sont exécutées à l’aide d’une instance de SqlQueryTemplate , qui nécessite également un JdbcOperations/JdbcTemplate de Spring pour fonctionner. En fin de compte, toutes les requêtes atteignent le bon vieux JdbcTemplate par lequel elles sont exécutées tout en participant aux transactions Spring, tout comme toute exécution directe de JdbcTemplate ..

Des constructions SQL spécifiques à la base de données et une logique sont fournies via des implémentations de l’interface JdbcFlavor, qui sont ensuite injectées dans la plupart des beans mentionnés ci-dessus. Dans cet article, comme une base de données H2 est utilisée, une implémentation de H2JdbcFlavor est configurée.

La configuration complète de l’ORM dans le cadre de l’application exemple est OrmConfig.

Implémentation

Le modèle de domaine expérimental exposé par l’application exemple est simple et se compose de deux entités : les fabricants de voitures et les modèles de voitures. Représentant exactement ce que leurs noms désignent, la relation entre eux est évidente : un fabricant de voitures peut posséder plusieurs modèles de voitures.

En plus de son nom, le fabricant de voitures est enrichi d’attributs (colonnes) qui sont saisies par l’utilisateur de l’application dynamiquement à l’exécution. Le cas d’utilisation exemplifié est simple :

  • L’utilisateur est invité à fournir les noms et types visés pour les attributs dynamiques
  • Quelques fabricants de voitures sont créés avec des valeurs concrètes pour les attributs dynamiques précédemment ajoutés, puis
  • Les entités sont rechargées, décrites à la fois par les attributs initiaux et ceux définis à l’exécution

Les entités initiales sont mappées en utilisant les tables de base de données ci-dessous :

SQL

 

Les classes de domaine correspondantes sont décorées avec des annotations spécifiques à l’ORM pour configurer les mappages vers les tables de base de données ci-dessus.

Java

 

Java

 

Quelques considérations :

  • @Table – associe la classe à une table de base de données
  • @PkColumn – mappe le id (identifiant unique) à la clé primaire de la table
  • @Column – mappe un membre de la classe à une colonne de table
  • @Child – définit la relation avec une autre entité
  • @Child membres annotés – configurés pour être chargés de manière paresseuse
  • type colonne de table – mappée à un champ enumCarType

Pour que la classe CarManufacturer prenne en charge les attributs définis à l’exécution (mappés à des colonnes de table définies à l’exécution), une sous-classe comme celle ci-dessous est définie :

Java

 

Cette classe stocke les attributs définis à l’exécution (champs) dans un Map. L’interaction entre les valeurs des champs à l’exécution et l’ORM est réalisée via l’implémentation de l’interface DynamicColumnEntity .

Java

 

  • setValue() – est utilisé pour définir la valeur de la colonne définie à l’exécution lorsque celle-ci est lue à partir de la table
  • getValue() – est utilisé pour récupérer la valeur d’une colonne définie à l’exécution lorsque celle-ci est enregistrée dans la table

Le DynamicColumn mappe les attributs définis à l’exécution sur leurs colonnes correspondantes de manière similaire à l’annotation @Column qui mappe les membres connus à la compilation

Lors de l’exécution de l’application, le CfRunner est exécuté. L’utilisateur est invité à saisir les noms et types des attributs personnalisés dynamiques souhaités qui enrichissent l’entité CarManufacturer (pour simplifier, seuls les types int et varchar sont pris en charge).

Pour chaque paire nom-type, une commande DML est exécutée afin que les nouvelles colonnes puissent être ajoutées à la table de base de données CarManufacturer . La méthode suivante (déclarée dans CarService) effectue l’opération.

Java

 

Chaque attribut d’entrée est enregistré en tant que DefaultDynamicColumn, une implémentation de référence DynamicColumn .

Une fois que tous les attributs sont définis, deux fabricants de voitures sont ajoutés à la base de données, car l’utilisateur fournit des valeurs pour chaque attribut.

Java

 

La méthode ci-dessous (déclarée dans CarService) crée effectivement l’entité via l’ORM.

Java

 

La version à 2 paramètres de la méthode OrmOperations update() est appelée, ce qui permet de passer une instance de UpdateSettings et de communiquer à l’ORM lors de l’exécution qu’il existe des valeurs définies à l’exécution qui doivent être persistées.

Enfin, deux modèles de voiture sont créés, correspondant à l’un des fabricants de voitures précédemment ajoutés.

Java

 

La méthode ci-dessous (déclarée dans CarService) crée effectivement les entités via l’ORM, cette fois en utilisant la méthode OrmOperations update() pour persister des entités sans attributs dynamiques. Par souci de commodité, plusieurs entités sont créées en un seul appel.

Java

 

Comme dernière étape, l’un des fabricants créés est rechargé par son nom à l’aide d’une requête générée par l’ORM.

Java

 

Quelques explications concernant la méthode définie ci-dessus méritent d’être faites.

La méthode OrmOperations newSqlBuilder() créé une instance de SqlBuilder, et comme son nom l’indique, cela peut être utilisé pour générer des requêtes SQL. La méthode SqlBuilder select() génère la partie select from table de la requête, tandis que le reste (where, order by) doit être ajouté. La partie de la requête select peut être personnalisée en passant des instances de EntityDescriptorNodeCallback (les détails sur EntityDescriptorNodeCallback pourraient faire l’objet d’un futur article).

Afin de faire savoir à l’ORM que le plan est de sélectionner et de mapper des colonnes définies à l’exécution, un DynamicColumnsEntityNodeCallback doit être passé. Avec cela, un AutoEagerLoader est fourni afin que l’ORM comprenne qu’il doit charger de manière anticipée la liste des CarModels associés au fabricant. Néanmoins, cela n’a rien à voir avec les attributs définis à l’exécution, mais cela démontre comment un membre enfant peut être chargé de manière anticipée.

Conclusion

Bien qu’il y ait probablement d’autres façons de travailler avec des colonnes définies à l’exécution lorsque les données sont stockées dans des bases de données relationnelles, l’approche présentée dans cet article a l’avantage d’utiliser des colonnes de base de données standard qui sont lues/écrites à l’aide de requêtes SQL standard générées directement par l’ORM.

Il n’était pas rare que nous ayons l’occasion de discuter dans « la communauté » du asentinel-orm, des raisons qui nous ont poussés à développer un tel outil. Habituellement, à première vue, les développeurs se montraient réticents et réservés lorsqu’il s’agissait d’un ORM sur mesure, demandant pourquoi ne pas utiliser Hibernate ou d’autres implémentations JPA.

Dans notre cas, le principal moteur était le besoin d’une méthode rapide, flexible et facile pour travailler avec un nombre parfois assez important d’attributs définis à l’exécution (colonnes) pour des entités faisant partie du domaine métier. Pour nous, cela s’est avéré être la bonne voie. Les applications fonctionnent sans problème en production, les clients sont satisfaits de la rapidité et des performances atteintes, et les développeurs se sentent à l’aise et créatifs avec l’API intuitive.

Comme le projet est désormais open source, il est très facile pour toute personne intéressée de jeter un coup d’œil, de se faire une opinion objective à son sujet, et, pourquoi pas, de le dupliquer, ouvrir une PR et contribuer.

Ressources

  • Le projet ORM open source est ici.
  • Le code source de l’application d’exemple est ici.

Source:
https://dzone.com/articles/runtime-defined-columns-with-asentinel-orm