JavaRush /จาวาบล็อก /Random-TH /เราเพิ่มทุกสิ่งที่เกี่ยวข้องกับฐานข้อมูล (ตอนที่ 2) - "โค...
Roman Beekeeper
ระดับ

เราเพิ่มทุกสิ่งที่เกี่ยวข้องกับฐานข้อมูล (ตอนที่ 2) - "โครงการ Java จาก A ถึง Z"

เผยแพร่ในกลุ่ม
สวัสดีทุกคน. ฉันขอเตือนคุณ: ในส่วนแรกเราได้เพิ่ม Flyway มาต่อกัน

การเพิ่มฐานข้อมูลลงใน docker-compose.yml

ขั้นตอนต่อไปคือการตั้งค่างานกับฐานข้อมูลใน main docker-compose.yml มาเพิ่มฐานข้อมูลลงในไฟล์นักเทียบท่า:
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 ในลักษณะเดียวกับ telegram bot - ผ่านตัวแปรสภาพแวดล้อม ฉันทำสิ่งนี้เพื่อให้เรามีเพียงที่เดียวที่เราตั้งค่าชื่อผู้ใช้ฐานข้อมูลและรหัสผ่าน เราส่งต่อไปยังอิมเมจนักเทียบท่าของแอปพลิเคชันของเราและไปยังคอนเทนเนอร์นักเทียบท่าของฐานข้อมูลของเรา ต่อไปเราต้องอัปเดต 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'
นี่คือที่ที่เราตั้งชื่อผู้ใช้และรหัสผ่านฐานข้อมูล แน่นอนว่า มันเป็นไปได้ที่จะส่งผ่านตัวแปรเหล่านี้เมื่อรันสคริปต์ทุบตี เช่นเดียวกับที่เราทำกับชื่อและโทเค็นของบอท แต่สำหรับฉันดูเหมือนว่านี่ไม่จำเป็น ในการเข้าถึงฐานข้อมูลจริง คุณจำเป็นต้องทราบ IP ของเซิร์ฟเวอร์ที่จะใช้งานฐานข้อมูล และอยู่ในรายการที่อยู่ IP ที่อนุญาตสำหรับคำขอ สำหรับฉันเท่านี้ก็เพียงพอแล้ว วางรากฐานแล้ว: ตอนนี้คุณสามารถทำสิ่งที่นักพัฒนาเข้าใจได้มากขึ้น - เขียนโค้ด ก่อนหน้านั้น เรากำลังทำสิ่งที่วิศวกร DevOps ทำ นั่นคือการตั้งค่าสภาพแวดล้อม

การเพิ่มชั้นพื้นที่เก็บข้อมูล

โดยทั่วไปแอปพลิเคชันจะมีสามชั้น:
  1. ตัวควบคุมเป็นจุดเริ่มต้นเข้าสู่แอปพลิเคชัน
  2. บริการคือจุดที่ตรรกะทางธุรกิจทำงาน เรามีสิ่งนี้อยู่แล้วบางส่วน: SendMessageService เป็นตัวแทนที่ชัดเจนของตรรกะทางธุรกิจ
  3. ที่เก็บเป็นสถานที่ทำงานกับฐานข้อมูล ในกรณีของเรา นี่คือบอทโทรเลข
