Remarque :

TASK 4 : Implémenter des requêtes de base de données et créer une application Micronaut

Dans cet atelier, vous allez implémenter des requêtes de base de données et créer une application Micronaut en local qui se connecte à Oracle Autonomous Database.

Temps estimé : 30 minutes

Contenu de la tâche

Dans cette tâche :

Etape 1 : création d'entités de données Micronaut mises en correspondance avec des tables Oracle Database

Dans la tâche précédente, vous avez ajouté le script SQL qui créerait une table nommée OWNER et une table nommée PET une fois exécutée. Vous devez ensuite définir des classes d'entité qui peuvent être utilisées pour lire les données des tables de base de données.

  1. Créez une classe d'entité qui représentera un élément Owner dans le package src/main/java/com/example. Cliquez avec le bouton droit de la souris sur src/main/java/com/example pour développer le menu de contenu, sélectionnez New File, nommez-le Owner.java et collez le code suivant :

    package com.example;
    
    import io.micronaut.core.annotation.Creator;
    import io.micronaut.data.annotation.GeneratedValue;
    import io.micronaut.data.annotation.Id;
    import io.micronaut.data.annotation.MappedEntity;
    
    @MappedEntity
    public class Owner {
    
      // The ID of the class uses a generated sequence value
      @Id
      @GeneratedValue
      private Long id;
      private final String name;
      private int age;
    
      // the constructor reads column values by the name of each constructor argument
      @Creator
      public Owner(String name) {
          this.name = name;
      }
    
      // each property of the class maps to a database column
      public int getAge() {
          return age;
      }
    
      public void setAge(int age) {
          this.age = age;
      }
    
      public String getName() {
          return name;
      }
    
      public Long getId() {
          return id;
      }
    
      public void setId(Long id) {
          this.id = id;
      }
    }
    

    L'annotation @MappedEntity permet d'indiquer que l'entité est mise en correspondance avec une table de base de données. Par défaut, il s'agit d'une table portant le même nom que la classe (dans ce cas, owner).

    Les colonnes de la table sont représentées par chaque propriété Java. Dans le cas ci-dessus, une colonne id sera utilisée pour représenter la clé primaire et @GeneratedValue configurera le mapping pour prendre en compte l'utilisation d'une colonne identity dans Autonomous Database.

    L'annotation @Creator est utilisée sur le constructeur qui sera utilisé pour instancier l'entité mise en correspondance et qui est également utilisée pour exprimer les colonnes requises. Dans ce cas, la colonne name est requise et immuable tant que la colonne age n'est pas requise et peut être définie indépendamment à l'aide de la méthode set setAge.

  2. Créez un fichier Pet.java qui représentera l'entité Pet pour modéliser une table pet sous src/main/java/com/example :

    package com.example;
    
    import io.micronaut.core.annotation.Creator;
    import io.micronaut.core.annotation.Nullable;
    import io.micronaut.data.annotation.AutoPopulated;
    import io.micronaut.data.annotation.Id;
    import io.micronaut.data.annotation.MappedEntity;
    import io.micronaut.data.annotation.Relation;
    
    import java.util.UUID;
    
    @MappedEntity
    public class Pet {
    
        // This class uses an auto populated UUID for the primary key
        @Id
        @AutoPopulated
        private UUID id;
    
        private final String name;
    
        // A relation is defined between Pet and Owner
        @Relation(Relation.Kind.MANY_TO_ONE)
        private final Owner owner;
    
        private PetType type = PetType.DOG;
    
        // The constructor defines the columns to be read
        @Creator
        public Pet(String name, @Nullable Owner owner) {
            this.name = name;
            this.owner = owner;
        }
    
        public Owner getOwner() {
            return owner;
        }
    
        public String getName() {
            return name;
        }
    
        public UUID getId() {
            return id;
        }
    
        public void setId(UUID id) {
            this.id = id;
        }
    
        public PetType getType() {
            return type;
        }
    
        public void setType(PetType type) {
            this.type = type;
        }
    
        public enum PetType {
            DOG,
            CAT
        }
    }
    

    La classe Pet utilise une clé primaire UUID renseignée automatiquement pour illustrer les différentes approches de génération d'ID.

    Une relation entre la classe Pet et la classe Owner est également définie à l'aide de l'annotation @Relation(Relation.Kind.MANY_TO_ONE), ce qui indique qu'il s'agit d'une relation n à n.

    Cela étant, il est temps de passer à la définition des interfaces de référentiel pour implémenter des requêtes.

