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
Ownerim Packagesrc/main/java/com/exampledarstellt. 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
@MappedEntitywird 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@GeneratedValuerichtet das Mapping so ein, dass eineidentity-Spalte in Autonomous Database verwendet wird.Die Annotation
@Creatorwird für den Konstruktor verwendet, mit dem die zugeordnete Entity instanziiert wird. Außerdem werden erforderliche Spalten ausgedrückt. In diesem Fall ist die Spaltenameerforderlich und unveränderbar, während die Spalteagenicht erforderlich ist und unabhängig mit dem SettersetAgefestgelegt werden kann. -
Erstellen Sie eine
Pet.java-Datei, die diePet-Entity darstellt, um einepet-Tabelle untersrc/main/java/com/examplezu 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
Peteine automatisch aufgefüllteUUIDals Primärschlüssel verwendet, um unterschiedliche Ansätze bei der ID-Generierung darzustellen.Eine Beziehung zwischen der Klasse
Petund der KlasseOwnerwird 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
repositoriesuntersrc/main/java/com/example. -
Definieren Sie eine neue Repository-Schnittstelle, die sich von
CrudRepositoryerstreckt und mit@JdbcRepositorymit dem DialektORACLEin einer Datei mit dem NamenOwnerRepository.javaversehen 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
CrudRepositorybenö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
CrudRepositorydefiniert 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
Ownerzuordnen.Die oben definierte Methode
findByNameerzeugt 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
OwnerRepositoryein 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.javauntersrc/main/java/com/example/repositorieserstellen:
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
PetRepositoryin einer Datei mit dem NamenPetRepository.javafü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 TabellePetabzurufen, ruft sie nur die Spaltenameund alle anderen Spalten ab, die Sie definieren können.Die Annotation
@Joinführt eine Abfrage und Instanziierung des verknüpften Objekts (Owner) durch und weist es dem FeldOwnerder abgefragtenPetzu.Die Methode
findByNameist auch interessant, da sie ein weiteres wichtiges Feature von Micronaut Data verwendet, das die Annotation@Joinist. 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
controllersuntersrc/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
@Controllerdefiniert, 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
OwnerControllerverwendet 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 MethodebyNameangegeben.
-
Definieren Sie als Nächstes einen weiteren REST-Endpunkt mit dem Namen
PetControllerin einer Datei mit dem NamenPetController.javauntersrc/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
PetRepositoryinjiziert, 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
testaus, um Tests auszuführen:
./mvnw test
Wenn Sie Gradle verwenden, führen Sie alternativ die Aufgabe
testaus:
./gradlew testDie Meldung BUILD SUCCESS sollte am Ende der Testausführung angezeigt werden.
Sie können jetzt mit der nächsten Aufgabe fortfahren.