Nota:
- Este tutorial está disponible en un entorno de prácticas gratuitas proporcionado por Oracle.
- Utiliza valores de ejemplo para credenciales, arrendamiento y compartimentos de Oracle Cloud Infrastructure. Al finalizar el laboratorio, sustituya estos valores por valores específicos de su entorno en la nube.
TAREA 4: Implantación de consultas de base de datos y creación de una aplicación Micronaut
En este laboratorio, implantará consultas de base de datos y creará una aplicación Micronaut localmente que se conecte a Oracle Autonomous Database.
Tiempo estimado: 30 minutos
Contenido de tarea
En esta tarea:
- Crear entidades de datos de Micronaut que se asignen a tablas de Oracle Database
- Definir repositorios de datos de Micronaut para implantar consultas
- Exponer a los controladores de Micronaut como puntos finales de REST
- Rellenar datos en inicio de aplicación
- Ejecución de pruebas de integración para la aplicación Micronaut
Paso 1: Crear entidades de datos Micronaut que se asignan a tablas de Oracle Database
En la tarea anterior, ha agregado el script SQL que crearía una tabla denominada OWNER
y una tabla denominada PET
una vez ejecutada. A continuación, debe definir las clases de entidad que se pueden utilizar para leer datos de las tablas de la base de datos.
-
Cree una clase de entidad que represente un
Owner
en el paquetesrc/main/java/com/example
. Haga clic con el botón derecho ensrc/main/java/com/example
para ampliar el menú de contenido, seleccione Nuevo archivo, asígnele el nombreOwner.java
y pegue el siguiente código: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; } }
La anotación
@MappedEntity
se utiliza para indicar que la entidad está asignada a una tabla de base de datos. Por defecto, será una tabla con el mismo nombre que la clase (en este casoowner
).Las columnas de la tabla están representadas por cada propiedad de Java. En el caso anterior, se utilizará una columna
id
para representar la clave primaria y@GeneratedValue
configurará la asignación para asumir el uso de una columnaidentity
en Autonomous Database.La anotación
@Creator
se utiliza en el constructor que se utilizará para instanciar la entidad asignada y también se utiliza para expresar las columnas necesarias. En este caso, la columnaname
es necesaria e inmutable mientras que la columnaage
no es necesaria y se puede definir de forma independiente mediante el settersetAge
. -
Cree un archivo
Pet.java
que represente la entidadPet
para modelar una tablapet
ensrc/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 } }
Tenga en cuenta que la clase
Pet
utiliza unaUUID
rellenada automáticamente como clave primaria para demostrar diferentes enfoques en la generación de ID.Una relación entre la clase
Pet
y la claseOwner
también se define mediante la anotación@Relation(Relation.Kind.MANY_TO_ONE)
, lo que indica que se trata de una relación de varios a uno.Con ello, es hora de pasar a definir interfaces de repositorio para implantar consultas.
Paso 2: Definición de repositorios de datos de Micronaut para implantar consultas
Micronaut Data soporta la noción de definición de interfaces que implantan automáticamente consultas SQL para el usuario en el momento de la compilación mediante el patrón del repositorio de datos. En esta sección, aprovechará esta función Micronaut Data.
-
Cree una carpeta independiente denominada
repositories
ensrc/main/java/com/example
. -
Defina una nueva interfaz de repositorio que se amplíe desde
CrudRepository
y se anote con@JdbcRepository
mediante el dialectoORACLE
en un archivo denominadoOwnerRepository.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); }
La interfaz
CrudRepository
toma 2 tipos de argumentos genéricos. El primero es el tipo de entidad (en este casoOwner
) y el segundo es el tipo de ID (en este casoLong
).La interfaz
CrudRepository
define métodos que permiten crear, leer, actualizar y suprimir entidades (CRUD) de la base de datos con las inserciones, selecciones, actualizaciones y supresiones SQL adecuadas calculadas en el momento de la compilación. Para obtener más información, consulte el Javadoc de CrudRepository.Puede definir métodos dentro de la interfaz que realizan consultas JDBC y manejan automáticamente todos los detalles intrincados, como la definición de semántica de transacción correcta (transacciones de sólo lectura para consultas), la ejecución de la consulta y la asignación del juego de resultados a la clase de entidad
Owner
definida anteriormente.El método
findByName
definido anteriormente producirá una consulta comoSELECT ID, NAME, AGE FROM OWNER WHERE NAME = ?
automáticamente en el momento de la compilación.Para obtener más información sobre los métodos de consulta y los tipos de consultas que puede definir, consulte la documentación de los métodos de consulta en la documentación de datos de Micronaut.
-
Con
OwnerRepository
en su lugar, defina otro repositorio y esta vez mediante un objeto de transferencia de datos (DTO) para realizar una consulta optimizada. Por lo tanto, primero debe crear la clase DTO en un archivo denominadoNameDTO.java
ensrc/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 es un POJO simple que permite seleccionar solo las columnas que necesita una consulta concreta, produciendo así una consulta más optimizada.
-
Defina el repositorio denominado
PetRepository
en un archivo denominadoPetRepository.java
para la entidadPet
que utiliza el DTO en la misma ubicaciónsrc/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); }
Tome nota del método
list
que devuelve el DTO. Este método se volverá a implantar en el momento de la compilación, pero esta vez en lugar de recuperar todas las columnas de la tablaPet
, solo recuperará la columnaname
y cualquier otra columna que pueda definir.La anotación
@Join
consultará e instanciará el objeto unido (Owner
) y lo asignará al campoOwner
dePet
consultado.El método
findByName
también es interesante, ya que utiliza otra característica importante de Micronaut Data, que es la anotación@Join
. Permite especificar rutas de acceso de unión para recuperar exactamente los datos que necesita mediante uniones de base de datos, lo que da como resultado consultas mucho más eficientes.
Con los repositorios de datos en su lugar, continúe con la exposición de puntos finales de REST.
Paso 3: Exponer los controladores de Micronaut como puntos finales de REST
Los puntos finales de REST en Micronaut son fáciles de escribir y se definen como controllers (según el patrón MVC).
-
Cree una carpeta
controllers
ensrc/main/java/com/example/
. -
Defina una nueva clase
OwnerController
en un archivo denominadoOwnerController.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 clase de controlador se define con la anotación
@Controller
que puede utilizar para definir el URI raíz al que se asigna el controlador (en este caso/owners
).Observe la anotación
@ExecuteOn
que se utiliza para indicar a Micronaut que el controlador realiza la comunicación de E/S con una base de datos y, por lo tanto, las operaciones deben ejecutar en el pool de threads de E/S.La clase
OwnerController
utiliza la inyección de dependencia de micras para obtener una referencia a la interfaz de repositorioOwnerRepository
definida anteriormente y se utiliza para implementar dos puntos finales:/
: el punto final raíz muestra todos los propietarios/{name}
: el segundo punto final utiliza una plantilla de URI para permitir buscar un propietario por nombre. El valor de la variable de URI{name}
se proporciona como parámetro al métodobyName
.
-
A continuación, defina otro punto final de REST denominado
PetController
en un archivo denominadoPetController.java
ensrc/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); } }
Esta vez se inyecta
PetRepository
para exponer una lista de mascotas y mascotas por nombre.
Paso 4: Rellenar datos en el inicio de la aplicación
El siguiente paso es rellenar algunos datos de la aplicación al iniciar. Para ello, puede utilizar Micronaut application events.
Abra la clase src/main/java/com/example/Application.java
y sustituya el contenido inicial del archivo por lo siguiente:
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));
}
}
Tenga en cuenta que el constructor se modifica para que la dependencia inyecte las definiciones del repositorio para que se puedan mantener los datos.
Por último, el método init
se anota con @EventListener
con un argumento para recibir StartupEvent
. Este evento se llama una vez que la aplicación está activa y en ejecución, y se puede utilizar para mantener los datos cuando la aplicación esté lista para hacerlo.
En el resto del ejemplo, se muestra cómo guardar algunas entidades con el método saveAll de la interfaz CrudRepository.
Observe que javax.transaction.Transactional
se declara en el método que garantiza que Micronaut Data encapsula la ejecución del método init
en una transacción JDBC de la que se realiza un rollback si se produce una excepción durante la ejecución del método.
Paso 5: Ejecución de pruebas de integración para la aplicación Micronaut
La aplicación ya se ha configurado con una única prueba que comprueba si la aplicación se puede iniciar correctamente (y, por lo tanto, probará la lógica del método init
definido en la sección anterior).
-
Desde la navegación superior, vaya a Terminal y, a continuación, a New Terminal.
-
Ejecute el objetivo
test
para ejecutar pruebas:./mvnw test
También puede ejecutar la tarea
test
si utiliza Gradle:./gradlew test
Debe ver el mensaje BUILD SUCCESS al final de la ejecución de la prueba.
Ahora puede continuar con la siguiente tarea.