JavaRush /Java-Blog /Random-DE /Wir fügen alles hinzu, was mit der Datenbank zu tun hat. ...

Wir fügen alles hinzu, was mit der Datenbank zu tun hat. (Teil 2) - „Java-Projekt von A bis Z“

Veröffentlicht in der Gruppe Random-DE
Hallo zusammen. Ich möchte Sie daran erinnern: Im ersten Teil haben wir Flyway hinzugefügt. Lass uns weitermachen.

Hinzufügen einer Datenbank zu docker-compose.yml

Der nächste Schritt besteht darin, die Arbeit mit der Datenbank in der Hauptdatenbank docker-compose.yml einzurichten. Fügen wir die Datenbank zur Docker-Compose-Datei hinzu:
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'
Ich habe unserer Bewerbung auch diese Zeile hinzugefügt:
depends_on:
 - jrtb-db
Das bedeutet, dass wir auf den Start der Datenbank warten, bevor wir die Anwendung starten. Als nächstes können Sie die Hinzufügung von zwei weiteren Variablen bemerken, die wir für die Arbeit mit der Datenbank benötigen:
${BOT_DB_USERNAME}
${BOT_DB_PASSWORD}
Wir erhalten sie in Docker-Compose auf die gleiche Weise wie für den Telegram-Bot – über Umgebungsvariablen. Ich habe das so gemacht, dass wir nur einen Ort haben, an dem wir die Werte des Datenbankbenutzernamens und seines Passworts festlegen. Wir übergeben sie an das Docker-Image unserer Anwendung und an den Docker-Container unserer Datenbank. Als nächstes müssen wir die Docker-Datei aktualisieren, um unserem SpringBoot beizubringen, Variablen für die Datenbank zu akzeptieren.
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"]
Jetzt fügen wir der Docker-Datei Datenbankvariablen hinzu:
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
Die Variablenwerte werden unterschiedlich sein. Die Werte, die wir an die Docker-Datei übergeben, erfordern jedoch Standardwerte, daher habe ich einige eingegeben. Wir erweitern die letzte Zeile um zwei Elemente, mit deren Hilfe wir den DB-Benutzernamen und das Passwort an den Anwendungsstart übergeben:
"-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}"
Die letzte Zeile in der Docker-Datei (die mit ENTRYPOINT beginnt) muss ohne Umbruch sein. Wenn Sie eine Überweisung durchführen, funktioniert dieser Code nicht. Der letzte Schritt besteht darin, die Datei start.sh zu aktualisieren , um Variablen an die Datenbank zu übergeben.
#!/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
Wir wissen bereits, wie man Umgebungsvariablen hinzufügt, bevor man Docker-Compose ausführt. Dazu müssen Sie lediglich export var_name=var_value ausführen. Daher fügen wir nur zwei Zeilen hinzu:
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'
Hier legen wir den Datenbank-Benutzernamen und das Passwort fest. Natürlich wäre es möglich, diese Variablen beim Ausführen des Bash-Skripts zu übergeben, wie wir es für den Namen und das Token des Bots tun. Aber es scheint mir, dass dies unnötig ist. Um tatsächlich auf die Datenbank zuzugreifen, müssen Sie die IP des Servers kennen, auf dem die Datenbank bereitgestellt wird, und auf der Liste der zulässigen IP-Adressen für die Anfrage stehen. Für mich reicht das bereits aus. Der Grundstein ist gelegt: Jetzt können Sie Dinge tun, die für einen Entwickler verständlicher sind – Code schreiben. Davor haben wir das getan, was DevOps-Ingenieure tun: die Umgebung einrichten.

Hinzufügen einer Repository-Ebene

Typischerweise besteht eine Anwendung aus drei Schichten:
  1. Controller sind die Einstiegspunkte in die Anwendung.
  2. In Diensten funktioniert die Geschäftslogik. Das haben wir teilweise schon: SendMessageService ist ein expliziter Vertreter der Geschäftslogik.
  3. Repositorys sind ein Ort zum Arbeiten mit einer Datenbank. In unserem Fall handelt es sich um einen Telegram-Bot.