ตอนนี้เราจะเพิ่มเลเยอร์ที่สาม - ที่เก็บ ที่นี่เราจะใช้โปรเจ็กต์จากระบบนิเวศของ Spring - Spring Data คุณสามารถอ่านเกี่ยวกับสิ่งนี้ได้ในบทความเกี่ยวกับHabréนี้ เราจำเป็นต้องรู้และเข้าใจหลายประเด็น:
  1. เราจะไม่ต้องทำงานกับ JDBC: เราจะทำงานโดยตรงกับสิ่งที่เป็นนามธรรมที่สูงกว่า นั่นคือจัดเก็บ POJO ที่สอดคล้องกับตารางในฐานข้อมูล เราจะเรียกคลาส ดังกล่าวว่าเอนทิตี เนื่องจากถูกเรียกอย่างเป็นทางการในJava Persistence API (นี่คือชุดอินเทอร์เฟซทั่วไปสำหรับการทำงานกับฐานข้อมูลผ่าน ORM นั่นคือสิ่งที่เป็นนามธรรมมากกว่าการทำงานกับ JDBC) เราจะมีคลาสเอนทิตีที่เราจะบันทึกไว้ในฐานข้อมูล และจะถูกเขียนลงในตารางที่เราต้องการ เราจะได้รับวัตถุเดียวกันเมื่อทำการค้นหาในฐานข้อมูล
  2. Spring Data เสนอให้ใช้ชุดอินเทอร์เฟซ: JpaRepository , CrudRepositoryฯลฯ ... มีอินเทอร์เฟซอื่น ๆ : สามารถดูรายการทั้งหมดได้ที่นี่ สิ่งที่สวยงามคือคุณสามารถใช้วิธีการของพวกเขาได้โดยไม่ต้องนำไปปฏิบัติ(!) นอกจากนี้ยังมีเทมเพลตบางตัวที่คุณสามารถเขียนวิธีการใหม่ในอินเทอร์เฟซได้ และจะมีการใช้งานโดยอัตโนมัติ
  3. Spring ช่วยให้การพัฒนาของเราง่ายขึ้นมากที่สุด ในการทำเช่นนี้ เราจำเป็นต้องสร้างอินเทอร์เฟซของเราเองและสืบทอดจากที่อธิบายไว้ข้างต้น และเพื่อให้ Spring รู้ว่าจำเป็นต้องใช้อินเทอร์เฟซนี้ ให้เพิ่มคำอธิบายประกอบ Repository
  4. หากเราจำเป็นต้องเขียนวิธีการทำงานกับฐานข้อมูลที่ไม่มีอยู่นี่ก็ไม่ใช่ปัญหาเช่นกัน - เราจะเขียนมันขึ้นมา ฉันจะแสดงให้คุณเห็นว่าต้องทำอะไรที่นั่น
ในบทความนี้ เราจะดำเนินการเพิ่มตามเส้นทางทั้งหมดของ TelegramUser และแสดงส่วนนี้เป็นตัวอย่าง เราจะขยายส่วนที่เหลือในงานอื่น ๆ นั่นคือเมื่อเรารันคำสั่ง /start เราจะเขียน active = true ลงในฐานข้อมูลของผู้ใช้ของเรา นี่จะหมายความว่าผู้ใช้กำลังใช้บอท หากผู้ใช้อยู่ในฐานข้อมูลแล้ว เราจะอัปเดตฟิลด์ active = true เมื่อดำเนินการคำสั่ง /stop เราจะไม่ลบผู้ใช้ แต่จะอัปเดตฟิลด์ที่ใช้งานอยู่เป็นเท็จเท่านั้น เพื่อที่ว่าหากผู้ใช้ต้องการใช้บอทอีกครั้ง เขาสามารถเริ่มต้นและดำเนินการต่อจากจุดที่เขาค้างไว้ได้ และเมื่อทำการทดสอบ เราจะเห็นว่ามีบางอย่างเกิดขึ้น เราจะสร้างคำสั่ง /stat ซึ่งจะแสดงจำนวนผู้ใช้ที่ใช้งานอยู่ เราสร้าง แพ็คเกจ พื้นที่เก็บข้อมูลถัดจากบอท คำสั่ง แพ็คเกจบริการ ในแพ็คเกจนี้เราสร้างเอนทิตี อีกอัน หนึ่ง ในแพ็คเกจเอนทิตีเราสร้างคลาส 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 แม้ว่าการใช้งานอื่นๆ ก็สามารถใช้ได้ก็ตาม นี่คือรายการคำอธิบายประกอบที่เราใช้:
  • เอนทิตี - ระบุว่านี่คือเอนทิตีสำหรับการทำงานกับฐานข้อมูล
  • ตาราง - ที่นี่เรากำหนดชื่อของตาราง
  • Id - คำอธิบายประกอบบอกว่าฟิลด์ใดจะเป็นคีย์หลักในตาราง
  • คอลัมน์ - กำหนดชื่อของฟิลด์จากตาราง
ต่อไปเราจะสร้างอินเทอร์เฟซสำหรับการทำงานกับฐานข้อมูล โดยทั่วไปแล้ว ชื่อของอินเทอร์เฟซดังกล่าวจะถูกเขียนโดยใช้เทมเพลต - 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 ซึ่งมีactive field = true เราเพิ่มบริการสำหรับการทำงานกับเอนทิตี TelegramUser (เราใช้การผกผันการพึ่งพาจาก SOLID ในบริบทที่บริการของเอนทิตีอื่นไม่สามารถสื่อสารโดยตรงกับที่เก็บของเอนทิตีอื่น - ผ่านบริการของเอนทิตีนั้นเท่านั้น) เราสร้างบริการ TelegramUserService ในแพ็คเกจซึ่งตอนนี้จะมีหลายวิธี: บันทึกผู้ใช้ รับผู้ใช้ด้วย ID ของเขา และแสดงรายการผู้ใช้ที่ใช้งานอยู่ ขั้นแรกเราสร้างอินเทอร์เฟซ 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 คุณจะต้องบันทึกผู้ใช้ใหม่ในฐานข้อมูลและตั้งค่าเป็น active = 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 ไปยัง Constructor ซึ่งเราจะบันทึกผู้ใช้ใหม่ นอกจากนี้ การใช้ความยินดีของ Optional ใน Java ตรรกะต่อไปนี้ใช้งานได้: หากเรามีผู้ใช้ในฐานข้อมูล เราก็ทำให้เขาแอคทีฟ หากไม่มี เราก็สร้างแอคทีฟใหม่ขึ้นมา คำสั่งหยุด:
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 แชท เราจะปิดใช้งานมัน นั่นคือ เราตั้งค่า active = 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));
   }
}
ทุกอย่างเป็นเรื่องง่ายที่นี่: เราได้รับรายชื่อผู้ใช้ที่ใช้งานอยู่ทั้งหมดโดยใช้ วิธี ดึงข้อมูล AllActiveUsersและรับขนาดของคอลเลกชัน ตอนนี้เราจำเป็นต้องอัปเดตคลาสจากน้อยไปหามาก: CommandContainerและJavarushTelegramBotเพื่อให้คลาสเรียนรู้ที่จะส่งบริการใหม่ที่เราต้องการ คอนเทนเนอร์คำสั่ง:
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 แต่ในตัวบอทเอง มีเพียง Constructor เท่านั้นที่จะเปลี่ยนแปลง:
@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService) {
   this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this), telegramUserService);
}
ตอนนี้เราส่ง TelegramUserService เป็นอาร์กิวเมนต์ โดยเพิ่มคำอธิบายประกอบแบบ Autowired ซึ่งหมายความว่าเราจะได้รับจากบริบทของแอปพลิเคชัน นอกจากนี้เรายังจะอัปเดต คลาส HelpCommandเพื่อให้คำสั่งสถิติใหม่ปรากฏในคำอธิบาย

การทดสอบด้วยตนเอง

มาเปิดตัวฐานข้อมูลจาก docker-compose-test.yml และวิธีการหลักในคลาส JavarushTelegramBotApplication ต่อไปเราจะเขียนชุดคำสั่ง:
  • /stat - เราคาดว่าหากฐานข้อมูลว่างเปล่า จะไม่มีผู้ใช้บอทนี้
  • /start - เริ่มบอท;
  • /stat - ตอนนี้เราคาดว่าบอทจะมีคนใช้ 1 คน
  • /stop - หยุดบอท;
  • /stat - เราคาดว่าจะมีคนใช้มัน 0 คนอีกครั้ง
"โครงการ Java จาก 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 ของเรา (ดังที่คุณเห็น ชื่อของการทดสอบการรวมระบบจะแตกต่างออกไป - เราเพิ่มไอที ไม่ใช่การทดสอบ):
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 block ใหม่ ในนั้น เราได้เพิ่ม 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 และที่สำคัญที่สุด โครงสร้างของเราเป็นสีเขียว!"โครงการ Java จาก A ถึง Z": การเพิ่มทุกสิ่งที่เกี่ยวข้องกับฐานข้อมูล  ส่วนที่ 2 - 3

ลิงค์ที่เป็นประโยชน์:

การ เปลี่ยนแปลงทั้งหมดสามารถดูได้ที่นี่ในคำขอดึง ที่สร้างขึ้น ขอบคุณทุกคนที่อ่าน

รายการเนื้อหาทั้งหมดในซีรีส์นี้อยู่ที่ตอนต้นของบทความนี้

ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION