JavaRush /Blog Java /Random-ES /Agregamos todo lo relacionado con la base de datos. (Part...

Agregamos todo lo relacionado con la base de datos. (Parte 2) - "Proyecto Java de la A a la Z"

Publicado en el grupo Random-ES
Hola a todos. Déjame recordarte: en la primera parte agregamos Flyway. Continuemos.

Agregar una base de datos a docker-compose.yml

La siguiente etapa es configurar el trabajo con la base de datos en el docker-compose.yml principal. Agreguemos la base de datos al archivo 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'
También agregué esta línea a nuestra aplicación:
depends_on:
 - jrtb-db
Esto significa que esperamos a que se inicie la base de datos antes de iniciar la aplicación. A continuación, puede notar la adición de dos variables más que necesitamos para trabajar con la base de datos:
${BOT_DB_USERNAME}
${BOT_DB_PASSWORD}
Los obtendremos en Docker-Compose de la misma manera que para el bot de Telegram: a través de variables de entorno. Hice esto para que tengamos un solo lugar donde configuramos los valores del nombre de usuario de la base de datos y su contraseña. Los pasamos a la imagen acoplable de nuestra aplicación y al contenedor acoplable de nuestra base de datos. A continuación, necesitamos actualizar Dockerfile para enseñarle a nuestro SpringBoot a aceptar variables para la base de datos.
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"]
Ahora agregamos variables de base de datos al Dockerfile:
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
Los valores de las variables serán diferentes. Sin embargo, los que pasaremos al Dockerfile requieren valores predeterminados, así que ingresé algunos. Ampliamos la última línea con dos elementos, con la ayuda de los cuales pasaremos el nombre de usuario y la contraseña de la base de datos al iniciar la aplicación:
"-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}"
La última línea del Dockerfile (que comienza con ENTRYPOINT) debe estar sin ajuste. Si realiza una transferencia, este código no funcionará. El último paso es actualizar el archivo start.sh para pasar variables a la base de datos.
#!/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
Ya sabemos cómo agregar variables de entorno antes de ejecutar docker-compose. Para hacer esto, solo necesita ejecutar export var_name=var_value. Por lo tanto, agregamos solo dos líneas:
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'
Aquí es donde configuramos el nombre de usuario y la contraseña de la base de datos. Por supuesto, sería posible pasar estas variables al ejecutar el script bash, como lo hacemos con el nombre y el token del bot. Pero me parece que esto es innecesario. Para acceder realmente a la base de datos, necesita conocer la IP del servidor en el que se implementará la base de datos y estar en la lista de direcciones IP permitidas para la solicitud. En cuanto a mí, esto ya es suficiente. Se han sentado las bases: ahora puedes hacer cosas que son más comprensibles para un desarrollador: escribir código. Antes de eso, estábamos haciendo lo que hacen los ingenieros de DevOps: configurar el entorno.

Agregar una capa de repositorio

Normalmente una aplicación tiene tres capas:
  1. Los controladores son los puntos de entrada a la aplicación.
  2. Los servicios son donde funciona la lógica empresarial. Ya tenemos esto parcialmente: SendMessageService es un representante explícito de la lógica empresarial.
  3. Los repositorios son un lugar para trabajar con una base de datos. En nuestro caso, se trata de un bot de Telegram.
Ahora agregaremos la tercera capa: repositorios. Aquí usaremos un proyecto del ecosistema Spring: Spring Data. Puedes leer sobre qué es en este artículo sobre Habré . Necesitamos conocer y comprender varios puntos:
  1. No tendremos que trabajar con JDBC: trabajaremos directamente con abstracciones superiores. Es decir, almacenar POJO que correspondan a tablas en la base de datos. Llamaremos a estas clases entidad , como se las llama oficialmente en la API de persistencia de Java (este es un conjunto común de interfaces para trabajar con una base de datos a través de ORM, es decir, una abstracción sobre el trabajo con JDBC). Tendremos una clase de entidad que guardaremos en la base de datos y se escribirán exactamente en la tabla que necesitamos. Recibiremos los mismos objetos al buscar en la base de datos.
  2. Spring Data ofrece utilizar su conjunto de interfaces: JpaRepository , CrudRepository , etc... Hay otras interfaces: puede encontrar una lista completa aquí . Lo bueno es que puedes usar sus métodos sin implementarlos (!). Además, existe una determinada plantilla mediante la cual puede escribir nuevos métodos en la interfaz y se implementarán automáticamente.
  3. Spring simplifica nuestro desarrollo tanto como puede. Para ello, necesitamos crear nuestra propia interfaz y heredar de las descritas anteriormente. Y para que Spring sepa que necesita usar esta interfaz, agregue la anotación Repositorio.
  4. Si necesitamos escribir un método para trabajar con una base de datos que no existe, tampoco es un problema: lo escribiremos. Te mostraré qué y cómo hacer allí.
En este artículo, trabajaremos agregando a lo largo de toda la ruta de TelegramUser y mostraremos esta parte como ejemplo. El resto lo ampliaremos en otras tareas. Es decir, cuando ejecutamos el comando /start, escribiremos active = true en la base de datos de nuestro usuario. Esto significará que el usuario está utilizando un bot. Si el usuario ya está en la base de datos, actualizaremos el campo active = true. Al ejecutar el comando /stop, no eliminaremos al usuario, solo actualizaremos el campo activo a falso, de modo que si el usuario quiere usar el bot nuevamente, pueda iniciarlo y continuar donde lo dejó. Y para que al probar podamos ver que algo está pasando, crearemos un comando /stat: mostrará el número de usuarios activos. Creamos un paquete de repositorio junto a los paquetes de bot, comando y servicio. En este paquete creamos otra entidad única . En el paquete de entidad creamos la clase 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;
}
Aquí puedes ver que tenemos todas las anotaciones del paquete javax.persistence. Estas son anotaciones comunes que se utilizan para todas las implementaciones de ORM. De forma predeterminada, Spring Data Jpa usa Hibernate, aunque se pueden usar otras implementaciones. Aquí hay una lista de anotaciones que utilizamos:
  • Entidad : indica que se trata de una entidad para trabajar con la base de datos;
  • Tabla : aquí definimos el nombre de la tabla;
  • Id : la anotación dice qué campo será la clave principal en la tabla;
  • Columna : determine el nombre del campo de la tabla.
A continuación, creamos una interfaz para trabajar con la base de datos. Normalmente, los nombres de dichas interfaces se escriben utilizando la plantilla EntiryNameRepository. Tendremos un 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();
}
Aquí puedes ver cómo agregué el método findAllByActiveTrue() , que no implemento en ningún lado. Pero eso no le impedirá trabajar. Spring Data entenderá que necesita obtener todos los registros de la tabla tg_user cuyo campo activo = true . Agregamos un servicio para trabajar con la entidad TelegramUser (usamos la inversión de dependencia de SOLID en el contexto de que los servicios de otras entidades no pueden comunicarse directamente con el repositorio de otra entidad, solo a través del servicio de esa entidad). Creamos un servicio TelegramUserService en el paquete, que por ahora tendrá varios métodos: guardar al usuario, obtener el usuario por su ID y mostrar una lista de usuarios activos. Primero creamos la interfaz 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);
}
Y, de hecho, la implementación de 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);
   }
}
Aquí cabe señalar que usamos la inyección de dependencia (introducimos una instancia de clase) del objeto TelegramuserRepository usando la anotación Autowired y en el constructor. Puede hacer esto para una variable, pero este es el enfoque que nos recomienda el equipo de Spring Framework.

Agregar estadísticas para el bot

A continuación, debe actualizar los comandos /start y /stop. Cuando se utiliza el comando /start, debe guardar el nuevo usuario en la base de datos y configurarlo en activo = verdadero. Y cuando haya /stop, actualice los datos del usuario: set active = false. Arreglemos la clase 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);
   }
}
Aquí también pasamos el objeto TelegramuserService al constructor, con el que guardaremos al nuevo usuario. Además, utilizando las delicias de Opcional en Java, funciona la siguiente lógica: si tenemos un usuario en la base de datos, simplemente lo activamos, si no, creamos uno nuevo activo. Comando Detener:
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);
               });
   }
}
Pasamos TelegramServiceTest a StopCommand de la misma forma. La lógica adicional es la siguiente: si tenemos un usuario con dicho ID de chat, lo desactivamos, es decir, configuramos active = false. ¿Cómo puedes ver esto con tus propios ojos? Creemos un nuevo comando /stat, que mostrará las estadísticas del bot. En esta etapa, estas serán estadísticas simples disponibles para todos los usuarios. En el futuro, lo limitaremos y permitiremos el acceso solo a administradores. Habrá una entrada en las estadísticas: la cantidad de usuarios de bot activos. Para hacer esto, agregue el valor STAT("/stat") a CommandName. A continuación, cree la clase 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));
   }
}
Aquí todo es simple: obtenemos una lista de todos los usuarios activos usando el método retrieveAllActiveUsers y obtenemos el tamaño de la colección. Ahora también necesitamos actualizar las clases ascendentes: CommandContainer y JavarushTelegramBot para que aprendan a transferir el nuevo servicio que necesitamos. Contenedor de comandos:
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);
   }

}
Aquí agregamos un nuevo comando al mapa y lo pasamos a través del constructor TelegramUserService. Pero en el propio bot, solo cambiará el constructor:
@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService) {
   this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this), telegramUserService);
}
Ahora pasamos TelegramUserService como argumento y agregamos la anotación Autowired. Esto significa que lo recibiremos del Contexto de la Aplicación. También actualizaremos la clase HelpCommand para que aparezca un nuevo comando de estadísticas en la descripción.

