JavaRush /Blogue Java /Random-PT /Adicionamos tudo relacionado ao banco de dados. (Parte 2)...
Roman Beekeeper
Nível 35

Adicionamos tudo relacionado ao banco de dados. (Parte 2) - "Projeto Java de A a Z"

Publicado no grupo Random-PT
Olá a todos. Deixe-me lembrá-lo: na primeira parte adicionamos Flyway. Vamos continuar.

Adicionando um banco de dados ao docker-compose.yml

A próxima etapa é configurar o trabalho com o banco de dados no docker-compose.yml principal. Vamos adicionar o banco de dados ao arquivo docker-compose:
version: '3.1'

services:
 jrtb-bot:
   depends_on:
     - jrtb-db
   build:
     context: .
   environment:
     BOT_NAME: ${BOT_NAME}
     BOT_TOKEN: ${BOT_TOKEN}
     BOT_DB_USERNAME: ${BOT_DB_USERNAME}
     BOT_DB_PASSWORD: ${BOT_DB_PASSWORD}
   restart: always
 jrtb-db:
   image: mysql:5.7
   restart: always
   environment:
     MYSQL_USER: ${BOT_DB_USERNAME}
     MYSQL_PASSWORD: ${BOT_DB_PASSWORD}
     MYSQL_DATABASE: 'jrtb_db'
     MYSQL_ROOT_PASSWORD: 'root'
   ports:
     - '3306:3306'
   expose:
     - '3306'
Também adicionei esta linha ao nosso aplicativo:
depends_on:
 - jrtb-db