Etape 2 : Définir des référentiels de données Micronaut pour implémenter des requêtes

Les données Micronaut prennent en charge la notion de définition d'interfaces qui implémentent automatiquement des requêtes SQL pour vous au moment de la compilation à l'aide du modèle de référentiel de données. Dans cette section, vous allez tirer parti de cette fonctionnalité de données Micronaut.

  1. Créez un dossier distinct nommé repositories sous src/main/java/com/example.

  2. Définissez une nouvelle interface de référentiel qui s'étend à partir de CrudRepository et est annotée avec @JdbcRepository à l'aide du dialecte ORACLE dans un fichier appelé OwnerRepository.java :

    package com.example.repositories;
    
    import com.example.Owner;
    import io.micronaut.data.jdbc.annotation.JdbcRepository;
    import io.micronaut.data.model.query.builder.sql.Dialect;
    import io.micronaut.data.repository.CrudRepository;
    
    import java.util.List;
    import java.util.Optional;
    
    // The @JdbcRepository annotation indicates the database dialect
    @JdbcRepository(dialect = Dialect.ORACLE)
    public interface OwnerRepository extends CrudRepository<Owner, Long> {
    
        @Override
        List<Owner> findAll();
    
        // This method will compute at compilation time a query such as
        // SELECT ID, NAME, AGE FROM OWNER WHERE NAME = ?
        Optional<Owner> findByName(String name);
    }
    

    L'interface CrudRepository prend 2 types d'argument génériques. Le premier est le type de l'entité (dans ce cas, Owner) et le second est le type de l'ID (dans ce cas, Long).

    L'interface CrudRepository définit des méthodes permettant de créer, lire, mettre à jour et supprimer des entités (CRUD) de la base de données à l'aide des insertions, sélections, mises à jour et suppressions SQL appropriées calculées pour vous au moment de la compilation. Pour plus d'informations, reportez-vous à Javadoc pour CrudRepository.

    Vous pouvez définir des méthodes dans l'interface qui effectuent des requêtes JDBC et gérer automatiquement tous les détails complexes pour vous, tels que la définition d'une sémantique de transaction correcte (transactions en lecture seule pour les requêtes), l'exécution de la requête et la mise en correspondance de l'ensemble de résultats avec la classe d'entité Owner définie précédemment.

    La méthode findByName définie ci-dessus produira automatiquement une requête telle que SELECT ID, NAME, AGE FROM OWNER WHERE NAME = ? au moment de la compilation.

    Pour plus d'informations sur les méthodes de requête et les types de requête que vous pouvez définir, reportez-vous à la documentation relative aux méthodes de requête dans la documentation Micronaut Data.

  3. Une fois OwnerRepository en place, définissez un autre référentiel et, cette fois, utilisez un objet de transfert de données (DTO) pour effectuer une requête optimisée. Par conséquent, vous devez d'abord créer la classe DTO dans un fichier nommé NameDTO.java sous src/main/java/com/example/repositories :

    package com.example.repositories;
    
    import io.micronaut.core.annotation.Introspected;
    
    @Introspected
    public class NameDTO {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    

    Un DTO est un POJO simple qui vous permet de sélectionner uniquement les colonnes dont une requête particulière a besoin, produisant ainsi une requête plus optimisée.

  4. Définissez le référentiel appelé PetRepository dans un fichier appelé PetRepository.java pour l'entité Pet qui utilise le DTO au même emplacement src/main/java/com/example/repositories :

    package com.example.repositories;
    
    import com.example.Pet;
    import io.micronaut.data.annotation.Join;
    import io.micronaut.data.jdbc.annotation.JdbcRepository;
    import io.micronaut.data.model.query.builder.sql.Dialect;
    import io.micronaut.data.repository.PageableRepository;
    
    import java.util.List;
    import java.util.Optional;
    import java.util.UUID;
    
    @JdbcRepository(dialect = Dialect.ORACLE)
    public interface PetRepository extends PageableRepository<Pet, UUID> {
    
        List<NameDTO> list();
    
        @Join("owner")
        Optional<Pet> findByName(String name);
    }
    

    Notez la méthode list qui renvoie le DTO. Cette méthode sera à nouveau implémentée pour vous au moment de la compilation, mais cette fois-ci, au lieu d'extraire toutes les colonnes de la table Pet, elle extrait uniquement la colonne name et toutes les autres colonnes que vous pouvez définir.

    L'annotation @Join interroge et instancie l'objet joint (Owner) et l'affecte au champ Owner de l'objet interrogé Pet.

    La méthode findByName est également intéressante car elle utilise une autre caractéristique importante de Micronaut Data, qui est l'annotation @Join. Il vous permet de spécifier des chemins de jointure afin d'extraire exactement les données dont vous avez besoin via des jointures de base de données, ce qui permet d'obtenir des requêtes beaucoup plus efficaces.

Une fois les référentiels de données en place, passez à l'affichage des adresses REST.

Etape 3 : afficher les contrôleurs Micronaut en tant qu'adresses REST

Les adresses REST de Micronaut sont faciles à écrire et sont définies comme des contrôleurs (selon le modèle MVC).

  1. Créez un dossier controllers sous src/main/java/com/example/.

  2. Définissez une nouvelle classe OwnerController dans un fichier nommé OwnerController.java :

    package com.example.controllers;
    
    import java.util.List;
    import java.util.Optional;
    
    import javax.validation.constraints.NotBlank;
    
    import com.example.Owner;
    import com.example.repositories.OwnerRepository;
    import io.micronaut.http.annotation.Controller;
    import io.micronaut.http.annotation.Get;
    import io.micronaut.scheduling.TaskExecutors;
    import io.micronaut.scheduling.annotation.ExecuteOn;
    
    @Controller("/owners")
    @ExecuteOn(TaskExecutors.IO)
    class OwnerController {
    
        private final OwnerRepository ownerRepository;
    
        OwnerController(OwnerRepository ownerRepository) {
            this.ownerRepository = ownerRepository;
        }
    
        @Get("/")
        List<Owner> all() {
            return ownerRepository.findAll();
        }
    
        @Get("/{name}")
        Optional<Owner> byName(@NotBlank String name) {
            return ownerRepository.findByName(name);
        }
    }
    

    Une classe de contrôleur est définie avec l'annotation @Controller que vous pouvez utiliser pour définir l'URI racine avec lequel le contrôleur est mis en correspondance (dans ce cas, /owners).

    Notez l'annotation @ExecuteOn utilisée pour indiquer à Micronaut que le contrôleur effectue une communication d'E/S avec une base de données. Par conséquent, les opérations doivent être exécutées sur le pool de threads d'E/S.

    La classe OwnerController utilise l'injection de dépendance Micronaut pour obtenir une référence à l'interface de référentiel OwnerRepository que vous avez définie précédemment et est utilisée pour implémenter deux adresses :

    • / : l'adresse racine répertorie tous les propriétaires
    • /{name} : la deuxième adresse utilise un modèle d'URI pour permettre la recherche d'un propriétaire par son nom. La valeur de la variable d'URI {name} est fournie en tant que paramètre de la méthode byName.
  3. Définissez ensuite une autre adresse REST appelée PetController dans un fichier appelé PetController.java sous src/main/java/com/example/controllers :

    package com.example.controllers;
    
    import java.util.List;
    import java.util.Optional;
    
    import com.example.repositories.NameDTO;
    import com.example.Pet;
    import com.example.repositories.PetRepository;
    import io.micronaut.http.annotation.Controller;
    import io.micronaut.http.annotation.Get;
    import io.micronaut.scheduling.TaskExecutors;
    import io.micronaut.scheduling.annotation.ExecuteOn;
    
    @ExecuteOn(TaskExecutors.IO)
    @Controller("/pets")
    class PetController {
    
        private final PetRepository petRepository;
    
        PetController(PetRepository petRepository) {
            this.petRepository = petRepository;
        }
    
        @Get("/")
        List<NameDTO> all() {
            return petRepository.list();
        }
    
        @Get("/{name}")
        Optional<Pet> byName(String name) {
            return petRepository.findByName(name);
        }
    }
    

    Cette fois, PetRepository est injecté pour afficher la liste des animaux de compagnie et des animaux de compagnie par nom.

Etape 4 : Renseigner les données lors du démarrage de l'application

L'étape suivante consiste à renseigner certaines données d'application au démarrage. Pour ce faire, vous pouvez utiliser des événements d'application Micronaut.

Ouvrez la classe src/main/java/com/example/Application.java et remplacez le contenu initial du fichier par ce qui suit :

package com.example;

import com.example.repositories.OwnerRepository;
import com.example.repositories.PetRepository;
import io.micronaut.context.event.StartupEvent;
import io.micronaut.runtime.Micronaut;
import io.micronaut.runtime.event.annotation.EventListener;

import jakarta.inject.Singleton;
import javax.transaction.Transactional;
import java.util.Arrays;

@Singleton
public class Application {
    private final OwnerRepository ownerRepository;
    private final PetRepository petRepository;

    Application(OwnerRepository ownerRepository, PetRepository petRepository) {
        this.ownerRepository = ownerRepository;
        this.petRepository = petRepository;
    }

    public static void main(String[] args) {
        Micronaut.run(Application.class);
    }

    @EventListener
    @Transactional
    void init(StartupEvent event) {
        // clear out any existing data
        petRepository.deleteAll();
        ownerRepository.deleteAll();

        // create data
        Owner fred = new Owner("Fred");
        fred.setAge(45);
        Owner barney = new Owner("Barney");
        barney.setAge(40);
        ownerRepository.saveAll(Arrays.asList(fred, barney));

        Pet dino = new Pet("Dino", fred);
        Pet bp = new Pet("Baby Puss", fred);
        bp.setType(Pet.PetType.CAT);
        Pet hoppy = new Pet("Hoppy", barney);

        petRepository.saveAll(Arrays.asList(dino, bp, hoppy));
    }
}

Le constructeur est modifié pour injecter les définitions du référentiel de sorte que les données puissent être conservées.

Enfin, la méthode init est annotée avec @EventListener avec un argument pour recevoir StartupEvent. Cet événement est appelé une fois l'application en cours d'exécution et peut être utilisé pour la persistance des données lorsque l'application est prête à le faire.

Le reste de l'exemple illustre l'enregistrement de quelques entités à l'aide de la méthode saveAll de l'interface CrudRepository.

Notez que javax.transaction.Transactional est déclaré sur la méthode, ce qui garantit que Micronaut Data encapsule l'exécution de la méthode init dans une transaction JDBC qui est annulée si une exception survient lors de l'exécution de la méthode.

Etape 5 : exécutez des tests d'intégration pour l'application Micronaut.

L'application a déjà été configurée avec un seul test qui vérifie si l'application peut démarrer correctement (et testera donc la logique de la méthode init définie dans la section précédente).

  1. Dans la barre de navigation supérieure, accédez à Terminal, puis à New Terminal.

  2. Exécutez l'objectif test pour exécuter les tests :

    ./mvnw test
    

    Voir la structure des fichiers et exécuter des tests dans une fenêtre de terminal dans VS Code

    Si vous utilisez Gradle, vous pouvez également exécuter la tâche test :

    ./gradlew test
    

    Le message BUILD SUCCESS doit s'afficher à la fin de l'exécution du test.

Vous pouvez maintenant passer à la tâche suivante.

En savoir plus