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
Owner
nel pacchettosrc/main/java/com/example
. Fare clic con il pulsante destro del mouse susrc/main/java/com/example
per espandere il menu del contenuto, selezionare Nuovo file, assegnare il nomeOwner.java
e 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
@MappedEntity
viene 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
id
per rappresentare la chiave primaria e@GeneratedValue
imposterà il mapping per utilizzare una colonnaidentity
in Autonomous Database.L'annotazione
@Creator
viene 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 colonnaage
non è obbligatoria e può essere impostata in modo indipendente utilizzando il settersetAge
. -
Creare un file
Pet.java
che rappresenterà l'entitàPet
per modellare una tabellapet
insrc/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
Pet
utilizza automaticamenteUUID
popolata come chiave primaria per dimostrare gli approcci diversi alla generazione degli ID.Una relazione tra la classe
Pet
e la classeOwner
viene 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
repositories
insrc/main/java/com/example
. -
Definire una nuova interfaccia di repository che si estende da
CrudRepository
e viene annotata con@JdbcRepository
utilizzando il dialettoORACLE
in 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
CrudRepository
utilizza 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
CrudRepository
definisce 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à
Owner
definita in precedenza.Il metodo
findByName
definito 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
OwnerRepository
definire 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.java
insrc/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
PetRepository
in un file denominatoPetRepository.java
per l'entitàPet
che 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
list
che 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 colonnaname
e qualsiasi altra colonna che è possibile definire.L'annotazione
@Join
eseguirà una query e creerà un'istanza dell'oggetto unito (Owner
) e l'assegnerà al campoOwner
dell'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
controllers
insrc/main/java/com/example/
. -
Definire una nuova classe
OwnerController
in 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
@Controller
che è possibile utilizzare per definire l'URI root a cui il controller esegue il mapping (in questo caso/owners
).Notare l'annotazione
@ExecuteOn
utilizzata 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
OwnerController
utilizza l'iniezione di dipendenza Autonomo per ottenere un riferimento all'interfaccia repositoryOwnerRepository
definita 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
PetController
in un file denominatoPetController.java
insrc/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
PetRepository
per 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
test
per eseguire i test:./mvnw test
In alternativa, se si utilizza Gradle, eseguire il task
test
:./gradlew test
Al termine dell'esecuzione del test, vedere il messaggio BUILD SUCCESS.
È ora possibile passare al task successivo.