Isso significa que esperamos o banco de dados iniciar antes de iniciar a aplicação. A seguir, você pode notar a adição de mais duas variáveis ​​que precisamos para trabalhar com o banco de dados:
${BOT_DB_USERNAME}
${BOT_DB_PASSWORD}
Iremos colocá-los no docker-compose da mesma forma que no bot do telegrama - por meio de variáveis ​​​​de ambiente. Fiz isso para que tivéssemos apenas um local onde definimos os valores do nome de usuário do banco de dados e sua senha. Nós os passamos para a imagem docker do nosso aplicativo e para o contêiner docker do nosso banco de dados. Em seguida, precisamos atualizar o Dockerfile para ensinar nosso SpringBoot a aceitar variáveis ​​para o banco de dados.
FROM adoptopenjdk/openjdk11:ubi
ARG JAR_FILE=target/*.jar
ENV BOT_NAME=test.javarush_community_bot
ENV BOT_TOKEN=1375780501:AAE4A6Rz0BSnIGzeu896OjQnjzsMEG6_uso
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}", "-Dbot.token=${BOT_TOKEN}", "-Dspring.datasource.username=${BOT_DB_USERNAME}", "-jar", "app.jar"]
Agora adicionamos variáveis ​​de banco de dados ao Dockerfile:
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
Os valores das variáveis ​​serão diferentes. Aqueles que passaremos para o Dockerfile, entretanto, exigem valores padrão, então inseri alguns. Expandimos a última linha com dois elementos, com a ajuda dos quais passaremos o nome de usuário e a senha do banco de dados para iniciar o aplicativo:
"-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}"
A última linha do Dockerfile (que começa com ENTRYPOINT) deve estar sem quebra. Se você fizer uma transferência, este código não funcionará. A última etapa é atualizar o arquivo start.sh para passar variáveis ​​ao banco de dados.
#!/bin/bash

# Pull new changes
git pull

# Prepare Jar
mvn clean
mvn package

# Ensure, that docker-compose stopped
docker-compose stop

# Add environment variables
export BOT_NAME=$1
export BOT_TOKEN=$2
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'

# Start new deployment
docker-compose up --build -d
Já sabemos como adicionar variáveis ​​de ambiente antes de executar o docker-compose. Para fazer isso, basta executar export var_name=var_value.. Portanto, adicionamos apenas duas linhas:
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'
É aqui que definimos o nome de usuário e a senha do banco de dados. Claro, seria possível passar essas variáveis ​​ao executar o script bash, como fazemos para o nome e token do bot. Mas me parece que isso é desnecessário. Para realmente acessar o banco de dados, você precisa saber o IP do servidor no qual o banco de dados será implantado e estar na lista de endereços IP permitidos para a solicitação. Quanto a mim, isso já é suficiente. A base foi lançada: agora você pode fazer coisas que são mais compreensíveis para um desenvolvedor - escrever código. Antes disso, estávamos fazendo o que os engenheiros de DevOps fazem: configurando o ambiente.

Adicionando uma camada de repositório

Normalmente, um aplicativo possui três camadas:
  1. Os controladores são os pontos de entrada no aplicativo.
  2. Os serviços são onde a lógica de negócios funciona. Já temos isto parcialmente: SendMessageService é um representante explícito da lógica de negócios.
  3. Os repositórios são um local para trabalhar com um banco de dados. No nosso caso, este é um bot de telegrama.
Agora adicionaremos a terceira camada - repositórios. Aqui usaremos um projeto do ecossistema Spring - Spring Data. Você pode ler sobre o que é neste artigo no Habré . Precisamos conhecer e compreender vários pontos:
  1. Não teremos que trabalhar com JDBC: trabalharemos diretamente com abstrações superiores. Ou seja, armazene POJOs que correspondem às tabelas do banco de dados. Chamaremos essas classes de entidade , como são oficialmente chamadas na API Java Persistence (este é um conjunto comum de interfaces para trabalhar com um banco de dados por meio de um ORM, ou seja, uma abstração sobre o trabalho com JDBC). Teremos uma classe de entidade que salvaremos no banco de dados e serão gravadas exatamente na tabela que precisamos. Receberemos os mesmos objetos ao pesquisar no banco de dados.
  2. Spring Data oferece o uso de seu conjunto de interfaces: JpaRepository , CrudRepository , etc... Existem outras interfaces: uma lista completa pode ser encontrada aqui . A beleza é que você pode usar seus métodos sem implementá-los (!). Além disso, existe um determinado modelo com o qual você pode escrever novos métodos na interface, e eles serão implementados automaticamente.
  3. Spring simplifica nosso desenvolvimento tanto quanto pode. Para fazer isso, precisamos criar nossa própria interface e herdar as descritas acima. E para que o Spring saiba que precisa utilizar esta interface, adicione a anotação Repository.
  4. Se precisarmos escrever um método para trabalhar com um banco de dados que não existe, isso também não será um problema - nós o escreveremos. Vou te mostrar o que e como fazer lá.
Neste artigo trabalharemos adicionando ao longo de todo o caminho do TelegramUser e mostraremos esta parte como exemplo. Expandiremos o restante em outras tarefas. Ou seja, ao executarmos o comando /start, escreveremos active = true no banco de dados do nosso usuário. Isso significará que o usuário está usando um bot. Caso o usuário já esteja no banco de dados, atualizaremos o campo active=true. Ao executar o comando /stop, não excluiremos o usuário, apenas atualizaremos o campo ativo para falso, para que caso o usuário queira usar o bot novamente, ele possa iniciá-lo e continuar de onde parou. E para que ao testar possamos ver que algo está acontecendo, criaremos um comando /stat: ele exibirá o número de usuários ativos. Criamos um pacote de repositório próximo aos pacotes de bot, comando e serviço. Neste pacote criamos outra entidade única . No pacote de entidades criamos a classe TelegramUser:
package com.github.javarushcommunity.jrtb.repository.entity;

import lombok.Data;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;

/**
* Telegram User entity.
*/
@Data
@Entity
@Table(name = "tg_user")
public class TelegramUser {

   @Id
   @Column(name = "chat_id")
   private String chatId;

   @Column(name = "active")
   private boolean active;
}
Aqui você pode ver que temos todas as anotações do pacote javax.persistence. Estas são anotações comuns usadas para todas as implementações de ORM. Por padrão, Spring Data Jpa usa Hibernate, embora outras implementações possam ser usadas. Aqui está uma lista de anotações que usamos:
  • Entidade - indica que se trata de uma entidade para trabalhar com banco de dados;
  • Tabela – aqui definimos o nome da tabela;
  • Id - a anotação diz qual campo será a Chave Primária da tabela;
  • Coluna - determine o nome do campo da tabela.
A seguir, criamos uma interface para trabalhar com o banco de dados. Normalmente, os nomes dessas interfaces são escritos usando o modelo EntiryNameRepository. Teremos um TelegramuserRepository:
package com.github.javarushcommunity.jrtb.repository;