Prueba manual

Iniciemos la base de datos desde docker-compose-test.yml y el método principal en la clase JavarushTelegramBotApplication. A continuación escribimos un conjunto de comandos:
  • /stat: esperamos que si la base de datos está vacía, no habrá ninguna persona usando este bot;
  • /start - inicia el robot;
  • /stat: ahora esperamos que el bot sea utilizado por 1 persona;
  • /stop - detiene el robot;
  • /stat: esperamos que nuevamente haya 0 personas usándolo.
"Proyecto Java de la A a la Z": Agregando todo lo relacionado con la base de datos.  Parte 2 - 2Si el resultado es el mismo para usted, podemos decir que la funcionalidad funcionó correctamente y el bot está funcionando correctamente. Si algo sale mal, no importa: reiniciamos el método principal en modo de depuración y recorremos claramente toda la ruta para encontrar cuál fue el error.

Redactamos y actualizamos pruebas.

Como cambiamos los constructores, también necesitaremos actualizar las clases de prueba. En la clase AbstractCommandTest , necesitamos agregar un campo más: la clase TelegramUserService , que es necesaria para tres comandos:
protected TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
A continuación, actualicemos el método init() en CommandContainer :
@BeforeEach
public void init() {
   SendBotMessageService sendBotMessageService = Mockito.mock(SendBotMessageService.class);
   TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
   commandContainer = new CommandContainer(sendBotMessageService, telegramUserService);
}
En StartCommand necesitas actualizar el método getCommand() :
@Override
Command getCommand() {
   return new StartCommand(sendBotMessageService, telegramUserService);
}
También en StopCommand:
@Override
Command getCommand() {
   return new StopCommand(sendBotMessageService, telegramUserService);
}
A continuación, veamos las nuevas pruebas. Creemos una prueba típica 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);
   }
}
Esto es simple. Ahora hablemos de cómo probaremos trabajar con la base de datos. Todo lo que hicimos antes fueron pruebas unitarias. Una prueba de integración prueba la integración entre varias partes de una aplicación. Por ejemplo, aplicaciones y bases de datos. Aquí todo será más complicado, porque para realizar pruebas necesitamos una base de datos implementada. Por lo tanto, cuando ejecutamos nuestras pruebas localmente, debemos tener la base de datos ejecutándose desde docker-compose-test.yml. Para ejecutar esta prueba, necesita ejecutar toda la aplicación SpringBoot. La clase de prueba tiene una anotación SpringBootTest que iniciará la aplicación. Pero este enfoque no funcionará para nosotros, porque cuando se inicia la aplicación, también se iniciará el bot de Telegram. Pero aquí hay una contradicción. Las pruebas se ejecutarán tanto localmente en nuestra máquina como públicamente a través de GitHub Actions. Para que las pruebas pasen con el lanzamiento de toda la aplicación, debemos ejecutarlas con datos válidos en el bot de Telegram: es decir, por su nombre y token... Por tanto, tenemos dos opciones:
  1. Así que haga públicos el nombre y el token del bot y espere que todo esté bien, que nadie lo use ni interfiera con nosotros.
  2. Piensa en otra forma.
