JavaRush /Java Blogu /Random-AZ /Verilənlər bazası ilə əlaqəli hər şeyi əlavə edirik. (2-c...
Roman Beekeeper
Səviyyə

Verilənlər bazası ilə əlaqəli hər şeyi əlavə edirik. (2-ci hissə) - "A-dan Z-yə Java layihəsi"

Qrupda dərc edilmişdir
Hamıya salam. Xatırladım: birinci hissədə Flyway əlavə etdik. Davam edək.

docker-compose.yml-ə verilənlər bazası əlavə edilməsi

Növbəti mərhələ əsas docker-compose.yml-də verilənlər bazası ilə işin qurulmasıdır. Gəlin verilənlər bazasını docker-compose faylına əlavə edək:
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'
Mən də ərizəmizə bu xətti əlavə etdim:
depends_on:
 - jrtb-db
Bu o deməkdir ki, biz proqrama başlamazdan əvvəl verilənlər bazasının başlamasını gözləyirik. Sonra, verilənlər bazası ilə işləmək üçün lazım olan daha iki dəyişənin əlavə edildiyini görə bilərsiniz:
${BOT_DB_USERNAME}
${BOT_DB_PASSWORD}
Biz onları docker-compose-də teleqram botu ilə eyni şəkildə - mühit dəyişənləri vasitəsilə əldə edəcəyik. Mən bunu elə etdim ki, verilənlər bazası istifadəçi adı və parolunun dəyərlərini təyin etdiyimiz yalnız bir yerimiz olsun. Biz onları tətbiqimizin docker şəklinə və verilənlər bazamızın docker konteynerinə ötürürük. Sonra SpringBoot-a verilənlər bazası üçün dəyişənləri qəbul etməyi öyrətmək üçün Dockerfile-ni yeniləmək lazımdır.
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"]
İndi biz Dockerfile verilənlər bazası dəyişənlərini əlavə edirik:
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
Dəyişən dəyərlər fərqli olacaq. Dockerfile-ə keçəcəyimizlər standart dəyərlər tələb edir, ona görə də bəzilərini daxil etdim. Son sətri iki elementlə genişləndiririk, onların köməyi ilə DB istifadəçi adı və şifrəni tətbiqin işə salınmasına keçirəcəyik:
"-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}"
Dockerfile-dəki son sətir (ENTRYPOINT ilə başlayır) bükülmədən olmalıdır. Əgər köçürmə etsəniz, bu kod işləməyəcək. Son addım dəyişənləri verilənlər bazasına ötürmək üçün start.sh faylını yeniləməkdir .
#!/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
Docker-compose-ni işə salmazdan əvvəl ətraf mühit dəyişənlərini necə əlavə edəcəyimizi artıq bilirik. Bunun üçün sadəcə var_name=var_value ixracını yerinə yetirmək lazımdır. Buna görə də biz yalnız iki sətir əlavə edirik:
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'
Bu, verilənlər bazası istifadəçi adı və şifrəni təyin etdiyimiz yerdir. Əlbəttə ki, botun adı və işarəsi üçün etdiyimiz kimi, bash skriptini işləyərkən bu dəyişənləri ötürmək mümkün olardı. Amma mənə elə gəlir ki, bu, lazımsızdır. Verilənlər bazasına faktiki olaraq daxil olmaq üçün verilənlər bazasının yerləşdiriləcəyi serverin IP-ni bilməli və sorğu üçün icazə verilən IP ünvanları siyahısında olmalısınız. Mənə gəlincə, bu artıq kifayətdir. Əsası qoyuldu: indi siz tərtibatçı üçün daha başa düşülən şeylər edə bilərsiniz - kod yaza bilərsiniz. Bundan əvvəl biz DevOps mühəndislərinin gördüyü işlərlə məşğul idik - ətraf mühitin qurulması.

Repozitor qatının əlavə edilməsi

Tipik olaraq bir tətbiq üç qatdan ibarətdir:
  1. Nəzarətçilər proqrama giriş nöqtələridir.
  2. Xidmətlər biznes məntiqinin işlədiyi yerdir. Bizdə artıq qismən bu var: SendMessageService biznes məntiqinin açıq nümayəndəsidir.
  3. Repozitoriyalar verilənlər bazası ilə işləmək üçün yerdir. Bizim vəziyyətimizdə bu teleqram botudur.
İndi üçüncü bir təbəqə əlavə edəcəyik - depolar. Burada Bahar ekosistemindən bir layihədən istifadə edəcəyik - Spring Data. Bunun nə olduğunu Habré-dəki bu məqalədə oxuya bilərsiniz . Bir neçə məqamı bilməliyik və başa düşməliyik:
  1. Biz JDBC ilə işləmək məcburiyyətində qalmayacağıq: biz birbaşa daha yüksək abstraksiyalarla işləyəcəyik. Yəni verilənlər bazasında cədvəllərə uyğun gələn POJO-ları saxlayın. Biz bu cür sinifləri obyekt adlandıracağıq , çünki onlar rəsmi olaraq Java Persistence API- də adlanır (bu, ORM vasitəsilə verilənlər bazası ilə işləmək üçün ümumi interfeyslər dəstidir, yəni JDBC ilə işləmək üzərində abstraksiyadır). Verilənlər bazasında saxlayacağımız bir varlıq sinfimiz olacaq və onlar tam olaraq bizə lazım olan cədvələ yazılacaq. Verilənlər bazasında axtarış edərkən eyni obyektləri alacağıq.
  2. Spring Data onların interfeys dəstindən istifadə etməyi təklif edir: JpaRepository , CrudRepository , və s... Başqa interfeyslər də var: tam siyahını burada tapa bilərsiniz . Gözəlliyi ondadır ki, onların üsullarını həyata keçirmədən də istifadə edə bilərsiniz(!). Üstəlik, interfeysdə yeni metodlar yaza biləcəyiniz müəyyən bir şablon var və onlar avtomatik olaraq həyata keçiriləcək.
  3. Bahar inkişafımızı bacardığı qədər asanlaşdırır. Bunun üçün öz interfeysimizi yaratmalı və yuxarıda təsvir edilənlərdən miras almalıyıq. Baharın bu interfeysdən istifadə etməsi lazım olduğunu bilməsi üçün Repository annotasiyasını əlavə edin.
  4. Əgər mövcud olmayan verilənlər bazası ilə işləmək üçün metod yazmaq lazımdırsa, bu da problem deyil - biz onu yazacağıq. Orada nə və necə edəcəyinizi sizə göstərəcəyəm.
Bu yazıda biz TelegramUser-in bütün yolu boyunca əlavə etməklə işləyəcəyik və bu hissəni nümunə kimi göstərəcəyik. Qalanını digər tapşırıqlar üzrə genişləndirəcəyik. Yəni /start əmrini yerinə yetirdikdə istifadəçimizin verilənlər bazasına active = true yazacağıq. Bu o deməkdir ki, istifadəçi botdan istifadə edir. Əgər istifadəçi artıq verilənlər bazasındadırsa, aktiv = doğru sahəsini yeniləyəcəyik. /stop əmrini yerinə yetirərkən biz istifadəçini silməyəcəyik, ancaq aktiv sahəni false olaraq yeniləyəcəyik ki, əgər istifadəçi botu yenidən istifadə etmək istəsə, onu işə salıb qaldığı yerdən davam etdirə bilsin. Və test edərkən nəyinsə baş verdiyini görə bilək, biz /stat əmri yaradacağıq: o, aktiv istifadəçilərin sayını göstərəcək. Bot, komanda, xidmət paketlərinin yanında depo paketi yaradırıq . Bu paketdə biz başqa bir varlıq yaradırıq . Müəssisə paketində biz TelegramUser sinfini yaradırıq:
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;
}
Burada javax.persistence paketindən bütün annotasiyaların bizdə olduğunu görə bilərsiniz. Bunlar bütün ORM tətbiqləri üçün istifadə olunan ümumi qeydlərdir. Varsayılan olaraq, Spring Data Jpa Hibernate-dən istifadə edir, baxmayaraq ki, digər tətbiqlər istifadə edilə bilər. İstifadə etdiyimiz annotasiyaların siyahısı budur:
  • Müəssisə - bunun verilənlər bazası ilə işləmək üçün bir qurum olduğunu göstərir;
  • Cədvəl - burada cədvəlin adını müəyyən edirik;
  • Id - annotasiya cədvəldə hansı sahənin İlkin Açar olacağını bildirir;
  • Sütun - cədvəldən sahənin adını müəyyənləşdirin.
