Observação:

TAREFA 4: Implementar Consultas de Banco de Dados e Criar um Aplicativo Micronaut

Neste laboratório, você implementará consultas de banco de dados e criará um aplicativo Micronaut localmente que se conecta ao Oracle Autonomous Database.

Tempo Estimado: 30 minutos

Conteúdo da Tarefa

Nesta tarefa você:

Etapa 1: Crie Entidades de Dados Micronaut que Mapeiem para Tabelas do Oracle Database

Na tarefa anterior, você adicionou o script SQL que criaria uma tabela chamada OWNER e uma tabela chamada PET uma vez executada. Em seguida, você precisa definir classes de entidade que podem ser usadas para ler dados das tabelas do banco de dados.

  1. Crie uma classe de entidade que representará um Owner no pacote src/main/java/com/example. Clique com o botão direito do mouse em src/main/java/com/example para expandir o menu de conteúdo, selecione Novo Arquivo, nomeie-o como Owner.java e cole o seguinte 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;
      }
    }
    

    A anotação @MappedEntity é usada para indicar que a entidade está mapeada para uma tabela de banco de dados. Por padrão, essa será uma tabela usando o mesmo nome da classe (nesse caso, owner).

    As colunas da tabela são representadas por cada propriedade Java. No caso acima, uma coluna id será usada para representar a chave primária e @GeneratedValue configurará o mapeamento para assumir o uso de uma coluna identity no Autonomous Database.

    A anotação @Creator é usada no construtor que será usado para instanciar a entidade mapeada e também para expressar colunas obrigatórias. Nesse caso, a coluna name é obrigatória e imutável, enquanto a coluna age não é obrigatória e pode ser definida de forma independente usando o setter setAge.

  2. Crie um arquivo Pet.java que representará a entidade Pet para modelar uma tabela pet em src/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
        }
    }
    

    Observe que a classe Pet usa um UUID preenchido automaticamente como a chave primária para demonstrar diferentes abordagens à geração de ID.

    Um relacionamento entre a classe Pet e a classe Owner também é definido usando a anotação @Relation(Relation.Kind.MANY_TO_ONE), indicando que esse é um relacionamento muitos para um.

    Com isso, chegou a hora de definir interfaces de repositório para implementar consultas.

Etapa 2: Definir Repositórios de Dados Micronaut para Implementar Consultas

O Micronaut Data suporta a noção de definição de interfaces que implementam automaticamente consultas SQL para você no momento da compilação usando o padrão de repositório de dados. Nesta seção você aproveitará esse recurso de Dados Micronaut.

  1. Crie uma pasta separada chamada repositories em src/main/java/com/example.

  2. Defina uma nova interface de repositório que se estende de CrudRepository e seja anotada com @JdbcRepository usando o dialeto ORACLE em um arquivo chamado OwnerRepository.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);
    }
    

    A interface CrudRepository usa 2 tipos de argumentos genéricos. O primeiro é o tipo da entidade (nesse caso, Owner) e o segundo é o tipo do ID (nesse caso, Long).

    A interface CrudRepository define métodos que permitem criar, ler, atualizar e excluir entidades (CRUD) do banco de dados com as inserções, seleções, atualizações e exclusões SQL apropriadas calculadas para você no momento da compilação. Para obter mais informações, consulte o Javadoc para CrudRepository.

    Você pode definir métodos na interface que executam consultas JDBC e tratar automaticamente todos os detalhes intrincados para você, como definir semântica de transação correta (transações somente leitura para consultas), executar a consulta e mapear o conjunto de resultados para a classe de entidade Owner definida anteriormente.

    O método findByName definido acima produzirá uma consulta como SELECT ID, NAME, AGE FROM OWNER WHERE NAME = ? automaticamente no momento da compilação.

    Para obter mais informações sobre métodos de consulta e os tipos de consultas que você pode definir, consulte a documentação para métodos de consulta na documentação do Micronaut Data.

  3. Com o OwnerRepository instalado, defina outro repositório e desta vez usando um objeto de transferência de dados (DTO) para executar uma consulta otimizada. Então, primeiro, você precisa criar a classe DTO em um arquivo chamado NameDTO.java em src/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;
        }
    }
    

    DTO é uma POJO simples que permite selecionar apenas as colunas necessárias a uma consulta específica, produzindo uma consulta mais otimizada.

  4. Defina o repositório chamado PetRepository em um arquivo chamado PetRepository.java para a entidade Pet que usa o DTO no mesmo local 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);
    }
    

    Anote o método list que retorna o DTO. Esse método será implementado novamente para você no momento da compilação, mas dessa vez, em vez de recuperar todas as colunas da tabela Pet, ele só recuperará a coluna name e quaisquer outras colunas que você possa definir.

    A anotação @Join consultará e instanciará o objeto unido (Owner) e o designará ao campo Owner do Pet consultado.

    O método findByName também é interessante, pois usa outro recurso importante dos Dados Micronaut, que é a anotação @Join. Ele permite especificar caminhos de junção para que você recupere exatamente os dados necessários por meio de junções de banco de dados, resultando em consultas muito mais eficientes.