import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
* {@link Repository} for handling with {@link TelegramUser} entity.
*/
@Repository
public interface TelegramUserRepository extends JpaRepository<TelegramUser, String> {
   List<TelegramUser> findAllByActiveTrue();
}
Aqui você pode ver como adicionei o método findAllByActiveTrue() , que não implemento em lugar nenhum. Mas isso não o impedirá de trabalhar. Spring Data entenderá que precisa obter todos os registros da tabela tg_user cujo campo ativo = true . Adicionamos um serviço para trabalhar com a entidade TelegramUser (usamos inversão de dependência do SOLID no contexto de que serviços de outras entidades não podem se comunicar diretamente com o repositório de outra entidade - apenas através do serviço dessa entidade). Criamos um serviço TelegramUserService no pacote, que por enquanto terá vários métodos: salvar o usuário, pegar o usuário pelo seu ID e exibir uma lista de usuários ativos. Primeiro criamos a interface TelegramUserService:
package com.github.javarushcommunity.jrtb.service;

import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

/**
* {@link Service} for handling {@link TelegramUser} entity.
*/
public interface TelegramUserService {

   /**
    * Save provided {@link TelegramUser} entity.
    *
    * @param  telegramUser provided telegram user.
    */
   void save(TelegramUser telegramUser);

   /**
    * Retrieve all active {@link TelegramUser}.
    *
    * @return the collection of the active {@link TelegramUser} objects.
    */
   List<TelegramUser> retrieveAllActiveUsers();

   /**
    * Find {@link TelegramUser} by chatId.
    *
    * @param chatId provided Chat ID
    * @return {@link TelegramUser} with provided chat ID or null otherwise.
    */
   Optional<TelegramUser> findByChatId(String chatId);
}
E, de fato, a implementação do TelegramUserServiceImpl:
package com.github.javarushcommunity.jrtb.service;

import com.github.javarushcommunity.jrtb.repository.TelegramUserRepository;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

/**
* Implementation of {@link TelegramUserService}.
*/
@Service
public class TelegramUserServiceImpl implements TelegramUserService {

   private final TelegramUserRepository telegramUserRepository;

   @Autowired
   public TelegramUserServiceImpl(TelegramUserRepository telegramUserRepository) {
       this.telegramUserRepository = telegramUserRepository;
   }

   @Override
   public void save(TelegramUser telegramUser) {
       telegramUserRepository.save(telegramUser);
   }

   @Override
   public List<TelegramUser> retrieveAllActiveUsers() {
       return telegramUserRepository.findAllByActiveTrue();
   }

   @Override
   public Optional<TelegramUser> findByChatId(String chatId) {
       return telegramUserRepository.findById(chatId);
   }
}
Deve-se notar aqui que usamos injeção de dependência (introduzimos uma instância de classe) do objeto TelegramuserRepository usando a anotação Autowired e no construtor. Você pode fazer isso para uma variável, mas esta é a abordagem que a equipe do Spring Framework nos recomenda.

Adicionando estatísticas para o bot

Em seguida, você precisa atualizar os comandos /start e /stop. Quando o comando /start é usado, você precisa salvar o novo usuário no banco de dados e configurá-lo para active = true. E quando houver /stop, atualize os dados do usuário: set active = false. Vamos corrigir a classe StartCommand :
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import org.telegram.telegrambots.meta.api.objects.Update;

/**
* Start {@link Command}.
*/
public class StartCommand implements Command {

   private final SendBotMessageService sendBotMessageService;
   private final TelegramUserService telegramUserService;

   public final static String START_MESSAGE = "Привет. Я Javarush Telegram Bot. Я помогу тебе быть в курсе последних " +
           "статей тех авторов, котрые тебе интересны. Я еще маленький и только учусь.";

   public StartCommand(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) {
       this.sendBotMessageService = sendBotMessageService;
       this.telegramUserService = telegramUserService;
   }

   @Override
   public void execute(Update update) {
       String chatId = update.getMessage().getChatId().toString();

       telegramUserService.findByChatId(chatId).ifPresentOrElse(
               user -> {
                   user.setActive(true);
                   telegramUserService.save(user);
               },
               () -> {
                   TelegramUser telegramUser = new TelegramUser();
                   telegramUser.setActive(true);
                   telegramUser.setChatId(chatId);
                   telegramUserService.save(telegramUser);
               });

       sendBotMessageService.sendMessage(chatId, START_MESSAGE);
   }
}
Aqui também passamos o objeto TelegramuserService para o construtor, com o qual salvaremos o novo usuário. Além disso, usando as delícias do Opcional em Java, a seguinte lógica funciona: se tivermos um usuário no banco de dados, simplesmente o tornamos ativo, caso contrário, criamos um novo ativo. Comando Parar:
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import org.telegram.telegrambots.meta.api.objects.Update;

