Accéder aux bases de données pour en manipuler les données est une fonctionnalité commune dans diverses applications. Avec Java il existe plusieurs techniques pour manipuler et accéder aux données d'une base de données. Et ces techniques sont encore plus simples en utilisant le framework spring et plus particulièrement sa mouture springboot.
Dans le tutoriel ci dessous nous allons voir comment créer, modifier supprimer ou lire les données d'une base de données rapidement et sans effort.
1. Qu'est ce que JPA (Java Persistence API)
JPA est une API mis en place n java qui permet de mapper un POJO (Plain Old Java Object) ou Java Bean vers une ou plusieurs tables d'une base de données. L'objectif de JPA étant de simplifier l'exécution des requêtes SQL dans la base de données. JPA met à disposition du développeur un PersistenceContext et aussi fait le mapping entre l'objet java et la table en base de données via des annotations.
Schématiquement, JPA a pour but de sauvegarder comme tel des POJO dans la base de données et de retourner des données de la base de données sous forme de POJO. Toute la mécanique sous-jacente est transparente pour le développeur.
La connection à la base de données, la création des statements, la création des resulset, et leurs fermetures respectives sont toutes déléguées à l'API donc à la charge de toute implémentation de cette API.
Plusieurs implémentations de l'API JPA existent:
2. Qu'est ce qu'un ORM (Object Relational Mapper)
3. Qu'est ce que Spring Data
Spring Data est une couche d'abstraction mis en place par le framework spring et qui a pour but de d'uniformiser et de faciliter l'utilisations des technologies sous-jacentes telles que: JPA, pur JDBC, Document Data, Indexed Data, etc...
L'une des forces de Spring Data est qu'avec une convention de nommage des noms de méthodes, il est capable de générer des requêtes JPQL / SQL qui sont ensuite exécutées dans la base de données. Cette fonctionnalité accélère grandement les accès aux bases de données.
Dans la suite du tutoriel nous utiliserons Spring Data JPA
4. Les dépendances
Nous allons créer une API CRUD qui nous permettra de créer, modifier, Sélectionner ou Supprimer les données d'une base de données
A l'aide de Spring Initializr, nous créons notre projet qui utilisera: Spring Data JPA, Spring Web
Dépendances Gradle
Dépendances Maven
5. La configuration
L'autoconfiguration de springboot, nous facilite pleinement la tache, en matière de configuration. En effet, il suffit dans le fichier application.yaml (ou application.properties) de déclarer les propriétés d'accès à la base de données:
Avec cette configuration, springboot créera l'EntityManagerFactory (autrement dit PersistenceContext) automatiquement afin d'exécuter des requêtes dans la base de données.
Ensuite, il nous faudra nécessairement gérer les transactions dans notre application, dans le cas contraire, Spring Data JPA considèrera que toutes nos actions sont en auto-commit. De ce fait, dans notre classe de configuration:
Dans cette configuration, nous indiquons le package à scanner pour les entités (@EntityScan), pour les composants (@ComponentScan) et pour les repository (@EnableJpaRepositories)
6. Les entités
La première étape pour effectuer des opérations avec la base de données est la création des entités, car nous utilisons un ORM via la spécification JPA, donc il nous faut mapper nos tables de base de données en objets java (POJO)
@Entity: il s'agit de l'annotation la plus importante, car elle permet de marquer une classe java comme permettant le mapping entre les champs de la classe et les colonnes de la table
@Table: Cette annotation permet d'indiquer le nom de la table concernée dans la base de donnée. Si cette annotation est absente, l'implémentation de JPA utilisée (en l'occurence Hibernate dans notre cas) tentera de créer/trouver une table ayant le même nom que la classe Java.
@Id: Toute entité doit indiquer le champ qui sera pris comme étant l'identifiant de l'entité.
@Column: Permet d'indiquer le mapping entre la colonne de la table en base de données et le champ de la classe java. si cette annotation est absente, alors une colonne du meme nom que le champ de la classe sera attendue.
Ensuite dans la création des entités, il est primordial d'indiquer les relations (associations) entre elles afin que cela soit matérialisé en base de donnée par des relation entre table dans une base de données relationnelle. Dans notre exemple l'entité Quote concerne une et une seule entité Stock, alors que cette dernière possède une list de Quote. D'où l'utilisation de la relation: @ManyToOne combiné avec le nom de la colonne de jointure: @JoinColum.
@OneToMay: Cette annotation permet d'indiquer qu'une entité est associée à une autre via une relation de 1 à plusieurs. Il est alors obligatoire que le champ soit de type collection (List, Set, etc...)
@ManyToOne: Il s'agit de la relation inverse de la précédente. Qui indique que plusieurs entités de la même classe peut être associées à une autre entité.
JPA définie plusieurs autres associations que vous trouverez ici: OpenJPA documentation
7. Les Repository
La seconde étape consiste à créer les repository pour chaque entité et qui serviront en quelque sortes de DAO (Data Access Object). Comme nous utilisons Spring Data JPA, nous repository seront des interfaces simples qui hériterons de l'interface JpaRepository en indiquant l'entité concernée par ce repository et le type du champ Id.
De plus avec Spring Data, aucun besoin d'annoter la classe repository avec l'annotation @Repository, car grace à @EnableJpaRepositories(basePackages = {"com.javatutorialshub.market"}) les interfaces repository seront pris en compte.
En l'état, les repository sont utilisables pour savegarder, supprimer, mettre à jour ou sélectionner plusieurs éléments sans aucune implémentation des repository, comme nous allons le voir au niveau des services.
8. Les services
Dans cette couche, nous allons mettre en oeuvre l'utilisation des repository pour l'accès aux base de données. Pour ce faire, nous utiliserons MapStruct (que nous verrons en détail dans un autre tutoriel) qui nous aideras à mapper nos DTO (Data Transfer Object: ce sont des objets qui collecterons l'entrée saisie par les utilisateurs).
Dans l'exemple ci-dessus, le service CreateStockService utilise un le repository StockRepository et fait appel à sa méthode save, qui sauvegardera l'objet stock en base de donnée. Aucunement, nous n'avons eu besoin d'implémenter la méthode save et tout fonctionne.
De même si nous souhaitons retourner un stock en utilisant son Id comme paramètre d'entrée, nous n'avons rien à faire au niveau du repository. Le service ne fera qu'invoquer la méthode findById disponible dans le repository.
Il faut noter que les repository ne peuvent sauvegarder, modifier ou retourner des entités, dans la très grande majorité des cas. Dans certains cas, il est possible de sélectionner et retourner des objects qui ne sont pas forcément représentés comme étant des entités.
Dans le cas de l'update d'une entité, nous utiliserons la même méthode save(...) du repository, car cette dernière permet d'exécuter une requête insert ou update. Seulement il nous utiliser un marqueur pour indiquer à Spring Data quand faire un insert et quand faire un update. Ce marqueur est matérialisé par l'interface Persistable que doivent implémenter les entité comme suit:
Ensuite dans le service UpdateStockService, nous invoquerons la méthode markNotNew() avant l'appel à la méthode save(...)
La suppression quant à elle se fera la plus part du temps via l'Id de l'entité. De ce fait, nous appelons la méthode deleteById déjà disponible dans le repository comme suit:
9. Les conventions de nommage de Spring Data JPA
Imaginons maintenant que nous souhaitions maintenant sélectionner tous les stock par le nom. Cette fois ci, nous déclarons la méthode, findByName dans le repository comme suit:
Vu que la méthode suit le formalisme:
- Find (pour indiquer qu'on veut sélectionner)
- By (pour indiquer le nom du champ dans la classe d'entité)
- Name (le nom du champ dans la classe d'entité)
De même si nous souhaitons, sélectionner tous les stocks par le nom et la description. Nous utilisons le même formalisme, pour la méthode findByNameAndDescription:
}
Spring Data JPA défini une palette importante de formalisme pour l'écriture des noms de méthodes. Nous pouvons trouver cette documentation ici: Spring Data JPA Query Methods
10. Les requêtes personnalisées
Parfois, il arrive que les conventions de nommage prévues par Spring Data JPA ne suffisent pas définir correctement la requête qui convient à notre besoin. Dans ce cas, il est tout à fait possible d'utiliser l'annotation @Query au dessus d'une méthode de repository afin d'indiquer exactement la requête à exécuter.
Dans le repository ci-dessus, nous indiquons la requête à exécuter à l'appel de la méthode sumAllQuotationsAtDate, via l'annotation @Query. Quant à l'annotation @Param, elle permet de mapper les paramètres de la méthodes avec les paramètres dans la requête JPQL. Nous pouvons aussi écrire des requêtes SQL avec l'annotation @Query. Dans ce cas, il faudrait l'indiquer avec l'attribut nativeQuery = true.
En cas de requête de modification de la données en base de données (UPDATE, DELETE, ou INSERT), il est nécessaire d'adjoindre l'annotation @Modifiying à l'annotation @Query
11. Les transactions
Nous reviendrons en détail dans un tutoriel dédié aux transactions. Par contre pour l'instant, nous devons retenir qu'afin que les modifications, les créations et les suppressions de nos entités soient effectives dans la base de données, il nous faut annoter les méthodes de nos services avec l'annotation @Transactional.
Toute méthode, annotée avec cette annotation, exécutera un "commit" dans la base de données lorsque tout ce passe bien, et fera un "rollback" en cas d'exception.
Il est possible d'indiquer les classes d'exception, pour lesquelles nous souhaitons un rollback. En l'absence de cette indication, toute exception engendrera un "rollback" dans la base de données.
Il est même très fortement conseillé d'annoter aussi les méthode de selection, de lecture de données dans les services avec l'annotation @Transactional, mais en indiquant qu'il s'agit de méthode readOnly.
Attention à bien sélectionner l'annotation @Transactional de Spring Data et non celle de JPA (Jakarta Persistence). Car en effet, nous souhaitons que les transactions soient gérées automatiquement par Spring Data.
Le code source de ce tutoriel est présent sur github à cette adresse:market-instument-crud-service
Ne pas hésiter à commenter ou à poser une question ou à demander de l'aide autours de java et des technologies connexes. Nous nous ferons un plaisir de vous répondre.
Commentaires
Enregistrer un commentaire