Sonra verilənlər bazası ilə işləmək üçün interfeys yaradırıq. Tipik olaraq, belə interfeyslərin adları şablondan istifadə etməklə yazılır - EntiryNameRepository. Bizdə TelegramuserRepository olacaq:
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();
}
Burada heç yerdə tətbiq etmədiyim findAllByActiveTrue() metodunu necə əlavə etdiyimi görə bilərsiniz . Amma bu, onun işləməsinə mane olmayacaq. Spring Data , aktiv sahəsi = true olan tg_user cədvəlindən bütün qeydləri əldə etməli olduğunu başa düşəcəkdir . Biz TelegramUser obyekti ilə işləmək üçün xidmət əlavə edirik (biz SOLID-dən asılılığın inversiyasından o kontekstdə istifadə edirik ki, digər qurumların xidmətləri başqa bir qurumun repozitoriyası ilə birbaşa əlaqə saxlaya bilməz - yalnız həmin qurumun xidməti vasitəsilə). Biz paketdə TelegramUserService xidmətini yaradırıq, onun hələlik bir neçə üsulu olacaq: istifadəçini yadda saxla, istifadəçini şəxsiyyət vəsiqəsi ilə əldə et və aktiv istifadəçilərin siyahısını göstər. Əvvəlcə TelegramUserService interfeysini yaradırıq:
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);
}
Və əslində TelegramUserServiceImpl tətbiqi:
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);
   }
}
Burada qeyd etmək lazımdır ki, biz Autowired annotasiyasından istifadə edərək TelegramuserRepository obyektinin asılılıq inyeksiyasından (sinif nümunəsini təqdim edirik) və konstruktordan istifadə edirik. Siz bunu dəyişən üçün edə bilərsiniz, lakin bu, Spring Framework komandasının bizə tövsiyə etdiyi yanaşmadır.