Elegí la segunda opción. Las pruebas SpringBoot tienen la anotación DataJpaTest , que fue creada para que al probar una base de datos, usemos solo las clases que necesitamos y dejemos otras en paz. Pero esto nos conviene, porque el bot de Telegram no se inicia en absoluto. ¡Esto significa que no es necesario pasarle un nombre y un token válidos!))) Obtendremos una prueba en la que comprobaremos que los métodos que Spring Data implementa para nosotros funcionan como esperamos. Es importante señalar aquí que utilizamos la anotación @ActiveProfiles("test") para especificar el uso del perfil de prueba. Y esto es exactamente lo que necesitamos para poder contar las propiedades correctas para nuestra base de datos. Sería bueno tener una base de datos preparada antes de ejecutar nuestras pruebas. Existe un enfoque para este asunto: agregue una anotación Sql a la prueba y pásele una colección de nombres de scripts que deben ejecutarse antes de comenzar la prueba:
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
Para nosotros, estarán ubicados a lo largo de la ruta ./src/test/resources/ + la ruta especificada en la anotación. Así es como se ven:
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);
Así es como se verá nuestra prueba TelegramUserRepositoryIT como resultado (como puede ver, el nombre para las pruebas de integración será diferente: agregamos TI, no Prueba):
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());
   }
}
Escribimos las pruebas, pero surge la pregunta: ¿qué pasará con el lanzamiento de nuestro proceso de CI en GitHub? No tendrá una base de datos. Por ahora, en realidad solo habrá una versión roja. Para ello contamos con acciones de GitHub, en las que podemos configurar el lanzamiento de nuestra build. Antes de ejecutar las pruebas, debe agregar un inicio de base de datos con la configuración necesaria. Resulta que no hay muchos ejemplos en Internet, así que te aconsejo que los guardes en algún lugar. Actualicemos el archivo .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
Ahora hay un nuevo bloque Configurar MySQL . En él agregamos MySQL a nuestro proceso de CI, definiendo simultáneamente las variables que necesitamos. Ahora hemos añadido todo lo que queríamos. La última etapa es impulsar los cambios y ver que la compilación se aprobará y será ecológica.

Actualizando la documentación

Actualicemos la versión del proyecto de 0.3.0-SNAPSHOT a 0.4.0-SNAPSHOT en pom.xml y agreguemos también a RELEASE_NOTES:
## 0.4.0-SNAPSHOT

*   JRTB-1: added repository layer.
Después de todo esto, creamos una solicitud de confirmación, inserción y extracción. Y lo más importante: ¡nuestra construcción es ecológica!"Proyecto Java de la A a la Z": Agregando todo lo relacionado con la base de datos.  Parte 2 - 3

Enlaces útiles:

Todos los cambios se pueden ver aquí en la solicitud de extracción creada . Gracias a todos por leer.

Al principio de este artículo encontrará una lista de todos los materiales de la serie.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION