Hinweis:
- Dieses Tutorial steht in einer von Oracle bereitgestellten kostenlosen Übungsumgebung zur Verfügung.
- Er verwendet Beispielwerte für Oracle Cloud Infrastructure-Zugangsdaten, Mandanten und Compartments. Ersetzen Sie diese Werte beim Durchführen Ihrer Übung durch spezifische Werte für Ihre Cloud-Umgebung.
TASK 4: Datenbankabfragen implementieren und Micronaut-Anwendung erstellen
In dieser Übung implementieren Sie Datenbankabfragen und erstellen lokal eine Micronaut-Anwendung, die sich mit Oracle Autonomous Database verbindet.
Voraussichtliche Zeit: 30 Minuten
Aufgabeninhalt
In dieser Aufgabe führen Sie folgende Schritte aus:
- Mikronaut-Datenentitys erstellen, die Oracle Database-Tabellen zugeordnet sind
- Micronaut-Daten-Repositorys zur Implementierung von Abfragen definieren
- Micronaut-Controller als REST-Endpunkte verfügbar machen
- Daten beim Anwendungsstart auffüllen
- Führen Sie Integrationstests für die Micronaut-Anwendung aus
Schritt 1: Mikronaut-Datenentitys erstellen, die Oracle Database-Tabellen zugeordnet sind
In der vorherigen Aufgabe haben Sie das SQL-Skript hinzugefügt, das eine Tabelle mit dem Namen OWNER
und eine Tabelle mit dem Namen PET
nach der Ausführung erstellt. Als Nächstes müssen Sie Entity-Klassen definieren, mit denen Daten aus den Datenbanktabellen gelesen werden können.
-
Erstellen Sie eine Entityklasse, die eine
Owner
im Packagesrc/main/java/com/example
darstellt. Klicken Sie mit der rechten Maustaste aufsrc/main/java/com/example
, um das Inhaltsmenü einzublenden, wählen Sie "Neue Datei", benennen Sie sieOwner.java
, und fügen Sie den folgenden Code ein: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; } }
Mit der Annotation
@MappedEntity
wird angegeben, dass die Entity einer Datenbanktabelle zugeordnet ist. Standardmäßig ist dies eine Tabelle, die denselben Namen wie die Klasse verwendet (in diesem Fallowner
).Die Spalten der Tabelle werden durch jede Java-Eigenschaft dargestellt. Im obigen Fall wird eine
id
-Spalte verwendet, um den Primärschlüssel darzustellen, und@GeneratedValue
richtet das Mapping so ein, dass eineidentity
-Spalte in Autonomous Database verwendet wird.Die Annotation
@Creator
wird für den Konstruktor verwendet, mit dem die zugeordnete Entity instanziiert wird. Außerdem werden erforderliche Spalten ausgedrückt. In diesem Fall ist die Spaltename
erforderlich und unveränderbar, während die Spalteage
nicht erforderlich ist und unabhängig mit dem SettersetAge
festgelegt werden kann. -
Erstellen Sie eine
Pet.java
-Datei, die diePet
-Entity darstellt, um einepet
-Tabelle untersrc/main/java/com/example
zu modellieren: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 } }
Beachten Sie, dass die Klasse
Pet
eine automatisch aufgefüllteUUID
als Primärschlüssel verwendet, um unterschiedliche Ansätze bei der ID-Generierung darzustellen.Eine Beziehung zwischen der Klasse
Pet
und der KlasseOwner
wird auch mit der Annotation@Relation(Relation.Kind.MANY_TO_ONE)
definiert. Dies bedeutet, dass es sich um eine Eins-zu-Eins-Beziehung handelt.Danach ist es an der Zeit, zur Definition von Repository-Schnittstellen zu wechseln, um Abfragen zu implementieren.
Schritt 2: Mikronaut-Daten-Repositorys zum Implementieren von Abfragen definieren
Micronaut Data unterstützt das Konzept der Definition von Schnittstellen, die SQL-Abfragen automatisch bei der Kompilierung mithilfe des Daten-Repository-Musters implementieren. In diesem Abschnitt nutzen Sie diese Micronaut-Datenfunktion.
-
Erstellen Sie einen separaten Ordner mit dem Namen
repositories
untersrc/main/java/com/example
. -
Definieren Sie eine neue Repository-Schnittstelle, die sich von
CrudRepository
erstreckt und mit@JdbcRepository
mit dem DialektORACLE
in einer Datei mit dem NamenOwnerRepository.java
versehen wird: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); }
Die Schnittstelle
CrudRepository
benötigt 2 generische Argumenttypen. Der erste Typ ist der Typ der Entity (in diesem FallOwner
), der zweite der Typ der ID (in diesem FallLong
).Die Schnittstelle
CrudRepository
definiert Methoden, mit denen Sie CRUD-Entitys mit den entsprechenden SQL-Einfügungen, -Auswahlen, -Updates und -Löschvorgängen erstellen, lesen, aktualisieren und aus der Datenbank löschen können, die zur Kompilierungszeit für Sie berechnet wurden. Weitere Informationen finden Sie im Javadoc für CrudRepository.Sie können Methoden innerhalb der Schnittstelle definieren, die JDBC-Abfragen ausführen und automatisch alle komplexen Details für Sie verarbeiten, wie die korrekte Transaktionssemantik (schreibgeschützte Transaktionen für Abfragen), die Abfrage ausführen und die Ergebnismenge der zuvor definierten Entityklasse
Owner
zuordnen.Die oben definierte Methode
findByName
erzeugt automatisch eine Abfrage wieSELECT ID, NAME, AGE FROM OWNER WHERE NAME = ?
zur Kompilierungszeit.Weitere Informationen zu Abfragemethoden und den Abfragetypen, die Sie definieren können, finden Sie in der Dokumentation für Abfragemethoden in der Dokumentation zu Micronaut Data.
-
Definieren Sie mit der vorhandenen
OwnerRepository
ein anderes Repository. Verwenden Sie dieses Mal ein Datenübertragungsobjekt (DTO), um eine optimierte Abfrage auszuführen. Zuerst müssen Sie die DTO-Klasse in einer Datei mit dem NamenNameDTO.java
untersrc/main/java/com/example/repositories
erstellen: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; } }
Ein DTO ist ein einfacher POJO, mit dem Sie nur die Spalten auswählen können, die eine bestimmte Abfrage benötigt, wodurch eine optimiertere Abfrage erzeugt wird.
-
Definieren Sie das Repository mit dem Namen
PetRepository
in einer Datei mit dem NamenPetRepository.java
für die EntityPet
, die das DTO im selben Speicherort verwendet: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); }
Notieren Sie sich die Methode
list
, die das DTO zurückgibt. Diese Methode wird bei der Kompilierung erneut für Sie implementiert. Anstatt jedoch alle Spalten der TabellePet
abzurufen, ruft sie nur die Spaltename
und alle anderen Spalten ab, die Sie definieren können.Die Annotation
@Join
führt eine Abfrage und Instanziierung des verknüpften Objekts (Owner
) durch und weist es dem FeldOwner
der abgefragtenPet
zu.Die Methode
findByName
ist auch interessant, da sie ein weiteres wichtiges Feature von Micronaut Data verwendet, das die Annotation@Join
ist. Damit können Sie Join-Pfade angeben, sodass Sie genau die Daten abrufen können, die Sie über Datenbank-Joins benötigen. Dies führt zu wesentlich effizienteren Abfragen.
Setzen Sie die Daten-Repositorys ein, und geben Sie REST-Endpunkte an.
Schritt 3: Micronaut-Controller als REST-Endpunkte angeben
REST-Endpunkte in Micronaut sind einfach zu schreiben und werden als Controller (gemäß dem MVC-Muster) definiert.
-
Erstellen Sie einen Ordner
controllers
untersrc/main/java/com/example/
. -
Definieren Sie eine neue
OwnerController
-Klasse in einer Datei mit dem NamenOwnerController.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); } }
Eine Controllerklasse wird mit der Annotation
@Controller
definiert, mit der Sie die Root-URI definieren können, der dem Controller zugeordnet wird (in diesem Fall/owners
).Beachten Sie die Annotation
@ExecuteOn
, mit der Micronaut mitgeteilt wird, dass der Controller I/O-Kommunikation mit einer Datenbank ausführt. Daher sollten Vorgänge auf dem I/O-Threadpool ausgeführt werden.Die Klasse
OwnerController
verwendet Micronaut Dependency Injection, um eine Referenz auf die zuvor definierteOwnerRepository
-Repository-Schnittstelle abzurufen und zwei Endpunkte zu implementieren:/
: Der Root-Endpunkt listet alle Eigentümer auf/{name}
: Der zweite Endpunkt verwendet eine URI-Vorlage, damit ein Eigentümer nach Name gesucht werden kann. Der Wert der URI-Variablen{name}
wird als Parameter für die MethodebyName
angegeben.
-
Definieren Sie als Nächstes einen weiteren REST-Endpunkt mit dem Namen
PetController
in einer Datei mit dem NamenPetController.java
untersrc/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); } }
Diesmal wird die
PetRepository
injiziert, um eine Liste von Haustieren und Haustieren nach Namen anzugeben.
Schritt 4: Daten beim Anwendungsstart auffüllen
Im nächsten Schritt werden beim Start einige Anwendungsdaten aufgefüllt. Dazu können Sie Micronaut-Anwendungsereignisse verwenden.
Öffnen Sie die Klasse src/main/java/com/example/Application.java
, und ersetzen Sie den ursprünglichen Dateiinhalt durch Folgendes:
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));
}
}
Beachten Sie, dass der Konstruktor in Abhängigkeit geändert wird, um die Repository-Definitionen zu injizieren, damit Daten dauerhaft gespeichert werden können.
Schließlich wird die Methode init
mit @EventListener
mit einem Argument versehen, um eine StartupEvent
zu empfangen. Dieses Ereignis wird aufgerufen, sobald die Anwendung hochgefahren und gestartet ist. Es kann verwendet werden, um Daten zu persistieren, wenn die Anwendung dazu bereit ist.
Im weiteren Beispiel wird gezeigt, wie einige Entitys mit der Methode saveAll der Schnittstelle CrudRepository gespeichert werden.
Beachten Sie, dass javax.transaction.Transactional
für die Methode deklariert wird, die sicherstellt, dass Micronaut Data die Ausführung der Methode init
in einer JDBC-Transaktion wrappt, die zurückgesetzt wird, wenn eine Ausnahme während der Ausführung der Methode auftritt.
Schritt 5: Integrationstests für die Micronaut-Anwendung ausführen
Die Anwendung wurde bereits mit einem einzelnen Test eingerichtet, der prüft, ob die Anwendung erfolgreich hochgefahren werden kann (und daher die Logik der im vorherigen Abschnitt definierten Methode init
testet).
-
Gehen Sie in der oberen Navigationsleiste zu Terminal und dann zu New Terminal.
-
Führen Sie das Ziel
test
aus, um Tests auszuführen:./mvnw test
Wenn Sie Gradle verwenden, führen Sie alternativ die Aufgabe
test
aus:./gradlew test
Die Meldung BUILD SUCCESS sollte am Ende der Testausführung angezeigt werden.
Sie können jetzt mit der nächsten Aufgabe fortfahren.