Bot üçün statistik məlumatların əlavə edilməsi

Sonra /start və /stop əmrlərini yeniləməlisiniz. /start əmrindən istifadə edildikdə, yeni istifadəçini verilənlər bazasında saxlamaq və onu aktiv = doğru olaraq təyin etmək lazımdır. Və /stop olduqda, istifadəçi məlumatlarını yeniləyin: aktiv = false təyin edin. StartCommand sinifini düzəldək :
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);
   }
}
Burada həm də TelegramuserService obyektini konstruktora ötürürük, onunla yeni istifadəçini saxlayacağıq. Bundan əlavə, Java-da Optional-ın ləzzətlərindən istifadə edərək, aşağıdakı məntiq işləyir: verilənlər bazasında istifadəçi varsa, onu sadəcə aktivləşdiririk, əgər yoxdursa, yeni bir aktiv yaradırıq. Dayandır əmri:
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);
               });
   }
}
Eyni şəkildə TelegramServiceTest-i StopCommand-a keçirik. Əlavə məntiq belədir: əgər belə bir chat ID-si olan istifadəçimiz varsa, onu deaktiv edirik, yəni aktiv = false təyin edirik. Bunu öz gözlərinizlə necə görə bilərsiniz? Botun statistikasını göstərəcək yeni /stat əmri yaradaq. Bu mərhələdə bütün istifadəçilər üçün açıq olan sadə statistikalar olacaq. Gələcəkdə biz onu məhdudlaşdıracağıq və girişi yalnız idarəçilər üçün edəcəyik. Statistikada bir qeyd olacaq: aktiv bot istifadəçilərinin sayı. Bunu etmək üçün CommandName-ə STAT("/stat") dəyərini əlavə edin . Sonra StatCommand sinifini yaradın:
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));
   }
}
Burada hər şey sadədir: biz retrieveAllActiveUsers metodundan istifadə edərək bütün aktiv istifadəçilərin siyahısını alırıq və kolleksiyanın ölçüsünü əldə edirik. Biz də indi artan sinifləri yeniləməliyik: CommandContainerJavarushTelegramBot ki, onlar bizə lazım olan yeni xidməti keçməyi öyrənsinlər. Komanda Konteyneri:
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);
   }

}
Burada xəritəyə yeni bir əmr əlavə etdik və onu TelegramUserService konstruktorundan keçirdik. Ancaq botun özündə yalnız konstruktor dəyişəcək:
@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService) {
   this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this), telegramUserService);
}
İndi biz Autowired annotasiyasını əlavə edərək, TelegramUserService-i arqument kimi təqdim edirik. Bu o deməkdir ki, biz onu Tətbiq Kontekstindən alacağıq. Biz həmçinin HelpCommand sinfini yeniləyəcəyik ki, təsvirdə yeni statistika əmri görünsün.

Manual test

Gəlin verilənlər bazasını docker-compose-test.yml-dən və JavarushTelegramBotApplication sinfində əsas metoddan işə salaq. Sonra bir sıra əmrlər yazırıq:
  • /stat - biz gözləyirik ki, verilənlər bazası boş olarsa, bu botdan istifadə edən sıfır insan olacaq;
  • /start - botu işə salın;
  • /stat - indi botun 1 nəfər tərəfindən istifadə ediləcəyini gözləyirik;
  • /stop - botu dayandırmaq;
  • /stat - biz gözləyirik ki, yenə ondan istifadə edən 0 nəfər olacaq.
"A-dan Z-yə Java layihəsi": verilənlər bazası ilə əlaqəli hər şeyi əlavə etmək.  2-2 hissəNəticə sizin üçün eyni olarsa, funksionallığın düzgün işlədiyini və botun düzgün işlədiyini söyləyə bilərik. Bir şey səhv olarsa, fərq etməz: biz əsas metodu debug rejimində yenidən başladın və səhvin nə olduğunu tapmaq üçün bütün yolu aydın şəkildə keçirik.

Testlər yazırıq və yeniləyirik

Konstruktorları dəyişdirdiyimiz üçün test siniflərini də yeniləməli olacağıq. AbstractCommandTest sinfində daha bir sahə əlavə etməliyik - üç əmr üçün lazım olan TelegramUserService sinfi:
protected TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
Sonra, CommandContainer-də init() metodunu yeniləyək :
@BeforeEach
public void init() {
   SendBotMessageService sendBotMessageService = Mockito.mock(SendBotMessageService.class);
   TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
   commandContainer = new CommandContainer(sendBotMessageService, telegramUserService);
}
StartCommand-da getCommand() metodunu yeniləməlisiniz :
@Override
Command getCommand() {
   return new StartCommand(sendBotMessageService, telegramUserService);
}
Həmçinin StopCommand-da:
@Override
Command getCommand() {
   return new StopCommand(sendBotMessageService, telegramUserService);
}
Sonra, yeni testlərə baxaq. StatCommand üçün tipik bir test yaradaq :
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);
   }
}
Bu sadədir. İndi verilənlər bazası ilə işləməyi necə sınaqdan keçirəcəyimizdən danışaq. Əvvəllər etdiyimiz tək şey vahid testləri idi. İnteqrasiya testi tətbiqin çoxsaylı hissələri arasında inteqrasiyanı yoxlayır. Məsələn, proqramlar və verilənlər bazası. Burada hər şey daha mürəkkəb olacaq, çünki sınaq üçün bizə yerləşdirilən verilənlər bazası lazımdır. Buna görə də, testlərimizi yerli olaraq həyata keçirəndə, docker-compose-test.yml-dən işləyən verilənlər bazasına sahib olmalıyıq. Bu testi yerinə yetirmək üçün bütün SpringBoot tətbiqini işə salmalısınız. Test sinifində tətbiqi işə salacaq SpringBootTest annotasiyası var. Amma bu yanaşma bizim üçün işləməyəcək, çünki proqram işə salındıqda teleqram botu da işə düşəcək. Ancaq burada bir ziddiyyət var. Testlər həm yerli olaraq maşınımızda, həm də GitHub Actions vasitəsilə açıq şəkildə həyata keçiriləcək. Testlərin bütün tətbiqin işə salınması ilə keçməsi üçün biz onları teleqram botunda etibarlı məlumatlarla işlətməliyik: yəni adı və işarəsi ilə... Buna görə də bizim iki seçimimiz var:
  1. Odur ki, botun adını və nişanını ictimailəşdirin və ümid edin ki, hər şey yaxşı olacaq, heç kim ondan istifadə edib bizə müdaxilə etməyəcək.
  2. Başqa bir yol tapın.
Mən ikinci variantı seçdim. SpringBoot testində DataJpaTest annotasiyası var ki, verilənlər bazasını sınaqdan keçirərkən biz yalnız ehtiyac duyduğumuz siniflərdən istifadə edirik və başqalarını tək buraxırıq. Ancaq bu bizə uyğundur, çünki teleqram botu ümumiyyətlə işə salınmayacaq. Bu o deməkdir ki, ona etibarlı ad və işarə ötürməyə ehtiyac yoxdur!))) Spring Data-nın bizim üçün tətbiq etdiyi metodların gözlədiyimiz kimi işlədiyini yoxlayacağımız bir test alacağıq. Burada qeyd etmək vacibdir ki, biz test profilinin istifadəsini müəyyən etmək üçün @ActiveProfiles("test") annotasiyasından istifadə edirik. Bu, verilənlər bazamız üçün düzgün xassələri saya bilməmiz üçün bizə lazım olan şeydir. Testlərimizi keçirməzdən əvvəl verilənlər bazası hazırlasanız yaxşı olardı. Bu məsələdə belə bir yanaşma var: testə bir Sql annotasiyası əlavə edin və testə başlamazdan əvvəl işə salınmalı olan skript adları toplusunu keçin:
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
Bizim üçün onlar ./src/test/resources/ + annotasiyada göstərilən yol boyunca yerləşəcəklər. Onlar necə görünür:
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);
Nəticədə TelegramUserRepositoryIT testimiz belə görünəcək (gördüyünüz kimi, inteqrasiya testinin adı fərqli olacaq - biz Testi yox, İT əlavə edirik):
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());
   }
}
Testləri yazdıq, amma sual yaranır: GitHub-da CI prosesimizin işə salınması ilə nə baş verəcək? Onun verilənlər bazası olmayacaq. Hələlik həqiqətən yalnız qırmızı bir quruluş olacaq. Bunu etmək üçün GitHub əməliyyatlarımız var ki, bu əməliyyatlarda biz quruluşumuzun işə salınmasını konfiqurasiya edə bilərik. Testləri işə salmazdan əvvəl, lazımi parametrlərlə verilənlər bazası işə salınmasını əlavə etməlisiniz. Göründüyü kimi, İnternetdə çoxlu nümunələr yoxdur, ona görə də sizə bunu bir yerdə saxlamağı məsləhət görürəm. .github/workflows/maven.yml faylını yeniləyək:
# 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
İndi yeni bir MySQL qurma bloku var . Biz burada ehtiyac duyduğumuz dəyişənləri eyni vaxtda təyin edərək, CI prosesimizə MySQL əlavə edirik. İndi istədiyimiz hər şeyi əlavə etdik. Son mərhələ dəyişiklikləri itələmək və quruluşun keçəcəyini və yaşıl olacağını görməkdir.

Sənədlərin yenilənməsi

Gəlin layihə versiyasını pom.xml-də 0.3.0-SNAPSHOT-dan 0.4.0-SNAPSHOT-a yeniləyək və həmçinin RELEASE_NOTES-ə əlavə edək:
## 0.4.0-SNAPSHOT

*   JRTB-1: added repository layer.
Bütün bunlardan sonra biz commit, push and pull sorğusu yaradırıq. Və ən əsası, quruluşumuz yaşıldır!"A-dan Z-yə Java layihəsi": verilənlər bazası ilə əlaqəli hər şeyi əlavə etmək.  2-3 hissə

Faydalı bağlantılar:

Bütün dəyişiklikləri burada yaradılmış çəkmə sorğusunda görmək olar . Oxuduğunuz üçün hər kəsə təşəkkür edirəm.

Serialdakı bütün materialların siyahısı bu məqalənin əvvəlindədir.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION