JavaRush /وبلاگ جاوا /Random-FA /ما همه چیز مربوط به پایگاه داده را اضافه می کنیم. (قسمت 2...
Roman Beekeeper
مرحله

ما همه چیز مربوط به پایگاه داده را اضافه می کنیم. (قسمت 2) - "پروژه جاوا از A تا Z"

در گروه منتشر شد
سلام به همه. یادآوری می کنم: در قسمت اول Flyway را اضافه کردیم. بیا ادامه بدهیم.

افزودن پایگاه داده به docker-compose.yml

مرحله بعدی راه اندازی کار با پایگاه داده در docker-compose.yml اصلی است. بیایید پایگاه داده را به فایل 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'
من همچنین این خط را به برنامه ما اضافه کردم:
depends_on:
 - jrtb-db
این بدان معنی است که ما قبل از شروع برنامه منتظر می مانیم تا پایگاه داده شروع به کار کند. در مرحله بعد، می توانید متوجه اضافه شدن دو متغیر دیگر شوید که برای کار با پایگاه داده نیاز داریم:
${BOT_DB_USERNAME}
${BOT_DB_PASSWORD}
ما آنها را در docker-compose مانند ربات تلگرام - از طریق متغیرهای محیطی دریافت می کنیم. من این کار را انجام دادم تا فقط یک مکان داشته باشیم که در آن مقادیر نام کاربری پایگاه داده و رمز عبور آن را تنظیم کنیم. ما آنها را به تصویر docker برنامه خود و به کانتینر docker پایگاه داده خود منتقل می کنیم. در مرحله بعد باید Dockerfile را به روز کنیم تا به SpringBoot خود بپذیریم که متغیرها را برای پایگاه داده بپذیرد.
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"]
اکنون متغیرهای پایگاه داده را به Dockerfile اضافه می کنیم:
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
مقادیر متغیر متفاوت خواهد بود. با این حال، مواردی که به Dockerfile منتقل می کنیم، به مقادیر پیش فرض نیاز دارند، بنابراین من مقداری را وارد کردم. آخرین خط را با دو عنصر گسترش می دهیم که با کمک آنها نام کاربری و رمز عبور DB را به راه اندازی برنامه منتقل می کنیم:
"-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}"
آخرین خط در Dockerfile (که با ENTRYPOINT شروع می شود) باید بدون بسته بندی باشد. اگر انتقالی انجام دهید، این کد کار نخواهد کرد. آخرین مرحله به روز رسانی فایل start.sh برای ارسال متغیرها به پایگاه داده است.
#!/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 اضافه کنیم. برای انجام این کار، فقط باید صادرات var_name=var_value را اجرا کنید. بنابراین، ما فقط دو خط اضافه می کنیم:
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'
اینجاست که ما نام کاربری و رمز عبور پایگاه داده را تنظیم می کنیم. البته، می‌توان این متغیرها را هنگام اجرای اسکریپت bash پاس داد، همانطور که برای نام و توکن ربات انجام می‌دهیم. اما به نظر من این غیر ضروری است. برای دسترسی واقعی به پایگاه داده، باید IP سروری را که پایگاه داده روی آن مستقر می‌شود، بدانید و در لیست آدرس‌های IP مجاز درخواست باشید. در مورد من، این در حال حاضر کافی است. پایه و اساس گذاشته شده است: اکنون می توانید کارهایی را انجام دهید که برای یک توسعه دهنده قابل درک تر است - کد بنویسید. قبل از آن، ما همان کاری را انجام می‌دادیم که مهندسان DevOps انجام می‌دهند - تنظیم محیط.

اضافه کردن یک لایه مخزن

به طور معمول یک برنامه دارای سه لایه است:
  1. کنترلرها نقاط ورود به برنامه هستند.
  2. خدمات جایی هستند که منطق تجاری کار می کند. ما قبلاً تا حدی این را داریم: SendMessageService نماینده صریح منطق تجاری است.
  3. مخازن مکانی برای کار با پایگاه داده هستند. در مورد ما، این یک ربات تلگرام است.
اکنون لایه سوم را اضافه می کنیم - مخازن. در اینجا ما از پروژه ای از اکوسیستم Spring - Spring Data استفاده خواهیم کرد. می توانید در این مقاله در Habré در مورد چیستی آن بخوانید . باید چندین نکته را بدانیم و بفهمیم:
  1. ما مجبور نیستیم با JDBC کار کنیم: ما مستقیماً با انتزاعات بالاتر کار خواهیم کرد. یعنی POJO هایی را که با جداول در پایگاه داده مطابقت دارند ذخیره کنید. ما به این کلاس‌ها entity می‌گوییم ، همانطور که رسماً در Java Persistence API نامیده می‌شوند (این یک مجموعه معمولی از رابط‌ها برای کار با پایگاه داده از طریق یک ORM است، یعنی یک انتزاع در کار با JDBC). ما یک کلاس entity خواهیم داشت که در پایگاه داده ذخیره می کنیم و دقیقاً در جدول مورد نیاز ما نوشته می شود. هنگام جستجو در پایگاه داده، همان اشیاء را دریافت خواهیم کرد.
  2. Spring Data پیشنهاد می کند از مجموعه رابط های خود استفاده کند: JpaRepository ، CrudRepository ، و غیره... واسط های دیگری نیز وجود دارد: فهرست کامل را می توانید در اینجا پیدا کنید . زیبایی این است که می توانید از روش های آنها بدون اجرای آنها استفاده کنید(!). علاوه بر این، یک الگوی خاص وجود دارد که با استفاده از آن می توانید روش های جدیدی را در رابط بنویسید و آنها به طور خودکار پیاده سازی می شوند.
  3. بهار توسعه ما را تا آنجا که می تواند ساده می کند. برای انجام این کار، ما باید رابط کاربری خود را ایجاد کنیم و از مواردی که در بالا توضیح داده شد، ارث ببریم. و برای اینکه Spring بداند که باید از این رابط استفاده کند، حاشیه نویسی Repository را اضافه کنید.
  4. اگر ما نیاز به نوشتن روشی برای کار با پایگاه داده ای داریم که وجود ندارد، این نیز مشکلی نیست - ما آن را می نویسیم. من به شما نشان خواهم داد که در آنجا چه کاری و چگونه انجام دهید.
در این مقاله با افزودن در کل مسیر تلگرام یوزر کار می کنیم و این قسمت را به عنوان نمونه نشان می دهیم. ما بقیه را در مورد وظایف دیگر گسترش خواهیم داد. یعنی وقتی دستور /start را اجرا می کنیم، Active = true را در پایگاه داده کاربر خود می نویسیم. این بدان معناست که کاربر از یک ربات استفاده می کند. اگر کاربر از قبل در پایگاه داده باشد، فیلد active = true را به روز می کنیم. هنگام اجرای دستور /stop، کاربر را حذف نمی کنیم، بلکه فقط فیلد فعال را به false به روز می کنیم تا در صورتی که کاربر بخواهد دوباره از ربات استفاده کند، آن را راه اندازی کند و از جایی که متوقف شده است ادامه دهد. و برای اینکه در هنگام تست ببینیم که چیزی در حال رخ دادن است، یک دستور /stat ایجاد می کنیم: تعداد کاربران فعال را نمایش می دهد. ما یک بسته مخزن در کنار بسته های ربات، فرمان، سرویس ایجاد می کنیم. در این بسته ما یک نهاد دیگر ایجاد می کنیم . در بسته entity کلاس 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;
}
در اینجا می توانید ببینید که ما تمام حاشیه نویسی های بسته javax.persistence را داریم. اینها حاشیه نویسی های رایجی هستند که برای همه پیاده سازی های ORM استفاده می شوند. به طور پیش فرض، Spring Data Jpa از Hibernate استفاده می کند، اگرچه می توان از پیاده سازی های دیگری نیز استفاده کرد. در اینجا لیستی از حاشیه نویسی هایی است که ما استفاده می کنیم:
  • نهاد - نشان می دهد که این موجودی برای کار با پایگاه داده است.
  • جدول - در اینجا نام جدول را تعریف می کنیم.
  • شناسه - حاشیه نویسی می گوید که کدام فیلد کلید اصلی در جدول خواهد بود.
  • ستون - نام فیلد را از جدول تعیین کنید.
