Nota:
- Questa esercitazione è disponibile in un ambiente di laboratorio gratuito fornito da Oracle.
- Utilizza valori di esempio per le credenziali, la tenancy e i compartimenti Oracle Cloud Infrastructure. Al termine del laboratorio, sostituire questi valori con quelli specifici del tuo ambiente cloud.
TASK 4: Implementa le query di database e crea un'applicazione Micronaut
In questo laboratorio implementerai le query di database e creerai un'applicazione Micronaut locale che si connette a Oracle Autonomous Database.
Tempo stimato: 30 minuti
Contenuto task
In questo task sarà possibile:
- Crea entità di dati Micronaut che mappano alle tabelle di Oracle Database
- Definire i repository di dati Micronaut per implementare le query
- Esponi controller Micronaut come endpoint REST
- Popola dati all'avvio dell'applicazione
- Eseguire i test di integrazione per l'applicazione Micronaut
Passo 1: creare entità di dati Micronaut mappate alle tabelle di Oracle Database
Nel task precedente è stato aggiunto lo script SQL che creerebbe una tabella denominata OWNER e una tabella denominata PET una volta eseguita. Successivamente è necessario definire le classi di entità che possono essere utilizzate per leggere i dati dalle tabelle di database.
-
Creare una classe di entità che rappresenterà un
Ownernel pacchettosrc/main/java/com/example. Fare clic con il pulsante destro del mouse susrc/main/java/com/exampleper espandere il menu del contenuto, selezionare Nuovo file, assegnare il nomeOwner.javae incollare il codice seguente:
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'annotazione
@MappedEntityviene utilizzata per indicare che l'entità è mappata a una tabella di database. Per impostazione predefinita, si tratta di una tabella che utilizza lo stesso nome della classe (in questo casoowner).Le colonne della tabella sono rappresentate da ogni proprietà Java. Nel caso precedente verrà utilizzata una colonna
idper rappresentare la chiave primaria e@GeneratedValueimposterà il mapping per utilizzare una colonnaidentityin Autonomous Database.L'annotazione
@Creatorviene utilizzata nel costruttore che verrà utilizzata per creare un'istanza dell'entità mappata ed è utilizzata anche per esprimere le colonne richieste. In questo caso la colonnanameè obbligatoria e immutabile mentre la colonnaagenon è obbligatoria e può essere impostata in modo indipendente utilizzando il settersetAge. -
Creare un file
Pet.javache rappresenterà l'entitàPetper modellare una tabellapetinsrc/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 } }Si noti che la classe
Petutilizza automaticamenteUUIDpopolata come chiave primaria per dimostrare gli approcci diversi alla generazione degli ID.Una relazione tra la classe
Pete la classeOwnerviene definita anche mediante l'annotazione@Relation(Relation.Kind.MANY_TO_ONE), a indicare che si tratta di una relazione molti-a-uno.A tal fine, è tempo di passare alla definizione delle interfacce di repository per implementare le query.
Passo 2: definire i repository di dati Micronaut per implementare le query.
I dati di Micronaut supportano la nozione di definizione di interfacce che implementano automaticamente query SQL in fase di compilazione utilizzando il pattern del repository di dati. In questa sezione potrete usufruire di questa funzione di dati Micronaut.
-
Creare una cartella separata denominata
repositoriesinsrc/main/java/com/example. -
Definire una nuova interfaccia di repository che si estende da
CrudRepositorye viene annotata con@JdbcRepositoryutilizzando il dialettoORACLEin un file denominatoOwnerRepository.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'interfaccia
CrudRepositoryutilizza due tipi di argomenti generici. Il primo è il tipo di entità (in questo casoOwner) e il secondo è il tipo di ID (in questo casoLong).L'interfaccia
CrudRepositorydefinisce metodi che consentono di creare, leggere, aggiornare ed eliminare entità (CRUD) dal database con gli inserimenti, le selezioni, gli aggiornamenti e le eliminazioni SQL appropriati calcolati in fase di compilazione. Per ulteriori informazioni, vedere Javadoc per CrudRepository.È possibile definire i metodi all'interno dell'interfaccia che eseguono query JDBC e gestire automaticamente tutti i dettagli intricati, ad esempio la definizione della semantica transazione corretta (transazioni di sola lettura per le query), l'esecuzione della query e il mapping del set di risultati alla classe entità
Ownerdefinita in precedenza.Il metodo
findByNamedefinito in precedenza genererà una query comeSELECT ID, NAME, AGE FROM OWNER WHERE NAME = ?automaticamente al momento della compilazione.Per ulteriori informazioni sui metodi di query e sui tipi di query che è possibile definire, vedere la documentazione per i metodi di query nella documentazione sui dati Micronaut.
-
In
OwnerRepositorydefinire un altro repository e questa volta utilizzando un oggetto di trasferimento dati (DTO) per eseguire una query ottimizzata. Per prima cosa, è necessario creare la classe DTO in un file denominatoNameDTO.javainsrc/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 è un POJO semplice che consente di selezionare solo le colonne necessarie per una determinata query, producendo una query più ottimizzata.
-
Definire il repository denominato
PetRepositoryin un file denominatoPetRepository.javaper l'entitàPetche utilizza il DTO nella stessa posizionesrc/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); }Prendere nota del metodo
listche restituisce il DTO. Questo metodo verrà di nuovo implementato in fase di compilazione, ma al posto di recuperare tutte le colonne della tabellaPet, recupererà solo la colonnanamee qualsiasi altra colonna che è possibile definire.L'annotazione
@Joineseguirà una query e creerà un'istanza dell'oggetto unito (Owner) e l'assegnerà al campoOwnerdell'interrogazionePet.Anche il metodo
findByNameè interessante in quanto utilizza un'altra caratteristica importante dei dati Micronaut, ovvero l'annotazione@Join. Consente di specificare percorsi di join in modo da recuperare esattamente i dati necessari tramite join di database, ottenendo query molto più efficienti.
Con i repository di dati esistenti, puoi passare all'esposizione degli endpoint REST.
Passo 3: espone i controller Micronaut come endpoint REST
Gli endpoint REST presenti in Micronaut sono facili da scrivere e vengono definiti come controller (in base al pattern MVC).
-
Creare una cartella
controllersinsrc/main/java/com/example/. -
Definire una nuova classe
OwnerControllerin un file denominatoOwnerController.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); } }Una classe di controller è definita con l'annotazione
@Controllerche è possibile utilizzare per definire l'URI root a cui il controller esegue il mapping (in questo caso/owners).Notare l'annotazione
@ExecuteOnutilizzata per indicare a Micronaut che il controller esegue la comunicazione I/O con un database e, di conseguenza, le operazioni devono eseguire il pool di thread I/O.La classe
OwnerControllerutilizza l'iniezione di dipendenza Autonomo per ottenere un riferimento all'interfaccia repositoryOwnerRepositorydefinita in precedenza e utilizzata per implementare due endpoint:/: l'endpoint root elenca tutti i proprietari/{name}: il secondo endpoint utilizza un modello URI per consentire la ricerca di un proprietario in base al nome. Il valore della variabile URI{name}viene fornito come parametro del metodobyName.
-
Definire un altro endpoint REST denominato
PetControllerin un file denominatoPetController.javainsrc/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); } }Questa volta viene iniettato il file
PetRepositoryper esporre un elenco di animali domestici e animali domestici per nome.
Passo 4: popolamento dei dati all'avvio dell'applicazione
Il passo successivo consiste nel popolare alcuni dati dell'applicazione all'avvio. A tale scopo, è possibile utilizzare gli eventi dell'applicazione Micronaut.
Aprire la classe src/main/java/com/example/Application.java e sostituire il contenuto del file iniziale con quanto segue:
![]()
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));
}
}
Tenere presente che il costruttore viene modificato per inserire le definizioni del repository in base alle dipendenze in modo da rendere persistenti i dati.
Infine, il metodo init viene annotato con @EventListener con un argomento per ricevere un StartupEvent. Questo evento viene chiamato quando l'applicazione è attiva e in esecuzione e può essere utilizzato per rendere persistenti i dati quando l'applicazione è pronta a farlo.
La parte restante dell'esempio mostra il salvataggio di alcune entità utilizzando il metodo saveAll dell'interfaccia CrudRepository.
Si noti che javax.transaction.Transactional è dichiarato nel metodo che garantisce che Micronaut Data avvolga l'esecuzione del metodo init in una transazione JDBC di cui viene eseguito il rollback se si verifica un'eccezione durante l'esecuzione del metodo.
Passo 5: esegue i test di integrazione per l'applicazione Micronaut
L'applicazione è già stata impostata con un singolo test che verifica se l'avvio dell'applicazione è riuscito, quindi eseguirà il test della logica del metodo init definito nella sezione precedente.
-
Dalla navigazione in alto, passare a Terminale, quindi a Nuovo terminale.
-
Eseguire l'obiettivo
testper eseguire i test:
./mvnw test
In alternativa, se si utilizza Gradle, eseguire il task
test:
./gradlew testAl termine dell'esecuzione del test, vedere il messaggio BUILD SUCCESS.
È ora possibile passare al task successivo.