import java.util.Optional;

/**
* Stop {@link Command}.
*/
public class StopCommand implements Command {

   private final SendBotMessageService sendBotMessageService;
   private final TelegramUserService telegramUserService;

   public static final String STOP_MESSAGE = "Деактивировал все ваши подписки \uD83D\uDE1F.";

   public StopCommand(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) {
       this.sendBotMessageService = sendBotMessageService;
       this.telegramUserService = telegramUserService;
   }

   @Override
   public void execute(Update update) {
       sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), STOP_MESSAGE);
       telegramUserService.findByChatId(update.getMessage().getChatId().toString())
               .ifPresent(it -> {
                   it.setActive(false);
                   telegramUserService.save(it);
               });
   }
}
Passamos TelegramServiceTest para StopCommand da mesma maneira. A lógica adicional é esta: se tivermos um usuário com esse ID de chat, nós o desativamos, ou seja, definimos active = false. Como você pode ver isso com seus próprios olhos? Vamos criar um novo comando /stat, que exibirá as estatísticas do bot. Nesta fase, estas serão estatísticas simples disponíveis para todos os utilizadores. No futuro, iremos limitá-lo e tornar o acesso apenas para administradores. Haverá uma entrada nas estatísticas: o número de usuários de bot ativos. Para fazer isso, adicione o valor STAT("/stat") a CommandName. A seguir, crie a classe StatCommand :
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.telegram.telegrambots.meta.api.objects.Update;

/**
* Statistics {@link Command}.
*/
public class StatCommand implements Command {

   private final TelegramUserService telegramUserService;
   private final SendBotMessageService sendBotMessageService;

   public final static String STAT_MESSAGE = "Javarush Telegram Bot использует %s человек.";

   @Autowired
   public StatCommand(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) {
       this.sendBotMessageService = sendBotMessageService;
       this.telegramUserService = telegramUserService;
   }

   @Override
   public void execute(Update update) {
       int activeUserCount = telegramUserService.retrieveAllActiveUsers().size();
       sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), String.format(STAT_MESSAGE, activeUserCount));
   }
}
Tudo é simples aqui: obtemos uma lista de todos os usuários ativos usando o método retrieveAllActiveUsers e obtemos o tamanho da coleção. Agora também precisamos atualizar as classes ascendentes: CommandContainer e JavarushTelegramBot para que aprendam a transferir o novo serviço que precisamos. CommandContainer:
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import com.google.common.collect.ImmutableMap;

import static com.github.javarushcommunity.jrtb.command.CommandName.*;

/**
* Container of the {@link Command}s, which are using for handling telegram commands.
*/
public class CommandContainer {

   private final ImmutableMap<String, Command> commandMap;
   private final Command unknownCommand;

   public CommandContainer(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) {

       commandMap = ImmutableMap.<String, Command>builder()
               .put(START.getCommandName(), new StartCommand(sendBotMessageService, telegramUserService))
               .put(STOP.getCommandName(), new StopCommand(sendBotMessageService, telegramUserService))
               .put(HELP.getCommandName(), new HelpCommand(sendBotMessageService))
               .put(NO.getCommandName(), new NoCommand(sendBotMessageService))
               .put(STAT.getCommandName(), new StatCommand(sendBotMessageService, telegramUserService))
               .build();

       unknownCommand = new UnknownCommand(sendBotMessageService);
   }

   public Command retrieveCommand(String commandIdentifier) {
       return commandMap.getOrDefault(commandIdentifier, unknownCommand);
   }

}
Aqui adicionamos um novo comando ao mapa e o passamos pelo construtor TelegramUserService. Mas no próprio bot, apenas o construtor mudará:
@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService) {
   this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this), telegramUserService);
}
Agora passamos TelegramUserService como argumento, adicionando a anotação Autowired. Isso significa que o receberemos do Contexto do Aplicativo. Também atualizaremos a classe HelpCommand para que um novo comando de estatísticas apareça na descrição.

Teste manual