در مرحله بعد، ما یک رابط برای کار با پایگاه داده ایجاد می کنیم. به طور معمول، نام چنین رابط هایی با استفاده از الگو - EntiryNameRepository نوشته می شود. ما یک 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();
}
در اینجا می‌توانید ببینید که چگونه متد findAllByActiveTrue() را اضافه کردم که در هیچ کجا پیاده‌سازی نمی‌کنم. اما این مانع از کار او نمی شود. Spring Data متوجه خواهد شد که باید تمام رکوردها را از جدول tg_user که فیلد فعال آن = true است دریافت کند . ما یک سرویس برای کار با موجودیت TelegramUser اضافه می کنیم (از وارونگی وابستگی از SOLID در شرایطی استفاده می کنیم که سرویس های موجودیت های دیگر نمی توانند مستقیماً با مخزن موجودیت دیگر ارتباط برقرار کنند - فقط از طریق سرویس آن موجودیت). ما یک سرویس TelegramUserService در بسته ایجاد می کنیم که در حال حاضر چندین روش دارد: ذخیره کاربر، دریافت کاربر با شناسه او و نمایش لیست کاربران فعال. ابتدا رابط کاربری 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);
}
و در واقع پیاده سازی 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);
   }
}
در اینجا لازم به ذکر است که ما از تزریق وابستگی (معرفی یک نمونه کلاس) شی TelegramuserRepository با استفاده از حاشیه نویسی Autowired و بر روی سازنده استفاده می کنیم. شما می توانید این کار را برای یک متغیر انجام دهید، اما این رویکردی است که تیم Spring Framework به ما توصیه می کند.

افزودن آمار برای ربات

در مرحله بعد باید دستورات /start و /stop را به روز کنید. هنگامی که دستور start / استفاده می شود، باید کاربر جدید را در پایگاه داده ذخیره کنید و آن را فعال = true تنظیم کنید. و هنگامی که /stop وجود دارد، داده های کاربر را به روز کنید: set active = false. بیایید کلاس 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);
   }
}
در اینجا شی TelegramuserService را نیز به سازنده ارسال می کنیم که با آن کاربر جدید را ذخیره می کنیم. علاوه بر این، با استفاده از لذت‌های Optional در جاوا، منطق زیر کار می‌کند: اگر کاربر در پایگاه داده داریم، به سادگی او را فعال می‌کنیم، اگر نه، یک کاربر فعال جدید ایجاد می‌کنیم. StopCommand:
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);
               });
   }
}
TelegramServiceTest را به همین ترتیب به StopCommand می دهیم. منطق اضافی این است: اگر کاربری با چنین ID چت داشته باشیم، آن را غیرفعال می کنیم، یعنی فعال = false را تنظیم می کنیم. چگونه می توانید این را با چشمان خود ببینید؟ بیایید یک دستور /stat جدید ایجاد کنیم که آمار ربات را نمایش می دهد. در این مرحله، این آمار ساده در دسترس همه کاربران خواهد بود. در آینده، آن را محدود خواهیم کرد و دسترسی را فقط برای مدیران فراهم خواهیم کرد. یک ورودی در آمار وجود خواهد داشت: تعداد کاربران فعال ربات. برای انجام این کار، مقدار STAT("/stat") را به CommandName اضافه کنید. سپس کلاس 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));
   }
}
همه چیز در اینجا ساده است: ما لیستی از همه کاربران فعال را با استفاده از روش retrieveAllActiveUsers دریافت می کنیم و اندازه مجموعه را بدست می آوریم. همچنین اکنون باید کلاس‌های صعودی را به‌روزرسانی کنیم: CommandContainer و JavarushTelegramBot تا یاد بگیرند سرویس جدیدی را که ما نیاز داریم منتقل کنند. 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);
   }

}
در اینجا یک دستور جدید به نقشه اضافه کردیم و آن را از سازنده TelegramUserService عبور دادیم. اما در خود ربات، فقط سازنده تغییر خواهد کرد:
@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService) {
   this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this), telegramUserService);
}
اکنون TelegramUserService را به عنوان آرگومان ارسال می کنیم و حاشیه نویسی Autowired را اضافه می کنیم. این بدان معناست که ما آن را از Application Context دریافت خواهیم کرد. همچنین کلاس HelpCommand را به روز می کنیم تا یک دستور آمار جدید در توضیحات ظاهر شود.

تست دستی

بیایید پایگاه داده را از docker-compose-test.yml و متد اصلی در کلاس JavarushTelegramBotApplication راه اندازی کنیم. سپس مجموعه ای از دستورات را می نویسیم:
  • /stat - ما انتظار داریم که اگر پایگاه داده خالی باشد، صفر نفر از این ربات استفاده کنند.
  • /start - ربات را شروع کنید.
  • /stat - اکنون انتظار داریم که ربات توسط 1 نفر استفاده شود.
  • /توقف - متوقف کردن ربات؛
  • /stat - ما انتظار داریم که دوباره 0 نفر از آن استفاده کنند.
"پروژه جاوا از A تا Z": افزودن همه چیز مربوط به پایگاه داده.  قسمت 2 - 2اگر نتیجه برای شما یکسان است، می توان گفت که عملکرد به درستی کار کرده و ربات به درستی کار می کند. اگر مشکلی پیش بیاید، مهم نیست: روش اصلی را در حالت اشکال زدایی مجدداً راه اندازی می کنیم و به وضوح کل مسیر را طی می کنیم تا خطا را پیدا کنیم.

ما تست ها را می نویسیم و به روز می کنیم

از آنجایی که سازنده ها را تغییر دادیم، باید کلاس های تست را نیز به روز کنیم. در کلاس AbstractCommandTest ، باید یک فیلد دیگر اضافه کنیم - کلاس TelegramUserService که برای سه دستور لازم است:
protected TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
در مرحله بعد، بیایید متد init() را در CommandContainer به روز کنیم :
@BeforeEach
public void init() {
   SendBotMessageService sendBotMessageService = Mockito.mock(SendBotMessageService.class);
   TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
   commandContainer = new CommandContainer(sendBotMessageService, telegramUserService);
}
در StartCommand باید متد getCommand() را به روز کنید :
@Override
Command getCommand() {
   return new StartCommand(sendBotMessageService, telegramUserService);
}
همچنین در StopCommand:
@Override
Command getCommand() {
   return new StopCommand(sendBotMessageService, telegramUserService);
}
در ادامه به بررسی تست های جدید می پردازیم. بیایید یک تست معمولی برای 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);
   }
}
این ساده است. حالا بیایید در مورد نحوه آزمایش کار با پایگاه داده صحبت کنیم. تمام کاری که ما قبلا انجام دادیم تست واحد بود. تست یکپارچه سازی یکپارچگی بین بخش های مختلف یک برنامه کاربردی را آزمایش می کند. به عنوان مثال، برنامه های کاربردی و پایگاه های داده. در اینجا همه چیز پیچیده تر خواهد شد، زیرا برای آزمایش ما به یک پایگاه داده مستقر نیاز داریم. بنابراین، زمانی که تست های خود را به صورت محلی اجرا می کنیم، باید پایگاه داده را از docker-compose-test.yml اجرا کنیم. برای اجرای این تست، باید کل اپلیکیشن SpringBoot را اجرا کنید. کلاس تست دارای یک حاشیه نویسی SpringBootTest است که برنامه را شروع می کند. اما این روش برای ما کارساز نخواهد بود، زیرا با راه اندازی اپلیکیشن، ربات تلگرام نیز راه اندازی می شود. اما در اینجا یک تناقض وجود دارد. آزمایش ها هم به صورت محلی روی دستگاه ما و هم به صورت عمومی از طریق GitHub Actions اجرا می شوند. برای اینکه تست ها با راه اندازی کل برنامه رد شوند، باید آنها را با داده های معتبر روی ربات تلگرام اجرا کنیم: یعنی با نام و نشان آن ... بنابراین دو گزینه داریم:
  1. پس نام و نشان ربات را عمومی کنید و امیدوار باشید که همه چیز خوب باشد، کسی از آن استفاده نکند و با ما دخالت نکند.
  2. راه دیگری را در نظر بگیرید.
من گزینه دوم را انتخاب کردم. تست SpringBoot دارای حاشیه نویسی DataJpaTest است که به گونه ای ایجاد شده است که هنگام آزمایش پایگاه داده، فقط از کلاس هایی که نیاز داریم استفاده می کنیم و دیگران را به حال خود رها می کنیم. اما این برای ما مناسب است، زیرا ربات تلگرام اصلا راه اندازی نمی شود. این بدان معنی است که نیازی به پاس کردن نام و نشانه معتبر نیست!))) ما یک تست دریافت می کنیم که در آن بررسی می کنیم که آیا روش هایی که Spring Data برای ما پیاده سازی می کند همانطور که انتظار داریم کار می کنند. در اینجا ذکر این نکته ضروری است که ما از حاشیه نویسی @ActiveProfiles("test") برای تعیین استفاده از نمایه آزمایشی استفاده می کنیم. و این دقیقاً همان چیزی است که ما نیاز داریم تا بتوانیم ویژگی های صحیح را برای پایگاه داده خود بشماریم. خوب است که قبل از اجرای آزمایشات خود یک پایگاه داده آماده کنیم. چنین رویکردی برای این موضوع وجود دارد: یک حاشیه نویسی Sql را به آزمون اضافه کنید و مجموعه ای از نام های اسکریپت را که باید قبل از شروع آزمایش اجرا شوند، به آن پاس دهید:
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
برای ما، آنها در امتداد مسیر ./src/test/resources/ + مسیر مشخص شده در حاشیه قرار خواهند گرفت. در اینجا ظاهر آنها به شرح زیر است:
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);
در نتیجه تست TelegramUserRepositoryIT ما به این صورت خواهد بود (همانطور که می بینید، نام تست یکپارچه سازی متفاوت خواهد بود - ما IT را اضافه می کنیم، نه 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());
   }
}
ما تست ها را نوشتیم، اما این سوال پیش می آید: با راه اندازی فرآیند CI ما در GitHub چه اتفاقی خواهد افتاد؟ پایگاه داده نخواهد داشت. در حال حاضر واقعاً فقط یک ساخت قرمز وجود خواهد داشت. برای انجام این کار، اکشن های GitHub داریم که در آن می توانیم راه اندازی بیلد خود را پیکربندی کنیم. قبل از اجرای آزمایش‌ها، باید یک پایگاه داده با تنظیمات لازم اضافه کنید. همانطور که مشخص است، نمونه های زیادی در اینترنت وجود ندارد، بنابراین به شما توصیه می کنم این را در جایی ذخیره کنید. بیایید فایل .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
اکنون یک بلوک جدید Set up MySQL وجود دارد . در آن ما MySQL را به فرآیند CI خود اضافه می کنیم و به طور همزمان متغیرهای مورد نیاز خود را تعریف می کنیم. اکنون هر آنچه را که می خواستیم اضافه کرده ایم. آخرین مرحله این است که تغییرات را فشار دهید و ببینید که بیلد عبور می کند و سبز می شود.

به روز رسانی مستندات

بیایید نسخه پروژه را از 0.3.0-SNAPSHOT به 0.4.0-SNAPSHOT در pom.xml به روز کنیم و همچنین به RELEASE_NOTES اضافه کنیم:
## 0.4.0-SNAPSHOT

*   JRTB-1: added repository layer.
بعد از همه اینها یک درخواست commit، push و pull ایجاد می کنیم. و مهمتر از همه، ساخت ما سبز است!"پروژه جاوا از A تا Z": افزودن همه چیز مربوط به پایگاه داده.  قسمت 2 - 3

لینک های مفید:

همه تغییرات را می توان در اینجا در درخواست کشش ایجاد شده مشاهده کرد . با تشکر از همه برای خواندن.

فهرستی از تمام مواد این مجموعه در ابتدای این مقاله است.

نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION