JavaRush /Java Blog /Random-IT /Aggiungiamo tutto ciò che riguarda il database. (Parte 2)...
Roman Beekeeper
Livello 35

Aggiungiamo tutto ciò che riguarda il database. (Parte 2) - "Progetto Java dalla A alla Z"

Pubblicato nel gruppo Random-IT
Ciao a tutti. Lascia che te lo ricordi: nella prima parte abbiamo aggiunto Flyway. Continuiamo.

Aggiunta di un database a docker-compose.yml

La fase successiva è l'impostazione del lavoro con il database nel docker-compose.yml principale. Aggiungiamo il database al file 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'
Ho anche aggiunto questa riga alla nostra applicazione:
depends_on:
 - jrtb-db
Ciò significa che aspettiamo l'avvio del database prima di avviare l'applicazione. Successivamente, puoi notare l'aggiunta di altre due variabili di cui abbiamo bisogno per lavorare con il database:
${BOT_DB_USERNAME}
${BOT_DB_PASSWORD}
Li inseriremo in docker-compose allo stesso modo del bot di Telegram, attraverso variabili di ambiente. L'ho fatto in modo da avere un solo posto in cui impostare i valori del nome utente del database e della relativa password. Li passiamo all'immagine docker della nostra applicazione e al contenitore docker del nostro database. Successivamente dobbiamo aggiornare il Dockerfile per insegnare al nostro SpringBoot ad accettare le variabili per il database.
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"]
Ora aggiungiamo le variabili del database al Dockerfile:
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
I valori delle variabili saranno diversi. Quelli che passeremo nel Dockerfile, però, richiedono valori predefiniti, quindi ne ho inseriti alcuni. Espandiamo l'ultima riga con due elementi, con l'aiuto dei quali passeremo il nome utente e la password del DB all'avvio dell'applicazione:
"-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}"
L'ultima riga nel Dockerfile (che inizia con ENTRYPOINT) deve essere senza ritorno a capo. Se effettui un trasferimento, questo codice non funzionerà. L'ultimo passaggio consiste nell'aggiornare il file start.sh per passare le variabili al database.
#!/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
Sappiamo già come aggiungere variabili di ambiente prima di eseguire docker-compose. Per fare ciò, devi solo eseguire export var_name=var_value.. Pertanto, aggiungiamo solo due righe:
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'
Qui è dove impostiamo il nome utente e la password del database. Naturalmente, sarebbe possibile passare queste variabili durante l’esecuzione dello script bash, come facciamo per il nome e il token del bot. Ma mi sembra che questo non sia necessario. Per accedere effettivamente al database, è necessario conoscere l'IP del server su cui verrà distribuito il database ed essere nell'elenco degli indirizzi IP consentiti per la richiesta. Per quanto mi riguarda, questo è già sufficiente. Le basi sono state gettate: ora puoi fare cose più comprensibili per uno sviluppatore: scrivere codice. Prima di ciò, facevamo ciò che fanno gli ingegneri DevOps: configurare l'ambiente.

Aggiunta di un livello di repository

In genere un'applicazione ha tre livelli:
  1. I controller sono i punti di ingresso nell'applicazione.
  2. I servizi sono il luogo in cui funziona la logica aziendale. In parte lo abbiamo già: SendMessageService è un esplicito rappresentante della logica aziendale.
  3. I repository sono un luogo in cui lavorare con un database. Nel nostro caso si tratta di un bot di Telegram.
Ora aggiungeremo il terzo livello: i repository. Qui utilizzeremo un progetto dell'ecosistema Spring: Spring Data. Puoi leggere di cosa si tratta in questo articolo su Habré . Dobbiamo conoscere e comprendere diversi punti:
  1. Non dovremo lavorare con JDBC: lavoreremo direttamente con astrazioni più elevate. Cioè, memorizza i POJO che corrispondono alle tabelle nel database. Chiameremo tali classi entità , come vengono ufficialmente chiamate nell'API Java Persistence (questo è un insieme comune di interfacce per lavorare con un database tramite un ORM, ovvero un'astrazione rispetto al lavoro con JDBC). Avremo una classe di entità che salveremo nel database e verranno scritte esattamente nella tabella di cui abbiamo bisogno. Riceveremo gli stessi oggetti durante la ricerca nel database.
  2. Spring Data offre di utilizzare il proprio set di interfacce: JpaRepository , CrudRepository , ecc... Esistono altre interfacce: un elenco completo può essere trovato qui . Il bello è che puoi usare i loro metodi senza implementarli (!). Inoltre, esiste un determinato modello in base al quale è possibile scrivere nuovi metodi nell'interfaccia e verranno implementati automaticamente.
  3. La primavera semplifica il nostro sviluppo il più possibile. Per fare ciò, dobbiamo creare la nostra interfaccia ed ereditare da quelle sopra descritte. E affinché Spring sappia che deve utilizzare questa interfaccia, aggiungi l'annotazione Repository.
  4. Se dobbiamo scrivere un metodo per lavorare con un database che non esiste, anche questo non è un problema: lo scriveremo. Ti mostrerò cosa e come fare lì.
In questo articolo lavoreremo con l'aggiunta lungo l'intero percorso di TelegramUser e mostreremo questa parte come esempio. Espanderemo il resto su altri compiti. Cioè, quando eseguiremo il comando /start, scriveremo active = true nel database del nostro utente. Ciò significherà che l'utente sta utilizzando un bot. Se l'utente è già nel database, aggiorneremo il campo active = true. Quando eseguiamo il comando /stop, non elimineremo l'utente, ma aggiorneremo solo il campo attivo su false, in modo che se l'utente desidera utilizzare nuovamente il bot, potrà avviarlo e riprendere da dove aveva interrotto. E affinché durante il test possiamo vedere che sta succedendo qualcosa, creeremo un comando /stat: mostrerà il numero di utenti attivi. Creiamo un pacchetto repository accanto ai pacchetti bot, comandi e servizi. In questo pacchetto creiamo un'altra entità unica . Nel pacchetto entità creiamo 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;
}
Qui puoi vedere che abbiamo tutte le annotazioni del pacchetto javax.persistence. Queste sono annotazioni comuni utilizzate per tutte le implementazioni ORM. Per impostazione predefinita, Spring Data Jpa utilizza Hibernate, sebbene sia possibile utilizzare altre implementazioni. Ecco un elenco delle annotazioni che utilizziamo:
  • Entità : indica che si tratta di un'entità per lavorare con il database;
  • Tabella - qui definiamo il nome della tabella;
  • Id : l'annotazione indica quale campo sarà la chiave primaria nella tabella;
  • Colonna : determina il nome del campo dalla tabella.
Successivamente, creiamo un'interfaccia per lavorare con il database. In genere, i nomi di tali interfacce vengono scritti utilizzando il modello EntiryNameRepository. Avremo 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();
}
Qui puoi vedere come ho aggiunto il metodo findAllByActiveTrue() , che non implemento da nessuna parte. Ma questo non gli impedirà di lavorare. Spring Data capirà che deve ottenere tutti i record dalla tabella tg_user il cui campo attivo = true . Aggiungiamo un servizio per lavorare con l'entità TelegramUser (usiamo l'inversione delle dipendenze da SOLID nel contesto in cui i servizi di altre entità non possono comunicare direttamente con il repository di un'altra entità - solo attraverso il servizio di quell'entità). Creiamo un servizio TelegramUserService nel pacchetto, che per ora avrà diversi metodi: salvare l'utente, ottenere l'utente tramite il suo ID e visualizzare un elenco di utenti attivi. Per prima cosa creiamo l'interfaccia 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, infatti, l'implementazione di 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);
   }
}
Qui va notato che utilizziamo l'iniezione di dipendenza (introduciamo un'istanza di classe) dell'oggetto TelegramuserRepository utilizzando l' annotazione Autowired e sul costruttore. Puoi farlo per una variabile, ma questo è l'approccio consigliato dal team di Spring Framework.

Aggiunta di statistiche per il bot

Successivamente è necessario aggiornare i comandi /start e /stop. Quando viene utilizzato il comando /start, è necessario salvare il nuovo utente nel database e impostarlo su active = true. E quando c'è /stop, aggiorna i dati dell'utente: set active = false. Correggiamo 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);
   }
}
Qui passiamo anche l'oggetto TelegramuserService al costruttore, con il quale salveremo il nuovo utente. Inoltre, sfruttando le delizie dell'Optional in Java, funziona la seguente logica: se abbiamo un utente nel database, lo rendiamo semplicemente attivo, altrimenti ne creiamo uno nuovo attivo. Comando di arresto:
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);
               });
   }
}
Passiamo TelegramServiceTest a StopCommand allo stesso modo. La logica aggiuntiva è questa: se abbiamo un utente con tale ID chat, lo disattiviamo, cioè impostiamo active = false. Come puoi vederlo con i tuoi occhi? Creiamo un nuovo comando /stat, che visualizzerà le statistiche del bot. In questa fase, queste saranno semplici statistiche a disposizione di tutti gli utenti. In futuro lo limiteremo e renderemo l'accesso solo agli amministratori. Ci sarà una voce nelle statistiche: il numero di utenti bot attivi. A tale scopo, aggiungere il valore STAT("/stat") a CommandName. Successivamente, crea 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));
   }
}
Qui tutto è semplice: otteniamo un elenco di tutti gli utenti attivi utilizzando il metodo retrieveAllActiveUsers e otteniamo la dimensione della raccolta. Ora dobbiamo anche aggiornare le classi ascendenti: CommandContainer e JavarushTelegramBot in modo che imparino a trasferire il nuovo servizio di cui abbiamo bisogno. Contenitore comandi:
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);
   }

}
Qui abbiamo aggiunto un nuovo comando alla mappa e lo abbiamo passato attraverso il costruttore TelegramUserService. Ma nel bot stesso, cambierà solo il costruttore:
@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService) {
   this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this), telegramUserService);
}
Ora passiamo TelegramUserService come argomento, aggiungendo l'annotazione Autowired. Ciò significa che lo riceveremo dal contesto dell'applicazione. Aggiorneremo anche la classe HelpCommand in modo che nella descrizione appaia un nuovo comando statistico.

Test manuale

Lanciamo il database da docker-compose-test.yml e il metodo main nella classe JavarushTelegramBotApplication. Successivamente scriviamo una serie di comandi:
  • /stat: prevediamo che se il database è vuoto, non ci saranno persone che utilizzeranno questo bot;
  • /start - avvia il bot;
  • /stat - ora ci aspettiamo che il bot venga utilizzato da 1 persona;
  • /stop: ferma il bot;
  • /stat - prevediamo che ancora una volta ci saranno 0 persone che lo utilizzeranno.
"Progetto Java dalla A alla Z": aggiunta di tutto ciò che riguarda il database.  Parte 2 - 2Se il risultato è lo stesso per te, possiamo dire che la funzionalità ha funzionato correttamente e il bot funziona correttamente. Se qualcosa va storto, non importa: riavviamo il metodo principale in modalità debug e percorriamo chiaramente l'intero percorso per scoprire quale fosse l'errore.

Scriviamo e aggiorniamo i test

Poiché abbiamo modificato i costruttori, dovremo aggiornare anche le classi di test. Nella classe AbstractCommandTest , dobbiamo aggiungere un altro campo: la classe TelegramUserService , necessaria per tre comandi:
protected TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
Successivamente, aggiorniamo il metodo init() 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 devi aggiornare il metodo getCommand() :
@Override
Command getCommand() {
   return new StartCommand(sendBotMessageService, telegramUserService);
}
Anche in StopCommand:
@Override
Command getCommand() {
   return new StopCommand(sendBotMessageService, telegramUserService);
}
Successivamente, diamo un'occhiata ai nuovi test. Creiamo un tipico test per 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);
   }
}
Questo è semplice. Ora parliamo di come testeremo il funzionamento con il database. Tutto quello che facevamo prima erano test unitari. Un test di integrazione verifica l'integrazione tra più parti di un'applicazione. Ad esempio, applicazioni e database. Qui tutto sarà più complicato, perché per i test abbiamo bisogno di un database distribuito. Pertanto, quando eseguiamo i nostri test localmente, dobbiamo avere il database in esecuzione da docker-compose-test.yml. Per eseguire questo test, è necessario eseguire l'intera applicazione SpringBoot. La classe test ha un'annotazione SpringBootTest che avvierà l'applicazione. Ma questo approccio non funzionerà per noi, perché quando viene avviata l'applicazione, verrà avviato anche il bot di Telegram. Ma qui c’è una contraddizione. I test verranno eseguiti sia localmente sul nostro computer che pubblicamente tramite GitHub Actions. Affinché i test passino con il lancio dell'intera applicazione, dobbiamo eseguirli con dati validi sul bot di Telegram: cioè con il suo nome e token... Pertanto, abbiamo due opzioni:
  1. Quindi rendi pubblici il nome e il token del bot e spera che tutto vada bene, nessuno lo userà e interferirà con noi.
  2. Trova un altro modo.
Ho scelto la seconda opzione. Il test SpringBoot ha l' annotazione DataJpaTest , che è stata creata in modo che durante il test di un database utilizziamo solo le classi di cui abbiamo bisogno e lasciamo stare le altre. Ma questo ci va bene, perché il bot di Telegram non si avvia affatto. Ciò significa che non è necessario passargli un nome e un token validi!))) Riceveremo un test in cui controlleremo che i metodi che Spring Data implementa per noi funzionino come ci aspettiamo. È importante notare qui che utilizziamo l' annotazione @ActiveProfiles("test") per specificare l'uso del profilo di test. E questo è esattamente ciò di cui abbiamo bisogno per poter contare le proprietà corrette per il nostro database. Sarebbe bello avere un database preparato prima di eseguire i nostri test. Esiste un approccio del genere a questo problema: aggiungere un'annotazione SQL al test e passarle una raccolta di nomi di script che devono essere eseguiti prima di iniziare il test:
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
Per noi si troveranno lungo il percorso ./src/test/resources/ + il percorso specificato nell'annotazione. Ecco come appaiono:
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);
Questo è come apparirà come risultato il nostro test TelegramUserRepositoryIT (come puoi vedere, il nome per il test di integrazione sarà diverso: aggiungiamo IT, non 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());
   }
}
Abbiamo scritto i test, ma sorge spontanea la domanda: cosa accadrà con il lancio del nostro processo CI su GitHub? Non avrà un database. Per ora ci sarà davvero solo una build rossa. Per fare ciò, disponiamo di azioni GitHub, in cui possiamo configurare il lancio della nostra build. Prima di eseguire i test, è necessario aggiungere un avvio del database con le impostazioni necessarie. A quanto pare non ci sono molti esempi su Internet, quindi ti consiglio di salvarlo da qualche parte. Aggiorniamo il file .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
Ora c'è un nuovo blocco Configura MySQL . In esso aggiungiamo MySQL al nostro processo CI, definendo contemporaneamente le variabili di cui abbiamo bisogno. Ora abbiamo aggiunto tutto ciò che volevamo. L'ultima fase è spingere le modifiche e vedere che la build passerà e sarà verde.

Aggiornamento della documentazione

Aggiorniamo la versione del progetto da 0.3.0-SNAPSHOT a 0.4.0-SNAPSHOT in pom.xml e aggiungiamo anche a RELEASE_NOTES:
## 0.4.0-SNAPSHOT

*   JRTB-1: added repository layer.
Dopo tutto ciò, creiamo una richiesta di commit, push e pull. E, cosa più importante, la nostra costruzione è ecologica!"Progetto Java dalla A alla Z": aggiunta di tutto ciò che riguarda il database.  Parte 2 - 3

Link utili:

Tutte le modifiche possono essere visualizzate qui nella richiesta pull creata . Grazie a tutti per aver letto.

Un elenco di tutti i materiali della serie si trova all'inizio di questo articolo.

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