Com os repositórios de dados instalados, passe para a exposição de pontos finais REST.

Etapa 3: Exponha Controladores Micronaut como Pontos Finais REST

Os pontos finais REST no Micronaut são fáceis de gravar e são definidos como controladores (de acordo com o padrão MVC).

  1. Crie uma pasta controllers em src/main/java/com/example/.

  2. Defina uma nova classe OwnerController em um arquivo chamado OwnerController.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);
        }
    }
    

    Uma classe de controlador é definida com a anotação @Controller que você pode usar para definir o URI raiz para o qual o controlador mapeia (nesse caso, /owners).

    Observe a anotação @ExecuteOn que é usada para informar à Micronaut que o controlador executa a comunicação de E/S com um banco de dados e, portanto, as operações devem executar no pool de threads de E/S.

    A classe OwnerController usa a Injeção de dependência Micronaut para obter uma referência à interface do repositório OwnerRepository definida anteriormente e é usada para implementar dois pontos finais:

    • / - O ponto final raiz lista todos os proprietários
    • /{name} - O segundo ponto final usa um modelo de URI para permitir procurar um proprietário por nome. O valor da variável de URI {name} é fornecido como um parâmetro para o método byName.
  3. Em seguida, defina outro ponto final REST chamado PetController em um arquivo chamado PetController.java em src/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);
        }
    }
    

    Desta vez, o PetRepository é injetado para expor uma lista de animais de estimação e animais de estimação por nome.

Etapa 4: Preencher Dados na Inicialização do Aplicativo

A próxima etapa é preencher alguns dados do aplicativo na inicialização. Para fazer isso, você pode usar Eventos de aplicativo Micronaut.

Abra a classe src/main/java/com/example/Application.java e substitua o conteúdo do arquivo inicial pelo seguinte:

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));
    }
}

Observe que o construtor é modificado para a dependência injetar as definições do repositório para que os dados possam ser persistidos.

Por fim, o método init é anotado com @EventListener com um argumento para receber um StartupEvent. Este evento é chamado assim que o aplicativo está ativo e em execução e pode ser usado para persistir dados quando seu aplicativo estiver pronto para fazer isso.

O restante do exemplo demonstra como salvar algumas entidades usando o método saveAll da interface CrudRepository.

Observe que javax.transaction.Transactional é declarado no método que garante que os Dados Micronaut tragam a execução do método init em uma transação JDBC que é submetida a rollback se ocorrer uma exceção durante a execução do método.

Etapa 5: Executar Testes de Integração para o Aplicativo Micronaut

O aplicativo já foi configurado com um único teste que verifica se o aplicativo pode ser inicializado com sucesso (e, portanto, testará a lógica do método init definido na seção anterior).

  1. Na navegação superior, vá para Terminal e Novo Terminal.

  2. Execute a meta test para executar testes:

    ./mvnw test
    

    Consulte a estrutura dos arquivos e execute testes em uma janela de terminal no VS Code

    Como alternativa, se você estiver usando Gradle, execute a tarefa test:

    ./gradlew test
    

    Você deverá ver a mensagem SUILD SUCCESS no final da execução do teste.

Agora você pode passar para a próxima tarefa.

Saiba Mais