Jetzt fügen wir die dritte Ebene hinzu – Repositorys. Hier verwenden wir ein Projekt aus dem Spring-Ökosystem – Spring Data. Was es damit auf sich hat, können Sie in diesem Artikel über Habré nachlesen . Wir müssen mehrere Punkte kennen und verstehen:
  1. Wir müssen nicht mit JDBC arbeiten: Wir werden direkt mit höheren Abstraktionen arbeiten. Das heißt, Sie speichern POJOs, die Tabellen in der Datenbank entsprechen. Wir nennen solche Klassen „Entity“ , wie sie in der Java Persistence API offiziell heißen (dies ist ein allgemeiner Satz von Schnittstellen für die Arbeit mit einer Datenbank über ein ORM, also eine Abstraktion gegenüber der Arbeit mit JDBC). Wir werden eine Entitätsklasse haben, die wir in der Datenbank speichern, und sie werden genau in die Tabelle geschrieben, die wir brauchen. Bei der Suche in der Datenbank erhalten wir die gleichen Objekte.
  2. Spring Data bietet die Verwendung seiner Schnittstellen an: JpaRepository , CrudRepository usw. Es gibt noch weitere Schnittstellen: Eine vollständige Liste finden Sie hier . Das Schöne ist, dass Sie ihre Methoden verwenden können, ohne sie zu implementieren (!). Darüber hinaus gibt es eine bestimmte Vorlage, mit der Sie neue Methoden in die Schnittstelle schreiben können, die automatisch implementiert werden.
  3. Der Frühling vereinfacht unsere Entwicklung so weit wie möglich. Dazu müssen wir unsere eigene Schnittstelle erstellen und von den oben beschriebenen erben. Und damit Spring weiß, dass es diese Schnittstelle verwenden muss, fügen Sie die Repository-Annotation hinzu.
  4. Wenn wir eine Methode zum Arbeiten mit einer Datenbank schreiben müssen, die nicht existiert, ist das auch kein Problem – wir werden sie schreiben. Ich zeige dir, was und wie man dort machen kann.
In diesem Artikel werden wir mit dem Hinzufügen entlang des gesamten Pfads von TelegramUser arbeiten und diesen Teil als Beispiel zeigen. Den Rest werden wir auf andere Aufgaben ausweiten. Das heißt, wenn wir den Befehl /start ausführen, schreiben wir active = true in die Datenbank unseres Benutzers. Dies bedeutet, dass der Benutzer einen Bot verwendet. Wenn der Benutzer bereits in der Datenbank vorhanden ist, aktualisieren wir das Feld aktiv = wahr. Beim Ausführen des /stop-Befehls löschen wir den Benutzer nicht, sondern aktualisieren nur das aktive Feld auf „false“, sodass der Benutzer den Bot erneut starten und dort weitermachen kann, wo er aufgehört hat, wenn er ihn erneut verwenden möchte. Und damit wir beim Testen sehen können, dass etwas passiert, erstellen wir einen /stat-Befehl: Er zeigt die Anzahl der aktiven Benutzer an. Wir erstellen ein Repository- Paket neben den Bot-, Befehls- und Servicepaketen. In diesem Paket erstellen wir eine weitere Einheit . Im Entity-Paket erstellen wir die TelegramUser-Klasse:
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;
}
Hier können Sie sehen, dass wir alle Anmerkungen aus dem javax.persistence-Paket haben. Dabei handelt es sich um allgemeine Anmerkungen, die für alle ORM-Implementierungen verwendet werden. Standardmäßig verwendet Spring Data Jpa Hibernate, obwohl auch andere Implementierungen verwendet werden können. Hier ist eine Liste der von uns verwendeten Anmerkungen:
  • Entität – gibt an, dass es sich um eine Entität für die Arbeit mit der Datenbank handelt;
  • Tabelle – hier definieren wir den Namen der Tabelle;
  • Id – die Anmerkung gibt an, welches Feld der Primärschlüssel in der Tabelle sein wird;
  • Spalte – Bestimmen Sie den Namen des Feldes aus der Tabelle.