Vamos iniciar o banco de dados de docker-compose-test.yml e o método principal na classe JavarushTelegramBotApplication. A seguir, escrevemos um conjunto de comandos:
  • /stat - esperamos que se o banco de dados estiver vazio, não haverá nenhuma pessoa usando este bot;
  • /start – inicia o bot;
  • /stat - agora esperamos que o bot seja usado por 1 pessoa;
  • /stop - para o bot;
  • /stat - esperamos que novamente haja 0 pessoas usando-o.
"Projeto Java de A a Z": Adicionando tudo relacionado ao banco de dados.  Parte 2 - 2Se o resultado for o mesmo para você, podemos dizer que a funcionalidade funcionou corretamente e o bot está funcionando corretamente. Se algo der errado, não importa: reiniciamos o método principal no modo de depuração e percorremos claramente todo o caminho para descobrir qual foi o erro.

Escrevemos e atualizamos testes

Como alteramos os construtores, precisaremos atualizar também as classes de teste. Na classe AbstractCommandTest , precisamos adicionar mais um campo - a classe telegramUserService , necessária para três comandos:
protected TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
A seguir, vamos atualizar o método init() em CommandContainer :
@BeforeEach
public void init() {
   SendBotMessageService sendBotMessageService = Mockito.mock(SendBotMessageService.class);
   TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
   commandContainer = new CommandContainer(sendBotMessageService, telegramUserService);
}
No StartCommand você precisa atualizar o método getCommand() :
@Override
Command getCommand() {
   return new StartCommand(sendBotMessageService, telegramUserService);
}
Também em StopCommand:
@Override
Command getCommand() {
   return new StopCommand(sendBotMessageService, telegramUserService);
}
A seguir, vamos dar uma olhada nos novos testes. Vamos criar um teste típico para StatCommand :
package com.github.javarushcommunity.jrtb.command;

import static com.github.javarushcommunity.jrtb.command.CommandName.STAT;
import static com.github.javarushcommunity.jrtb.command.StatCommand.STAT_MESSAGE;

public class StatCommandTest extends AbstractCommandTest {
   @Override
   String getCommandName() {
       return STAT.getCommandName();
   }

   @Override
   String getCommandMessage() {
       return String.format(STAT_MESSAGE, 0);
   }

   @Override
   Command getCommand() {
       return new StatCommand(sendBotMessageService, telegramUserService);
   }
}
Isto é simples. Agora vamos falar sobre como testaremos o trabalho com o banco de dados. Tudo o que fizemos antes foram testes unitários. Um teste de integração testa a integração entre várias partes de um aplicativo. Por exemplo, aplicativos e bancos de dados. Aqui tudo será mais complicado, pois para testar precisamos de um banco de dados implantado. Portanto, quando executamos nossos testes localmente, devemos ter o banco de dados rodando em docker-compose-test.yml. Para executar este teste, você precisa executar todo o aplicativo SpringBoot. A classe de teste possui uma anotação SpringBootTest que iniciará o aplicativo. Mas essa abordagem não funcionará para nós, porque quando o aplicativo for iniciado, o bot do telegrama também será iniciado. Mas há uma contradição aqui. Os testes serão executados localmente em nossa máquina e publicamente por meio do GitHub Actions. Para que os testes passem com o lançamento de toda a aplicação, devemos executá-los com dados válidos no bot do telegrama: ou seja, pelo seu nome e token... Portanto, temos duas opções:
  1. Portanto, torne público o nome e o token do bot e torça para que tudo fique bem, ninguém vai usá-lo e interferir conosco.
  2. Pense em outra maneira.
Eu escolhi a segunda opção. Os testes do SpringBoot possuem a anotação DataJpaTest , que foi criada para que ao testar um banco de dados, utilizemos apenas as classes que precisamos e deixemos as outras de lado. Mas isso nos convém, porque o bot do Telegram não será iniciado. Isso significa que não há necessidade de passar um nome e token válidos!))) Faremos um teste no qual verificaremos se os métodos que o Spring Data implementa para nós funcionam como esperamos. É importante observar aqui que usamos a anotação @ActiveProfiles("test") para especificar o uso do perfil de teste. E é exatamente disso que precisamos para podermos contar as propriedades corretas para nosso banco de dados. Seria bom ter um banco de dados preparado antes de executar nossos testes. Existe uma abordagem para este assunto: adicione uma anotação SQL ao teste e passe para ela uma coleção de nomes de script que precisam ser executados antes de iniciar o teste:
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
Para nós, eles estarão localizados ao longo do caminho ./src/test/resources/ + o caminho especificado na anotação. Esta é a aparência deles:
clearDbs.sql:
DELETE FROM tg_user;

telegram_users.sql:
INSERT INTO tg_user VALUES ("123456789", 1);
INSERT INTO tg_user VALUES ("123456788", 1);
INSERT INTO tg_user VALUES ("123456787", 1);
INSERT INTO tg_user VALUES ("123456786", 1);
INSERT INTO tg_user VALUES ("123456785", 1);
INSERT INTO tg_user VALUES ("123456784", 0);
INSERT INTO tg_user VALUES ("123456782", 0);
INSERT INTO tg_user VALUES ("123456781", 0);
Esta é a aparência do nosso teste TelegramUserRepositoryIT (como você pode ver, o nome do teste de integração será diferente - adicionamos TI, não Teste):
package com.github.javarushcommunity.jrtb.repository;

import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;

import java.util.List;
import java.util.Optional;

import static org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace.NONE;

/**
* Integration-level testing for {@link TelegramUserRepository}.
*/
@ActiveProfiles("test")
@DataJpaTest
@AutoConfigureTestDatabase(replace = NONE)
public class TelegramUserRepositoryIT {

   @Autowired
   private TelegramUserRepository telegramUserRepository;

   @Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
   @Test
   public void shouldProperlyFindAllActiveUsers() {
       //when
       List<TelegramUser> users = telegramUserRepository.findAllByActiveTrue();

       //then
       Assertions.assertEquals(5, users.size());
   }

   @Sql(scripts = {"/sql/clearDbs.sql"})
   @Test
   public void shouldProperlySaveTelegramUser() {
       //given
       TelegramUser telegramUser = new TelegramUser();
       telegramUser.setChatId("1234567890");
       telegramUser.setActive(false);
       telegramUserRepository.save(telegramUser);

       //when
       Optional<TelegramUser> saved = telegramUserRepository.findById(telegramUser.getChatId());

       //then
       Assertions.assertTrue(saved.isPresent());
       Assertions.assertEquals(telegramUser, saved.get());
   }
}
Escrevemos os testes, mas surge a pergunta: o que acontecerá com o lançamento do nosso processo de CI no GitHub? Não terá um banco de dados. Por enquanto, haverá apenas uma construção vermelha. Para isso, temos ações do GitHub, nas quais podemos configurar o lançamento do nosso build. Antes de executar os testes, é necessário adicionar uma inicialização de banco de dados com as configurações necessárias. Acontece que não há muitos exemplos na Internet, então aconselho você a salvar isso em algum lugar. Vamos atualizar o arquivo .github/workflows/maven.yml:
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven

name: Java CI with Maven

on:
 push:
   branches: [ main ]
 pull_request:
   branches: [ main ]

jobs:
 build:
   runs-on: ubuntu-latest
   steps:
   - uses: actions/checkout@v2
   - name: Set up MySQL
     uses: mirromutth/mysql-action@v1.1
     with:
       mysql version: '5.7'
       mysql database: 'dev_jrtb_db'
       mysql root password: 'root'
       mysql user: 'dev_jrtb_db_user'
       mysql password: 'dev_jrtb_db_password'
   - name: Set up JDK 1.11
     uses: actions/setup-java@v1
     with:
       java-version: 1.11
   - name: Build with Maven
     run: mvn -B package --file pom.xml
Agora há um novo bloco Configurar MySQL . Nele adicionamos MySQL ao nosso processo de CI, definindo simultaneamente as variáveis ​​que necessitamos. Agora adicionamos tudo o que queríamos. A última etapa é empurrar as mudanças e ver se a construção vai passar e ficar verde.

Atualizando a documentação

Vamos atualizar a versão do projeto de 0.3.0-SNAPSHOT para 0.4.0-SNAPSHOT em pom.xml e também adicionar em RELEASE_NOTES:
## 0.4.0-SNAPSHOT

*   JRTB-1: added repository layer.
Depois de tudo isso, criamos uma solicitação de commit, push e pull. E o mais importante, nossa construção é verde!"Projeto Java de A a Z": Adicionando tudo relacionado ao banco de dados.  Parte 2 - 3

Links Úteis:

Todas as alterações podem ser vistas aqui na solicitação pull criada . Obrigado a todos pela leitura.

Uma lista de todos os materiais da série está no início deste artigo.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION