JavaRush /Blog Java /Random-FR /Nous ajoutons tout ce qui concerne la base de données. (P...
Roman Beekeeper
Niveau 35

Nous ajoutons tout ce qui concerne la base de données. (Partie 2) - "Projet Java de A à Z"

Publié dans le groupe Random-FR
Salut tout le monde. Je vous le rappelle : dans la première partie, nous avons ajouté Flyway. Nous allons continuer.

Ajout d'une base de données à docker-compose.yml

L'étape suivante consiste à configurer le travail avec la base de données dans le fichier docker-compose.yml principal. Ajoutons la base de données au fichier 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'
J'ai également ajouté cette ligne à notre application :
depends_on:
 - jrtb-db
Cela signifie que nous attendons que la base de données démarre avant de démarrer l'application. Ensuite, vous pouvez remarquer l'ajout de deux variables supplémentaires dont nous avons besoin pour travailler avec la base de données :
${BOT_DB_USERNAME}
${BOT_DB_PASSWORD}
Nous les obtiendrons dans docker-compose de la même manière que pour le bot télégramme - via des variables d'environnement. J'ai fait cela pour que nous n'ayons qu'un seul endroit où définir les valeurs du nom d'utilisateur de la base de données et de son mot de passe. Nous les transmettons à l'image docker de notre application et au conteneur docker de notre base de données. Ensuite, nous devons mettre à jour le Dockerfile pour apprendre à notre SpringBoot à accepter les variables pour la base de données.
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"]
Maintenant, nous ajoutons des variables de base de données au Dockerfile :
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
Les valeurs des variables seront différentes. Ceux que nous passerons dans le Dockerfile nécessitent cependant des valeurs par défaut, j'en ai donc entré quelques-unes. Nous développons la dernière ligne avec deux éléments, à l'aide desquels nous transmettrons le nom d'utilisateur et le mot de passe de la base de données au lancement de l'application :
"-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}"
La dernière ligne du Dockerfile (qui commence par ENTRYPOINT) doit être sans retour à la ligne. Si vous effectuez un virement, ce code ne fonctionnera pas. La dernière étape consiste à mettre à jour le fichier start.sh pour transmettre les variables à la base de données.
#!/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
Nous savons déjà comment ajouter des variables d'environnement avant d'exécuter docker-compose. Pour ce faire, il vous suffit d'exécuter export var_name=var_value.. Par conséquent, nous ajoutons seulement deux lignes :
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'
C'est ici que nous définissons le nom d'utilisateur et le mot de passe de la base de données. Bien entendu, il serait possible de transmettre ces variables lors de l’exécution du script bash, comme nous le faisons pour le nom et le token du bot. Mais il me semble que cela n'est pas nécessaire. Pour accéder réellement à la base de données, vous devez connaître l'adresse IP du serveur sur lequel la base de données sera déployée et figurer sur la liste des adresses IP autorisées pour la requête. Quant à moi, c'est déjà suffisant. Les bases sont posées : vous pouvez désormais faire des choses plus compréhensibles pour un développeur : écrire du code. Avant cela, nous faisions ce que font les ingénieurs DevOps : configurer l'environnement.

Ajout d'une couche de référentiel

Généralement, une application comporte trois couches :
  1. Les contrôleurs sont les points d'entrée dans l'application.
  2. Les services sont l'endroit où fonctionne la logique métier. Nous l'avons déjà partiellement : SendMessageService est un représentant explicite de la logique métier.
  3. Les référentiels sont un endroit où travailler avec une base de données. Dans notre cas, il s’agit d’un robot télégramme.
Nous allons maintenant ajouter la troisième couche : les référentiels. Ici, nous utiliserons un projet de l'écosystème Spring - Spring Data. Vous pouvez lire de quoi il s'agit dans cet article sur Habré . Nous devons connaître et comprendre plusieurs points :
  1. Nous n'aurons pas besoin de travailler avec JDBC : nous travaillerons directement avec des abstractions supérieures. Autrement dit, stockez les POJO qui correspondent aux tables de la base de données. Nous appellerons ces classes entité , comme elles sont officiellement appelées dans l' API Java Persistence (il s'agit d'un ensemble commun d'interfaces pour travailler avec une base de données via un ORM, c'est-à-dire une abstraction sur l'utilisation de JDBC). Nous aurons une classe d'entité que nous enregistrerons dans la base de données, et elles seront écrites exactement dans la table dont nous avons besoin. Nous recevrons les mêmes objets lors de la recherche dans la base de données.
  2. Spring Data propose d'utiliser leur ensemble d'interfaces : JpaRepository , CrudRepository , etc... Il existe d'autres interfaces : une liste complète peut être trouvée ici . La beauté est que vous pouvez utiliser leurs méthodes sans les mettre en œuvre (!). De plus, il existe un certain modèle à l'aide duquel vous pouvez écrire de nouvelles méthodes dans l'interface, et elles seront implémentées automatiquement.
  3. Spring simplifie notre développement autant que possible. Pour ce faire, nous devons créer notre propre interface et hériter de celles décrites ci-dessus. Et pour que Spring sache qu'il doit utiliser cette interface, ajoutez l'annotation Repository.
  4. Si nous devons écrire une méthode pour travailler avec une base de données qui n'existe pas, ce n'est pas non plus un problème - nous l'écrirons. Je vais vous montrer quoi et comment faire là-bas.
Dans cet article, nous travaillerons sur l'ajout tout au long du chemin de TelegramUser et montrerons cette partie à titre d'exemple. Nous développerons le reste sur d'autres tâches. Autrement dit, lorsque nous exécutons la commande /start, nous écrirons active = true dans la base de données de notre utilisateur. Cela signifiera que l'utilisateur utilise un bot. Si l'utilisateur est déjà dans la base de données, nous mettrons à jour le champ active = true. Lors de l'exécution de la commande /stop, nous ne supprimerons pas l'utilisateur, mais mettrons seulement à jour le champ actif sur false, de sorte que si l'utilisateur souhaite utiliser à nouveau le bot, il puisse le démarrer et reprendre là où il s'était arrêté. Et pour que lors des tests nous puissions voir que quelque chose se passe, nous allons créer une commande /stat : elle affichera le nombre d'utilisateurs actifs. Nous créons un package de référentiel à côté des packages de bot, de commande et de service. Dans ce package, nous créons une autre entité unique . Dans le package d'entité, nous créons la 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;
}
Ici vous pouvez voir que nous avons toutes les annotations du package javax.persistence. Ce sont des annotations courantes utilisées pour toutes les implémentations ORM. Par défaut, Spring Data Jpa utilise Hibernate, bien que d'autres implémentations puissent être utilisées. Voici une liste des annotations que nous utilisons :
  • Entité - indique qu'il s'agit d'une entité permettant de travailler avec la base de données ;
  • Table - nous définissons ici le nom de la table ;
  • Id - l'annotation indique quel champ sera la clé primaire dans la table ;
  • Colonne - déterminez le nom du champ à partir de la table.
Ensuite, nous créons une interface pour travailler avec la base de données. Généralement, les noms de ces interfaces sont écrits à l'aide du modèle - EntiryNameRepository. Nous aurons 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();
}
Ici vous pouvez voir comment j'ai ajouté la méthode findAllByActiveTrue() , que je n'implémente nulle part. Mais cela ne l'empêchera pas de travailler. Spring Data comprendra qu'il doit obtenir tous les enregistrements de la table tg_user dont le champ actif = true . Nous ajoutons un service pour travailler avec l'entité TelegramUser (nous utilisons l'inversion de dépendance de SOLID dans le contexte où les services d'autres entités ne peuvent pas communiquer directement avec le référentiel d'une autre entité - uniquement via le service de cette entité). Nous créons un service TelegramUserService dans le package, qui pour l'instant aura plusieurs méthodes : enregistrer l'utilisateur, récupérer l'utilisateur par son identifiant et afficher une liste des utilisateurs actifs. Nous créons d’abord l’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);
}
Et, en fait, l'implémentation 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);
   }
}
Ici, il convient de noter que nous utilisons l'injection de dépendances (introduction d'une instance de classe) de l'objet TelegramuserRepository à l'aide de l' annotation Autowired et sur le constructeur. Vous pouvez le faire pour une variable, mais c'est l'approche que nous recommande l'équipe Spring Framework.

Ajout de statistiques pour le bot

Ensuite, vous devez mettre à jour les commandes /start et /stop. Lorsque la commande /start est utilisée, vous devez enregistrer le nouvel utilisateur dans la base de données et le définir sur active = true. Et quand il y a /stop, mettez à jour les données utilisateur : set active = false. Corrigons la 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);
   }
}
Ici, nous transmettons également l'objet TelegramuserService au constructeur, avec lequel nous enregistrerons le nouvel utilisateur. De plus, en utilisant les délices d'Optional en Java, la logique suivante fonctionne : si nous avons un utilisateur dans la base de données, nous le rendons simplement actif, sinon nous en créons un nouveau actif. Commande d'arrêt :
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);
               });
   }
}
Nous transmettons TelegramServiceTest à StopCommand de la même manière. La logique supplémentaire est la suivante : si nous avons un utilisateur avec un tel identifiant de chat, nous le désactivons, c'est-à-dire que nous définissons active = false. Comment pouvez-vous voir cela de vos propres yeux ? Créons une nouvelle commande /stat, qui affichera les statistiques du bot. A ce stade, il s’agira de simples statistiques accessibles à tous les utilisateurs. À l'avenir, nous le limiterons et rendrons l'accès réservé aux administrateurs. Il y aura une entrée dans les statistiques : le nombre d’utilisateurs actifs du bot. Pour ce faire, ajoutez la valeur STAT("/stat") à CommandName. Ensuite, créez la 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));
   }
}
Tout est simple ici : nous obtenons une liste de tous les utilisateurs actifs à l'aide de la méthode retrieveAllActiveUsers et obtenons la taille de la collection. Nous devons également maintenant mettre à jour les classes ascendantes : CommandContainer et JavarushTelegramBot afin qu'elles apprennent à transférer le nouveau service dont nous avons besoin. Conteneur de commandes :
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);
   }

}
Ici, nous avons ajouté une nouvelle commande à la carte et l'avons transmise via le constructeur TelegramUserService. Mais dans le bot lui-même, seul le constructeur changera :
@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService) {
   this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this), telegramUserService);
}
Nous passons maintenant TelegramUserService comme argument, en ajoutant l'annotation Autowired. Cela signifie que nous le recevrons du contexte d'application. Nous mettrons également à jour la classe HelpCommand afin qu'une nouvelle commande de statistiques apparaisse dans la description.