Als nächstes erstellen wir eine Schnittstelle für die Arbeit mit der Datenbank. Typischerweise werden die Namen solcher Schnittstellen mithilfe der Vorlage EntiryNameRepository geschrieben. Wir werden ein TelegramuserRepository haben:
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();
}
Hier können Sie sehen, wie ich die Methode findAllByActiveTrue() hinzugefügt habe , die ich nirgendwo implementiert habe. Aber das wird ihn nicht von der Arbeit abhalten. Spring Data erkennt, dass es alle Datensätze aus der Tabelle tg_user abrufen muss, deren aktives Feld = true ist . Wir fügen einen Dienst für die Arbeit mit der TelegramUser-Entität hinzu (wir verwenden die Abhängigkeitsumkehr von SOLID in dem Kontext, dass Dienste anderer Entitäten nicht direkt mit dem Repository einer anderen Entität kommunizieren können – nur über den Dienst dieser Entität). Wir erstellen im Paket einen Dienst TelegramUserService, der vorerst über mehrere Methoden verfügt: Speichern des Benutzers, Abrufen des Benutzers anhand seiner ID und Anzeigen einer Liste der aktiven Benutzer. Zuerst erstellen wir die TelegramUserService-Schnittstelle:
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);
}
Und tatsächlich die Implementierung von 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);
   }
}
Hier ist zu beachten, dass wir die Abhängigkeitsinjektion (Einführung einer Klasseninstanz) des TelegramuserRepository-Objekts mithilfe der Autowired- Annotation und des Konstruktors verwenden. Sie können dies für eine Variable tun, aber das ist der Ansatz, den uns das Spring Framework-Team empfiehlt.

Statistiken für den Bot hinzufügen

Als nächstes müssen Sie die Befehle /start und /stop aktualisieren. Wenn der Befehl /start verwendet wird, müssen Sie den neuen Benutzer in der Datenbank speichern und auf active = true setzen. Und wenn /stop vorhanden ist, aktualisieren Sie die Benutzerdaten: set active = false. Lassen Sie uns die StartCommand- Klasse reparieren :
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);
   }
}
Hier übergeben wir auch das TelegramuserService-Objekt an den Konstruktor, mit dem wir den neuen Benutzer speichern. Darüber hinaus funktioniert unter Verwendung der Vorzüge von Optional in Java die folgende Logik: Wenn wir einen Benutzer in der Datenbank haben, machen wir ihn einfach aktiv, wenn nicht, erstellen wir einen neuen aktiven. Stoppbefehl:
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);
               });
   }
}
Auf die gleiche Weise übergeben wir TelegramServiceTest an StopCommand. Die zusätzliche Logik ist folgende: Wenn wir einen Benutzer mit einer solchen Chat-ID haben, deaktivieren wir diese, d. h. wir setzen active = false. Wie können Sie das mit eigenen Augen sehen? Erstellen wir einen neuen Befehl /stat, der die Statistiken des Bots anzeigt. Zu diesem Zeitpunkt handelt es sich um einfache Statistiken, die allen Benutzern zur Verfügung stehen. In Zukunft werden wir es einschränken und den Zugriff nur für Administratoren ermöglichen. In der Statistik wird es einen Eintrag geben: die Anzahl der aktiven Bot-Benutzer. Fügen Sie dazu den Wert STAT("/stat") zu CommandName hinzu. Als nächstes erstellen Sie die StatCommand- Klasse:
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));
   }
}
Hier ist alles einfach: Wir erhalten eine Liste aller aktiven Benutzer mit der Methode „retrieAllActiveUsers“ und ermitteln die Größe der Sammlung. Wir müssen jetzt auch die aufsteigenden Klassen CommandContainer und JavarushTelegramBot aktualisieren , damit sie lernen, den neuen Dienst zu übertragen, den wir benötigen. 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);
   }

}
Hier haben wir der Karte einen neuen Befehl hinzugefügt und ihn über den TelegramUserService-Konstruktor übergeben. Aber im Bot selbst ändert sich nur der Konstruktor:
@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService) {
   this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this), telegramUserService);
}
Jetzt übergeben wir TelegramUserService als Argument und fügen die Autowired-Annotation hinzu. Das bedeutet, dass wir es vom Anwendungskontext erhalten. Wir werden auch die HelpCommand- Klasse aktualisieren , sodass ein neuer Statistikbefehl in der Beschreibung erscheint.

Manuelles Testen

Starten wir die Datenbank über docker-compose-test.yml und die Hauptmethode in der JavarushTelegramBotApplication-Klasse. Als nächstes schreiben wir eine Reihe von Befehlen:
  • /stat – wir gehen davon aus, dass, wenn die Datenbank leer ist, dieser Bot von null Personen verwendet wird;
  • /start – Bot starten;
  • /stat – jetzt gehen wir davon aus, dass der Bot von einer Person verwendet wird;
  • /stop – stoppt den Bot;
  • /stat – wir gehen davon aus, dass es wieder 0 Leute verwenden werden.
„Java-Projekt von A bis Z“: Alles rund um die Datenbank hinzufügen.  Teil 2 - 2Wenn Ihre Ergebnisse gleich sind, können wir sagen, dass die Funktionalität ordnungsgemäß funktioniert hat und der Bot ordnungsgemäß funktioniert. Wenn etwas schief geht, spielt das keine Rolle: Wir starten die Hauptmethode im Debug-Modus neu und gehen den gesamten Pfad übersichtlich durch, um herauszufinden, was der Fehler war.

Wir schreiben und aktualisieren Tests

Da wir die Konstruktoren geändert haben, müssen wir auch die Testklassen aktualisieren. In der AbstractCommandTest- Klasse müssen wir ein weiteres Feld hinzufügen – die TelegramUserService- Klasse , die für drei Befehle benötigt wird:
protected TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
Als nächstes aktualisieren wir die init()- Methode in CommandContainer :
@BeforeEach
public void init() {
   SendBotMessageService sendBotMessageService = Mockito.mock(SendBotMessageService.class);
   TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
   commandContainer = new CommandContainer(sendBotMessageService, telegramUserService);
}
In StartCommand müssen Sie die Methode getCommand() aktualisieren :
@Override
Command getCommand() {
   return new StartCommand(sendBotMessageService, telegramUserService);
}
Auch in StopCommand:
@Override
Command getCommand() {
   return new StopCommand(sendBotMessageService, telegramUserService);
}
Schauen wir uns als nächstes die neuen Tests an. Lassen Sie uns einen typischen Test für StatCommand erstellen :
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);
   }
}
Das ist einfach. Lassen Sie uns nun darüber sprechen, wie wir die Arbeit mit der Datenbank testen werden. Alles, was wir vorher gemacht haben, waren Unit-Tests. Ein Integrationstest testet die Integration zwischen mehreren Teilen einer Anwendung. Zum Beispiel Anwendungen und Datenbanken. Hier wird alles komplizierter, da wir zum Testen eine bereitgestellte Datenbank benötigen. Wenn wir unsere Tests lokal ausführen, muss die Datenbank daher über docker-compose-test.yml ausgeführt werden. Um diesen Test auszuführen, müssen Sie die gesamte SpringBoot-Anwendung ausführen. Die Testklasse verfügt über eine SpringBootTest- Annotation , die die Anwendung startet. Dieser Ansatz wird für uns jedoch nicht funktionieren, da beim Start der Anwendung auch der Telegram-Bot gestartet wird. Aber hier gibt es einen Widerspruch. Tests werden sowohl lokal auf unserem Computer als auch öffentlich über GitHub Actions ausgeführt. Damit die Tests beim Start der gesamten Anwendung erfolgreich sind, müssen wir sie mit gültigen Daten zum Telegram-Bot ausführen: also anhand seines Namens und Tokens... Daher haben wir zwei Möglichkeiten:
  1. Machen Sie also den Namen und das Token des Bots öffentlich und hoffen Sie, dass alles gut wird und niemand ihn benutzt und uns stört.
  2. Überlegen Sie sich einen anderen Weg.
Ich habe mich für die zweite Option entschieden. SpringBoot-Tests verfügen über die DataJpaTest- Annotation , die erstellt wurde, damit wir beim Testen einer Datenbank nur die Klassen verwenden, die wir benötigen, und andere in Ruhe lassen. Aber das kommt uns entgegen, denn der Telegram-Bot startet überhaupt nicht. Dies bedeutet, dass kein gültiger Name und kein gültiges Token übergeben werden müssen!))) Wir erhalten einen Test, bei dem wir überprüfen, ob die Methoden, die Spring Data für uns implementiert, wie erwartet funktionieren. Hier ist es wichtig zu beachten, dass wir die Annotation @ActiveProfiles("test") verwenden , um die Verwendung des Testprofils anzugeben. Und genau das brauchen wir, damit wir die richtigen Eigenschaften für unsere Datenbank zählen können. Es wäre gut, eine Datenbank vorzubereiten, bevor wir unsere Tests durchführen. Hierzu gibt es einen solchen Ansatz: Fügen Sie dem Test eine SQL-Annotation hinzu und übergeben Sie ihm eine Sammlung von Skriptnamen, die vor dem Start des Tests ausgeführt werden müssen:
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
Für uns befinden sie sich entlang des Pfads ./src/test/resources/ + dem in der Anmerkung angegebenen Pfad. So sehen sie aus:
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);
So wird unser TelegramUserRepositoryIT-Test als Ergebnis aussehen (wie Sie sehen, wird der Name für Integrationstests anders sein – wir fügen IT und nicht Test hinzu):
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());
   }
}
Wir haben die Tests geschrieben, aber es stellt sich die Frage: Was passiert mit der Einführung unseres CI-Prozesses auf GitHub? Es wird keine Datenbank geben. Vorerst wird es wirklich nur einen roten Build geben. Dazu verfügen wir über GitHub-Aktionen, in denen wir den Start unseres Builds konfigurieren können. Bevor Sie die Tests ausführen, müssen Sie einen Datenbankstart mit den erforderlichen Einstellungen hinzufügen. Wie sich herausstellt, gibt es im Internet nicht viele Beispiele, daher rate ich Ihnen, diese irgendwo aufzubewahren. Aktualisieren wir die Datei .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
Jetzt gibt es einen neuen Block zum Einrichten von MySQL . Darin fügen wir MySQL zu unserem CI-Prozess hinzu und definieren gleichzeitig die Variablen, die wir benötigen. Jetzt haben wir alles hinzugefügt, was wir wollten. Der letzte Schritt besteht darin, die Änderungen voranzutreiben und sicherzustellen, dass der Build erfolgreich ist und grün ist.

Aktualisierung der Dokumentation

Lassen Sie uns die Projektversion von 0.3.0-SNAPSHOT auf 0.4.0-SNAPSHOT in pom.xml aktualisieren und auch zu RELEASE_NOTES hinzufügen:
## 0.4.0-SNAPSHOT

*   JRTB-1: added repository layer.
Nach all dem erstellen wir eine Commit-, Push- und Pull-Anfrage. Und das Wichtigste: Unser Bau ist umweltfreundlich!„Java-Projekt von A bis Z“: Alles rund um die Datenbank hinzufügen.  Teil 2 - 3

Nützliche Links:

Alle Änderungen sind hier im erstellten Pull Request zu sehen . Vielen Dank an alle fürs Lesen.

Eine Liste aller Materialien der Serie finden Sie am Anfang dieses Artikels.

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