JavaRush /Java blogi /Random-UZ /Biz ma'lumotlar bazasiga tegishli hamma narsani qo'shamiz...

Biz ma'lumotlar bazasiga tegishli hamma narsani qo'shamiz. (2-qism) - "A dan Zgacha Java loyihasi"

Guruhda nashr etilgan
Hammaga salom. Sizga eslatib o'taman: birinchi qismda biz Flyway-ni qo'shdik. Davom etaylik.

Docker-compose.yml ga ma'lumotlar bazasini qo'shish

Keyingi bosqich - asosiy docker-compose.yml-da ma'lumotlar bazasi bilan ishlashni sozlash. Keling, ma'lumotlar bazasini docker-compose fayliga qo'shamiz:
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'
Men ushbu qatorni ilovamizga ham qo'shdim:
depends_on:
 - jrtb-db
Bu shuni anglatadiki, biz dasturni ishga tushirishdan oldin ma'lumotlar bazasi ishga tushishini kutamiz. Keyinchalik, ma'lumotlar bazasi bilan ishlashimiz kerak bo'lgan yana ikkita o'zgaruvchining qo'shilishini ko'rishingiz mumkin:
${BOT_DB_USERNAME}
${BOT_DB_PASSWORD}
Biz ularni docker-compose-da telegram botidagi kabi - muhit o'zgaruvchilari orqali olamiz. Men buni shunday qildimki, bizda ma'lumotlar bazasi foydalanuvchi nomi va uning paroli qiymatlarini o'rnatadigan faqat bitta joy bor. Biz ularni ilovamizning docker tasviriga va ma'lumotlar bazasining docker konteyneriga o'tkazamiz. Keyin SpringBoot-ni ma'lumotlar bazasi uchun o'zgaruvchilarni qabul qilishni o'rgatish uchun Dockerfile-ni yangilashimiz kerak.
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"]
Endi biz Dockerfile-ga ma'lumotlar bazasi o'zgaruvchilarini qo'shamiz:
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
O'zgaruvchan qiymatlar boshqacha bo'ladi. Biz Dockerfile-ga o'tadiganlar standart qiymatlarni talab qiladi, shuning uchun men ba'zilarini kiritdim. Biz oxirgi qatorni ikkita element bilan kengaytiramiz, ularning yordamida biz dasturni ishga tushirish uchun JB foydalanuvchi nomi va parolini o'tkazamiz:
"-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}"
Dockerfiledagi oxirgi satr (ENTRYPOINT bilan boshlanadi) o'ramsiz bo'lishi kerak. Agar pul o'tkazmasini amalga oshirsangiz, bu kod ishlamaydi. Oxirgi qadam o'zgaruvchilarni ma'lumotlar bazasiga o'tkazish uchun start.sh faylini yangilashdir .
#!/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
Biz docker-compose ni ishga tushirishdan oldin atrof-muhit o'zgaruvchilarini qanday qo'shishni allaqachon bilamiz. Buni amalga oshirish uchun faqat var_name=var_value eksportini bajarish kerak.. Shuning uchun biz faqat ikkita qatorni qo'shamiz:
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'
Bu erda biz ma'lumotlar bazasining foydalanuvchi nomi va parolini o'rnatamiz. Albatta, bu o'zgaruvchilarni bot nomi va tokeni uchun qilganimiz kabi, bash skriptini ishga tushirishda uzatish mumkin edi. Lekin bu menga kerak emasdek tuyuladi. Ma'lumotlar bazasiga haqiqiy kirish uchun siz ma'lumotlar bazasi joylashtiriladigan serverning IP-manzilini bilishingiz va so'rov uchun ruxsat etilgan IP-manzillar ro'yxatida bo'lishingiz kerak. Menga kelsak, bu allaqachon etarli. Poydevor qo'yildi: endi siz ishlab chiquvchi uchun tushunarliroq narsalarni qilishingiz mumkin - kod yozish. Bungacha biz DevOps muhandislari qiladigan ishni bajarar edik - atrof-muhitni sozlash.

Repository qatlamini qo'shish

Odatda dastur uchta qatlamdan iborat:
  1. Nazoratchilar ilovaga kirish nuqtalari hisoblanadi.
  2. Xizmatlar biznes mantig'i ishlaydi. Bizda allaqachon qisman bor: SendMessageService biznes mantig'ining aniq vakili.
  3. Repozitariylar ma'lumotlar bazasi bilan ishlash uchun joy. Bizning holatda, bu telegram boti.
Endi biz uchinchi qatlamni - omborlarni qo'shamiz. Bu erda biz Bahor ekotizimining loyihasidan foydalanamiz - Spring Data. Bu nima haqida Habré-dagi ushbu maqolada o'qishingiz mumkin . Biz bir nechta fikrlarni bilishimiz va tushunishimiz kerak:
  1. Biz JDBC bilan ishlashimiz shart emas: biz to'g'ridan-to'g'ri yuqori abstraktsiyalar bilan ishlaymiz. Ya'ni, ma'lumotlar bazasidagi jadvallarga mos keladigan POJO'larni saqlang. Biz bunday sinflarni ob'ekt deb ataymiz , chunki ular rasmiy ravishda Java Persistence API- da chaqiriladi (bu ORM orqali ma'lumotlar bazasi bilan ishlash uchun umumiy interfeyslar to'plami, ya'ni JDBC bilan ishlash bo'yicha abstraktsiya). Bizda ma'lumotlar bazasida saqlaydigan ob'ekt sinfi bo'ladi va ular bizga kerak bo'lgan jadvalga yoziladi. Ma'lumotlar bazasida qidirishda biz bir xil ob'ektlarni olamiz.
  2. Spring Data o'zlarining interfeyslar to'plamidan foydalanishni taklif qiladi: JpaRepository , CrudRepository , va hokazo... Boshqa interfeyslar ham mavjud: to'liq ro'yxatni bu yerda topishingiz mumkin . Chiroyli tomoni shundaki, siz ularning usullarini amalga oshirmasdan ham foydalanishingiz mumkin(!). Bundan tashqari, interfeysda yangi usullarni yozishingiz mumkin bo'lgan ma'lum bir shablon mavjud va ular avtomatik ravishda amalga oshiriladi.
  3. Bahor bizning rivojlanishimizni iloji boricha soddalashtiradi. Buning uchun biz o'z interfeysimizni yaratishimiz va yuqorida tavsiflanganlardan meros olishimiz kerak. Bahor ushbu interfeysdan foydalanishi kerakligini bilishi uchun Repository izohini qo'shing.
  4. Agar biz mavjud bo'lmagan ma'lumotlar bazasi bilan ishlash usulini yozishimiz kerak bo'lsa, unda bu ham muammo emas - biz uni yozamiz. Men sizga u erda nima va qanday qilishni ko'rsataman.
Ushbu maqolada biz TelegramUser-ning butun yo'li bo'ylab qo'shish bilan ishlaymiz va ushbu qismni misol sifatida ko'rsatamiz. Qolganlarini boshqa vazifalarda kengaytiramiz. Ya'ni, /start buyrug'ini bajarganimizda, foydalanuvchimizning ma'lumotlar bazasiga active = true deb yozamiz. Bu foydalanuvchi botdan foydalanayotganini bildiradi. Agar foydalanuvchi allaqachon ma'lumotlar bazasida bo'lsa, faol = true maydonini yangilaymiz. /stop buyrug'ini bajarishda biz foydalanuvchini o'chirmaymiz, faqat faol maydonni yolg'onga yangilaymiz, shunda foydalanuvchi botni qayta ishlatmoqchi bo'lsa, uni ishga tushirib, to'xtagan joyidan davom ettirishi mumkin. Va sinov paytida biz nimadir sodir bo'layotganini ko'rishimiz uchun /stat buyrug'ini yaratamiz: u faol foydalanuvchilar sonini ko'rsatadi. Biz bot, buyruq, xizmat paketlari yonida ombor paketini yaratamiz. Ushbu paketda biz boshqa bir ob'ekt yaratamiz . Ob'ekt paketida biz TelegramUser sinfini yaratamiz:
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;
}
Bu yerda javax.persistence paketidagi barcha izohlar borligini ko'rishingiz mumkin. Bu barcha ORM ilovalari uchun ishlatiladigan umumiy izohlardir. Odatiy bo'lib, Spring Data Jpa Hibernate-dan foydalanadi, ammo boshqa ilovalardan foydalanish mumkin. Mana biz foydalanadigan izohlar ro'yxati:
  • Ob'ekt - bu ma'lumotlar bazasi bilan ishlash uchun ob'ekt ekanligini ko'rsatadi;
  • Jadval - bu yerda jadval nomini aniqlaymiz;
  • Id - izohda jadvalda qaysi maydon Asosiy kalit bo'lishi aytiladi;
  • Ustun - jadvaldan maydon nomini aniqlang.
Keyinchalik, ma'lumotlar bazasi bilan ishlash uchun interfeys yaratamiz. Odatda, bunday interfeyslarning nomlari shablon yordamida yoziladi - EntiryNameRepository. Bizda TelegramuserRepository bo'ladi:
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();
}
Bu yerda siz findAllByActiveTrue() usulini qanday qo'shganimni ko'rishingiz mumkin , men uni hech qayerda qo'llamayman. Ammo bu uning ishlashiga to'sqinlik qilmaydi. Spring Data faol maydoni = true tg_user jadvalidan barcha yozuvlarni olish kerakligini tushunadi . Biz TelegramUser ob'ekti bilan ishlash xizmatini qo'shamiz (biz SOLID-dan qaramlik inversiyasidan foydalanamiz, chunki boshqa ob'ektlarning xizmatlari boshqa ob'ektning repozitori bilan bevosita bog'lana olmaydi - faqat shu ob'ekt xizmati orqali). Biz paketda TelegramUserService xizmatini yaratamiz, unda hozircha bir nechta usullar mavjud: foydalanuvchini saqlash, foydalanuvchini uning identifikatori bo'yicha olish va faol foydalanuvchilar ro'yxatini ko'rsatish. Avval TelegramUserService interfeysini yaratamiz:
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);
}
Va, aslida, TelegramUserServiceImpl-ni amalga oshirish:
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);
   }
}
Bu erda shuni ta'kidlash kerakki, biz Autowired izohidan foydalangan holda TelegramuserRepository ob'ektiga bog'liqlik in'ektsiyasidan (sinf namunasini kiritamiz) va konstruktorda foydalanamiz. Siz buni o'zgaruvchi uchun qilishingiz mumkin, ammo bu Spring Framework jamoasi bizga tavsiya qiladigan yondashuv.

Bot uchun statistik ma'lumotlarni qo'shish

Keyin / start va / stop buyruqlarini yangilashingiz kerak. /start buyrug'idan foydalanilganda, siz yangi foydalanuvchini ma'lumotlar bazasida saqlashingiz va uni faol = true ga o'rnatishingiz kerak. Va /stop mavjud bo'lganda, foydalanuvchi ma'lumotlarini yangilang: faol = noto'g'ri belgilang. StartCommand sinfini tuzatamiz :
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);
   }
}
Bu erda biz TelegramuserService obyektini ham konstruktorga o'tkazamiz, uning yordamida biz yangi foydalanuvchini saqlab qolamiz. Bundan tashqari, Java-da Optional-ning zavqlaridan foydalanib, quyidagi mantiq ishlaydi: agar bizda ma'lumotlar bazasida foydalanuvchi bo'lsa, biz uni shunchaki faol qilamiz, agar bo'lmasa, biz yangisini yaratamiz. To'xtatish buyrug'i:
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);
               });
   }
}
Biz TelegramServiceTest-ni StopCommand-ga xuddi shu tarzda o'tkazamiz. Qo'shimcha mantiq shunday: agar bizda shunday chat identifikatoriga ega foydalanuvchi bo'lsa, biz uni o'chirib qo'yamiz, ya'ni faol = yolg'onni o'rnatamiz. Buni o'z ko'zingiz bilan qanday ko'rishingiz mumkin? Keling, yangi /stat buyrug'ini yarataylik, unda bot statistikasi ko'rsatiladi. Ushbu bosqichda bu barcha foydalanuvchilar uchun mavjud bo'lgan oddiy statistika bo'ladi. Kelajakda biz uni cheklaymiz va faqat administratorlar uchun ruxsat beramiz. Statistikada bitta yozuv bo'ladi: faol bot foydalanuvchilari soni. Buning uchun CommandName-ga STAT("/stat") qiymatini qo'shing . Keyin StatCommand sinfini yarating:
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));
   }
}
Bu erda hamma narsa oddiy: biz retrieveAllActiveUsers usuli yordamida barcha faol foydalanuvchilar ro'yxatini olamiz va to'plam hajmini olamiz. Endi biz o'sib borayotgan sinflarni yangilashimiz kerak: CommandContainer va JavarushTelegramBot , ular bizga kerak bo'lgan yangi xizmatni uzatishni o'rganishlari uchun. Buyruqlar 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);
   }

}
Bu yerda biz xaritaga yangi buyruq qo‘shdik va uni TelegramUserService konstruktori orqali o‘tkazdik. Ammo botning o'zida faqat konstruktor o'zgaradi:
@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService) {
   this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this), telegramUserService);
}
Endi biz TelegramUserService-ni argument sifatida uzatamiz va Autowired izohini qo'shamiz. Bu shuni anglatadiki, biz uni Ilova kontekstidan olamiz. Shuningdek , tavsifda yangi statistika buyrug'i paydo bo'lishi uchun HelpCommand sinfini yangilaymiz .

Qo'lda sinov

Keling, docker-compose-test.yml dan ma'lumotlar bazasini va JavarushTelegramBotApplication sinfidagi asosiy usulni ishga tushiramiz. Keyin biz buyruqlar to'plamini yozamiz:
  • /stat - agar ma'lumotlar bazasi bo'sh bo'lsa, bu botdan foydalanuvchi nol bo'lishini kutamiz;
  • /start - botni ishga tushirish;
  • /stat - endi biz botdan 1 kishi foydalanishini kutamiz;
  • /stop - botni to'xtatish;
  • /stat - biz undan yana 0 kishi foydalanishini kutamiz.
"A dan Zgacha Java loyihasi": ma'lumotlar bazasiga tegishli hamma narsani qo'shish.  2-2 qismAgar natija siz uchun bir xil bo'lsa, biz funksionallik to'g'ri ishlagan va bot to'g'ri ishlayapti deb aytishimiz mumkin. Agar biror narsa noto'g'ri bo'lsa, bu muhim emas: biz disk raskadrovka rejimida asosiy usulni qayta ishga tushiramiz va xato nima ekanligini aniqlash uchun butun yo'lni aniq bosib o'tamiz.

Biz testlarni yozamiz va yangilaymiz

Konstruktorlarni o'zgartirganimiz sababli, test sinflarini ham yangilashimiz kerak bo'ladi. AbstractCommandTest sinfida biz yana bitta maydonni qo'shishimiz kerak - uchta buyruq uchun zarur bo'lgan TelegramUserService sinfi:
protected TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
Keyin, CommandContainer-da init() usulini yangilaymiz :
@BeforeEach
public void init() {
   SendBotMessageService sendBotMessageService = Mockito.mock(SendBotMessageService.class);
   TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
   commandContainer = new CommandContainer(sendBotMessageService, telegramUserService);
}
StartCommand da getCommand() usulini yangilashingiz kerak :
@Override
Command getCommand() {
   return new StartCommand(sendBotMessageService, telegramUserService);
}
Shuningdek, StopCommand da:
@Override
Command getCommand() {
   return new StopCommand(sendBotMessageService, telegramUserService);
}
Keling, yangi testlarni ko'rib chiqaylik. StatCommand uchun odatiy test yarataylik :
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 oddiy. Keling, ma'lumotlar bazasi bilan ishlashni qanday sinab ko'rishimiz haqida gapiraylik. Ilgari qilgan barchamiz birlik sinovlari edi. Integratsiya testi ilovaning bir nechta qismlari o'rtasidagi integratsiyani sinovdan o'tkazadi. Masalan, ilovalar va ma'lumotlar bazalari. Bu erda hamma narsa murakkabroq bo'ladi, chunki sinov uchun bizga joylashtirilgan ma'lumotlar bazasi kerak. Shuning uchun, testlarimizni mahalliy sifatida o'tkazganimizda, bizda docker-compose-test.yml dan ishlaydigan ma'lumotlar bazasi bo'lishi kerak. Ushbu testni bajarish uchun siz SpringBoot dasturini to'liq ishga tushirishingiz kerak. Sinov sinfida dasturni ishga tushiradigan SpringBootTest izohi mavjud. Lekin bu yondashuv biz uchun ishlamaydi, chunki ilova ishga tushganda telegram boti ham ishga tushadi. Ammo bu erda qarama-qarshilik bor. Sinovlar bizning mashinamizda ham mahalliy, ham GitHub Actions orqali ommaviy ravishda o'tkaziladi. Testlar butun ilova ishga tushirilishi bilan o‘tishi uchun biz ularni telegram botdagi haqiqiy ma’lumotlar bilan ishga tushirishimiz kerak: ya’ni uning nomi va tokeni bo‘yicha... Shuning uchun bizda ikkita variant bor:
  1. Shunday ekan, botning nomi va tokenini ommaga oshkor eting va hamma narsa yaxshi bo'lishiga umid qiling, hech kim undan foydalanmaydi va bizga aralashmaydi.
  2. Boshqa yo'l bilan keling.
Men ikkinchi variantni tanladim. SpringBoot testida DataJpaTest annotatsiyasi mavjud bo'lib, u ma'lumotlar bazasini sinab ko'rishda biz faqat kerakli sinflardan foydalanamiz va boshqalarni yolg'iz qoldiramiz. Lekin bu bizga mos keladi, chunki telegram boti umuman ishga tushmaydi. Bu shuni anglatadiki, unga to'g'ri nom va tokenni topshirishning hojati yo'q!))) Biz sinovdan o'tamiz, unda Spring Data biz uchun amalga oshiradigan usullar biz kutgandek ishlashini tekshiramiz. Bu erda shuni ta'kidlash kerakki, biz test profilidan foydalanishni belgilash uchun @ActiveProfiles("test") izohidan foydalanamiz. Va ma'lumotlar bazasi uchun to'g'ri xususiyatlarni hisoblashimiz uchun aynan shu narsa kerak. Sinovlarimizni o'tkazishdan oldin ma'lumotlar bazasi tayyorlangan bo'lsa yaxshi bo'lardi. Bu masala bo'yicha shunday yondashuv mavjud: testga Sql izohini qo'shing va testni boshlashdan oldin ishga tushirilishi kerak bo'lgan skript nomlari to'plamini topshiring:
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
Biz uchun ular ./src/test/resources/ + izohda ko'rsatilgan yo'l bo'ylab joylashgan bo'ladi. Mana ular qanday ko'rinishga ega:
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);
Natijada TelegramUserRepositoryIT testimiz shunday ko'rinishga ega bo'ladi (ko'rib turganingizdek, integratsiya testining nomi boshqacha bo'ladi - biz Testni emas, ITni qo'shamiz):
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());
   }
}
Biz testlarni yozdik, ammo savol tug'iladi: GitHub-da CI jarayonimizni ishga tushirish bilan nima bo'ladi? Unda ma'lumotlar bazasi bo'lmaydi. Hozircha, albatta, faqat qizil qurilish bo'ladi. Buning uchun bizda GitHub amallari mavjud bo'lib, ularda biz o'z qurilishimizni ishga tushirishni sozlashimiz mumkin. Sinovlarni o'tkazishdan oldin kerakli sozlamalar bilan ma'lumotlar bazasini ishga tushirishni qo'shishingiz kerak. Ma'lum bo'lishicha, Internetda juda ko'p misollar yo'q, shuning uchun men buni biron bir joyda saqlashni maslahat beraman. .github/workflows/maven.yml faylini yangilaymiz:
# 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
Endi yangi MySQL o'rnatish bloki mavjud . Unda biz MySQL-ni CI jarayonimizga qo'shamiz va bir vaqtning o'zida kerakli o'zgaruvchilarni aniqlaymiz. Endi biz xohlagan hamma narsani qo'shdik. Oxirgi bosqich - o'zgarishlarni surish va qurilishning o'tishi va yashil bo'lishini ko'rish.

Hujjatlarni yangilash

Loyiha versiyasini pom.xml formatida 0.3.0-SNAPSHOT dan 0.4.0-SNAPSHOT ga yangilaymiz va RELEASE_NOTES ga qo‘shamiz:
## 0.4.0-SNAPSHOT

*   JRTB-1: added repository layer.
Bularning barchasidan so'ng biz majburiyat, surish va tortish so'rovini yaratamiz. Va eng muhimi, bizning qurilishimiz yashil rangda!"A dan Zgacha Java loyihasi": ma'lumotlar bazasiga tegishli hamma narsani qo'shish.  2-3 qism

Foydali havolalar:

Barcha o'zgarishlar bu erda yaratilgan tortish so'rovida ko'rish mumkin . O'qiganingiz uchun barchaga rahmat.

Seriyadagi barcha materiallar ro'yxati ushbu maqolaning boshida.

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