Tests manuels

Lançons la base de données à partir de docker-compose-test.yml et la méthode main de la classe JavarushTelegramBotApplication. Ensuite, nous écrivons un ensemble de commandes :
  • /stat - nous nous attendons à ce que si la base de données est vide, aucune personne n'utilisera ce bot ;
  • /start - démarre le bot ;
  • /stat - nous nous attendons maintenant à ce que le bot soit utilisé par 1 personne ;
  • /stop - arrête le bot ;
  • /stat - nous nous attendons à ce qu'à nouveau 0 personne l'utilise.
"Projet Java de A à Z" : Ajout de tout ce qui concerne la base de données.  Partie 2 - 2Si le résultat est le même pour vous, nous pouvons dire que la fonctionnalité a fonctionné correctement et que le bot fonctionne correctement. Si quelque chose ne va pas, ce n'est pas grave : nous redémarrons la méthode principale en mode débogage et parcourons clairement tout le chemin pour trouver quelle était l'erreur.

Nous écrivons et mettons à jour des tests

Puisque nous avons modifié les constructeurs, nous devrons également mettre à jour les classes de test. Dans la classe AbstractCommandTest , nous devons ajouter un champ supplémentaire - la classe TelegramUserService , qui est nécessaire pour trois commandes :
protected TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
Ensuite, mettons à jour la méthode init() dans CommandContainer :
@BeforeEach
public void init() {
   SendBotMessageService sendBotMessageService = Mockito.mock(SendBotMessageService.class);
   TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
   commandContainer = new CommandContainer(sendBotMessageService, telegramUserService);
}
Dans StartCommand, vous devez mettre à jour la méthode getCommand() :
@Override
Command getCommand() {
   return new StartCommand(sendBotMessageService, telegramUserService);
}
Également dans StopCommand :
@Override
Command getCommand() {
   return new StopCommand(sendBotMessageService, telegramUserService);
}
Examinons ensuite les nouveaux tests. Créons un test typique pour 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);
   }
}
C'est simple. Parlons maintenant de la façon dont nous allons tester le travail avec la base de données. Tout ce que nous faisions auparavant, c'étaient des tests unitaires. Un test d'intégration teste l'intégration entre plusieurs parties d'une application. Par exemple, les applications et les bases de données. Ici, tout sera plus compliqué, car pour tester, nous avons besoin d'une base de données déployée. Par conséquent, lorsque nous exécutons nos tests localement, la base de données doit s'exécuter à partir de docker-compose-test.yml. Pour exécuter ce test, vous devez exécuter l'intégralité de l'application SpringBoot. La classe de test possède une annotation SpringBootTest qui démarrera l'application. Mais cette approche ne fonctionnera pas pour nous, car au lancement de l'application, le robot télégramme se lancera également. Mais il y a ici une contradiction. Les tests seront exécutés à la fois localement sur notre machine et publiquement via GitHub Actions. Pour que les tests réussissent au lancement de l'ensemble de l'application, nous devons les exécuter avec des données valides sur le bot télégramme : c'est-à-dire par son nom et son jeton... Par conséquent, nous avons deux options :
  1. Alors rendez publics le nom et le jeton du bot et espérons que tout ira bien, personne ne l'utilisera et n'interférera avec nous.
  2. Trouvez une autre façon.
J'ai choisi la deuxième option. Les tests SpringBoot ont l' annotation DataJpaTest , qui a été créée pour que lors du test d'une base de données, nous utilisions uniquement les classes dont nous avons besoin et laissons les autres tranquilles. Mais cela nous convient, car le bot télégramme ne se lancera pas du tout. Cela signifie qu'il n'est pas nécessaire de lui transmettre un nom et un jeton valides !))) Nous obtiendrons un test dans lequel nous vérifierons que les méthodes que Spring Data implémente pour nous fonctionnent comme prévu. Il est important de noter ici que nous utilisons l' annotation @ActiveProfiles("test") pour spécifier l'utilisation du profil de test. Et c’est exactement ce dont nous avons besoin pour pouvoir compter les propriétés correctes pour notre base de données. Ce serait bien d'avoir une base de données préparée avant d'exécuter nos tests. Il existe une telle approche à cet égard : ajoutez une annotation SQL au test et transmettez-lui une collection de noms de script qui doivent être exécutés avant de démarrer le test :
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
Pour nous, ils seront situés le long du chemin ./src/test/resources/ + le chemin spécifié dans l'annotation. Voici à quoi ils ressemblent :
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);
Voici à quoi ressemblera notre test TelegramUserRepositoryIT (comme vous pouvez le voir, le nom du test d'intégration sera différent - nous ajoutons IT, pas Test) :
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());
   }
}
Nous avons écrit les tests, mais la question se pose : que va-t-il se passer avec le lancement de notre processus CI sur GitHub ? Il n'aura pas de base de données. Pour l’instant, il n’y aura en réalité qu’une version rouge. Pour ce faire, nous disposons d'actions GitHub, dans lesquelles nous pouvons configurer le lancement de notre build. Avant d'exécuter les tests, vous devez ajouter un lancement de base de données avec les paramètres nécessaires. Il s’avère qu’il n’existe pas beaucoup d’exemples sur Internet, je vous conseille donc de le sauvegarder quelque part. Mettons à jour le fichier .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
Il y a maintenant un nouveau bloc Set up MySQL . Dans celui-ci, nous ajoutons MySQL à notre processus CI, définissant simultanément les variables dont nous avons besoin. Maintenant, nous avons ajouté tout ce que nous voulions. La dernière étape consiste à pousser les changements et à veiller à ce que la construction soit réussie et verte.

Mise à jour de la documentation

Mettons à jour la version du projet de 0.3.0-SNAPSHOT à 0.4.0-SNAPSHOT dans pom.xml et ajoutons également à RELEASE_NOTES :
## 0.4.0-SNAPSHOT

*   JRTB-1: added repository layer.
Après tout cela, nous créons une demande de commit, push et pull. Et surtout, notre construction est verte !"Projet Java de A à Z" : Ajout de tout ce qui concerne la base de données.  Partie 2 - 3

Liens utiles:

Toutes les modifications peuvent être vues ici dans la pull request créée . Merci à tous d'avoir lu.

Une liste de tous les matériaux de la série se trouve au début de cet article.

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