Remarque :
- Ce tutoriel est disponible dans un environnement de laboratoire gratuit fourni par Oracle.
- Il utilise des exemples de valeur pour les informations d'identification, la location et les compartiments Oracle Cloud Infrastructure. A la fin de l'exercice, remplacez ces valeurs par celles propres à votre environnement cloud.
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 :
- Créer des entités de données Micronaut qui sont mises en correspondance avec les tables Oracle Database
- Définir des référentiels de données Micronaut pour implémenter des requêtes
- Exposer les contrôleurs Micronaut en tant qu'adresses REST
- Renseigner les données lors du démarrage de l'application
- Exécuter des tests d'intégration pour l'application Micronaut
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.
-
Créez une classe d'entité qui représentera un élément
Owner
dans le packagesrc/main/java/com/example
. Cliquez avec le bouton droit de la souris sursrc/main/java/com/example
pour développer le menu de contenu, sélectionnez New File, nommez-leOwner.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 colonneidentity
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 colonnename
est requise et immuable tant que la colonneage
n'est pas requise et peut être définie indépendamment à l'aide de la méthode setsetAge
. -
Créez un fichier
Pet.java
qui représentera l'entitéPet
pour modéliser une tablepet
soussrc/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é primaireUUID
renseignée automatiquement pour illustrer les différentes approches de génération d'ID.Une relation entre la classe
Pet
et la classeOwner
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.
-
Créez un dossier distinct nommé
repositories
soussrc/main/java/com/example
. -
Définissez une nouvelle interface de référentiel qui s'étend à partir de
CrudRepository
et est annotée avec@JdbcRepository
à l'aide du dialecteORACLE
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 queSELECT 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.
-
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
soussrc/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.
-
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 emplacementsrc/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 tablePet
, elle extrait uniquement la colonnename
et toutes les autres colonnes que vous pouvez définir.L'annotation
@Join
interroge et instancie l'objet joint (Owner
) et l'affecte au champOwner
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).
-
Créez un dossier
controllers
soussrc/main/java/com/example/
. -
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érentielOwnerRepository
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éthodebyName
.
-
Définissez ensuite une autre adresse REST appelée
PetController
dans un fichier appeléPetController.java
soussrc/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).
-
Dans la barre de navigation supérieure, accédez à Terminal, puis à New Terminal.
-
Exécutez l'objectif
test
pour exécuter les tests :./mvnw test
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.