JavaRush /Blog Java /Random-VI /Chúng tôi thêm mọi thứ liên quan đến cơ sở dữ liệu. (Phần...
Roman Beekeeper
Mức độ

Chúng tôi thêm mọi thứ liên quan đến cơ sở dữ liệu. (Phần 2) - "Dự án Java từ A đến Z"

Xuất bản trong nhóm
Chào mọi người. Hãy để tôi nhắc bạn: trong phần đầu tiên chúng tôi đã thêm Đường bay. Tiếp tục đi.

Thêm cơ sở dữ liệu vào docker-compose.yml

Giai đoạn tiếp theo là thiết lập công việc với cơ sở dữ liệu trong docker-compose.yml chính. Hãy thêm cơ sở dữ liệu vào tệp 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'
Tôi cũng đã thêm dòng này vào ứng dụng của chúng tôi:
depends_on:
 - jrtb-db
Điều này có nghĩa là chúng ta đợi cơ sở dữ liệu khởi động trước khi khởi động ứng dụng. Tiếp theo, bạn có thể nhận thấy việc bổ sung thêm hai biến nữa mà chúng ta cần để làm việc với cơ sở dữ liệu:
${BOT_DB_USERNAME}
${BOT_DB_PASSWORD}
Chúng ta sẽ lấy chúng trong docker-compose theo cách tương tự như đối với telegram bot - thông qua các biến môi trường. Tôi đã làm điều này để chúng tôi chỉ có một nơi đặt giá trị của tên người dùng cơ sở dữ liệu và mật khẩu của nó. Chúng tôi chuyển chúng tới hình ảnh docker của ứng dụng và tới vùng chứa docker trong cơ sở dữ liệu của chúng tôi. Tiếp theo, chúng ta cần cập nhật Dockerfile để hướng dẫn SpringBoot chấp nhận các biến cho cơ sở dữ liệu.
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"]
Bây giờ chúng ta thêm các biến cơ sở dữ liệu vào Dockerfile:
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
Các giá trị biến sẽ khác nhau. Tuy nhiên, những cái chúng tôi sẽ chuyển vào Dockerfile yêu cầu các giá trị mặc định, vì vậy tôi đã nhập một số giá trị. Chúng tôi mở rộng dòng cuối cùng với hai phần tử, với sự trợ giúp của chúng tôi sẽ chuyển tên người dùng và mật khẩu DB để khởi chạy ứng dụng:
"-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}"
Dòng cuối cùng trong Dockerfile (bắt đầu bằng ENTRYPOINT) không được ngắt dòng. Nếu bạn thực hiện chuyển khoản, mã này sẽ không hoạt động. Bước cuối cùng là cập nhật tệp start.sh để chuyển các biến vào cơ sở dữ liệu.
#!/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
Chúng ta đã biết cách thêm các biến môi trường trước khi chạy docker-compose. Để thực hiện việc này, bạn chỉ cần thực thi import var_name=var_value.. Do đó, chúng tôi chỉ thêm hai dòng:
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'
Đây là nơi chúng tôi đặt tên người dùng và mật khẩu cơ sở dữ liệu. Tất nhiên, có thể chuyển các biến này khi chạy tập lệnh bash, như chúng tôi làm với tên và mã thông báo của bot. Nhưng đối với tôi, có vẻ như điều này là không cần thiết. Để thực sự truy cập cơ sở dữ liệu, bạn cần biết IP của máy chủ nơi cơ sở dữ liệu sẽ được triển khai và nằm trong danh sách các địa chỉ IP được phép cho yêu cầu. Với tôi thế này là đủ rồi. Nền tảng đã được đặt ra: bây giờ bạn có thể làm những việc mà nhà phát triển dễ hiểu hơn - viết mã. Trước đó, chúng tôi đang làm công việc mà các kỹ sư DevOps thường làm - thiết lập môi trường.

Thêm một lớp lưu trữ

Thông thường, một ứng dụng có ba lớp:
  1. Bộ điều khiển là điểm vào ứng dụng.
  2. Dịch vụ là nơi logic kinh doanh hoạt động. Chúng tôi đã có một phần điều này: SendMessageService là đại diện rõ ràng cho logic nghiệp vụ.
  3. Kho lưu trữ là nơi để làm việc với cơ sở dữ liệu. Trong trường hợp của chúng tôi, đây là bot điện tín.
Bây giờ chúng ta sẽ thêm lớp thứ ba - kho lưu trữ. Ở đây chúng ta sẽ sử dụng một dự án từ hệ sinh thái Spring - Spring Data. Bạn có thể đọc về nó trong bài viết này trên Habré . Chúng ta cần biết và hiểu một số điểm:
  1. Chúng tôi sẽ không phải làm việc với JDBC: chúng tôi sẽ làm việc trực tiếp với các mức độ trừu tượng cao hơn. Tức là lưu trữ các POJO tương ứng với các bảng trong cơ sở dữ liệu. Chúng ta sẽ gọi các lớp như vậy là thực thể , vì chúng được gọi chính thức trong Java Persistence API (đây là một bộ giao diện phổ biến để làm việc với cơ sở dữ liệu thông qua ORM, nghĩa là một sự trừu tượng hóa khi làm việc với JDBC). Chúng ta sẽ có một lớp thực thể mà chúng ta sẽ lưu trong cơ sở dữ liệu và chúng sẽ được ghi vào chính xác bảng mà chúng ta cần. Chúng ta sẽ nhận được các đối tượng tương tự khi tìm kiếm trong cơ sở dữ liệu.
  2. Spring Data đề xuất sử dụng bộ giao diện của họ: JpaRepository , CrudRepository , v.v... Có các giao diện khác: bạn có thể tìm thấy danh sách đầy đủ tại đây . Điều thú vị là bạn có thể sử dụng các phương pháp của họ mà không cần thực hiện chúng (!). Hơn nữa, có một mẫu nhất định mà bạn có thể viết các phương thức mới trong giao diện và chúng sẽ được triển khai tự động.
  3. Mùa xuân đơn giản hóa sự phát triển của chúng tôi nhiều nhất có thể. Để làm điều này, chúng ta cần tạo giao diện của riêng mình và kế thừa từ những giao diện được mô tả ở trên. Và để Spring biết có nhu cầu sử dụng giao diện này hãy thêm chú thích Repository.
  4. Nếu chúng ta cần viết một phương thức để làm việc với cơ sở dữ liệu không tồn tại, thì đây cũng không phải là vấn đề - chúng ta sẽ viết nó. Tôi sẽ chỉ cho bạn những gì và làm thế nào để làm ở đó.
Trong bài viết này, chúng tôi sẽ làm việc với việc thêm dọc theo toàn bộ đường dẫn của TelegramUser và hiển thị phần này làm ví dụ. Chúng tôi sẽ mở rộng phần còn lại cho các nhiệm vụ khác. Nghĩa là, khi chúng ta thực thi lệnh /start, chúng ta sẽ ghi active = true vào cơ sở dữ liệu của người dùng. Điều này có nghĩa là người dùng đang sử dụng bot. Nếu người dùng đã có trong cơ sở dữ liệu, chúng tôi sẽ cập nhật trường active = true. Khi thực thi lệnh /stop, chúng tôi sẽ không xóa người dùng mà chỉ cập nhật trường hoạt động thành sai, để nếu người dùng muốn sử dụng lại bot, họ có thể khởi động nó và tiếp tục từ nơi mình đã dừng lại. Và để khi kiểm tra chúng ta có thể thấy có điều gì đó đang xảy ra, chúng ta sẽ tạo lệnh /stat: nó sẽ hiển thị số lượng người dùng đang hoạt động. Chúng tôi tạo một gói kho lưu trữ bên cạnh các gói bot, lệnh, dịch vụ. Trong gói này chúng ta tạo một thực thể khác . Trong gói thực thể, chúng tôi tạo lớp 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;
}
Ở đây bạn có thể thấy rằng chúng tôi có tất cả các chú thích từ gói javax.persistence. Đây là những chú thích phổ biến được sử dụng cho tất cả việc triển khai ORM. Theo mặc định, Spring Data Jpa sử dụng Hibernate, mặc dù có thể sử dụng các cách triển khai khác. Dưới đây là danh sách các chú thích chúng tôi sử dụng:
  • Thực thể - cho biết đây là thực thể để làm việc với cơ sở dữ liệu;
  • Bảng - ở đây chúng tôi xác định tên của bảng;
  • Id - chú thích cho biết trường nào sẽ là Khóa chính trong bảng;
  • Cột - xác định tên của trường từ bảng.
Tiếp theo, chúng ta tạo giao diện để làm việc với cơ sở dữ liệu. Thông thường, tên của các giao diện như vậy được viết bằng mẫu - EntiryNameRepository. Chúng ta sẽ có một 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();
}
Tại đây, bạn có thể thấy cách tôi thêm phương thức findAllByActiveTrue() mà tôi không triển khai ở bất kỳ đâu. Nhưng điều đó sẽ không ngăn cản anh ta làm việc. Spring Data sẽ hiểu rằng nó cần lấy tất cả các bản ghi từ bảng tg_user có trường hoạt động = true . Chúng tôi thêm dịch vụ để làm việc với thực thể TelegramUser (chúng tôi sử dụng tính năng đảo ngược phụ thuộc từ SOLID trong bối cảnh dịch vụ của các thực thể khác không thể giao tiếp trực tiếp với kho lưu trữ của thực thể khác - chỉ thông qua dịch vụ của thực thể đó). Chúng tôi tạo một dịch vụ TelegramUserService trong gói, hiện tại sẽ có một số phương pháp: lưu người dùng, lấy người dùng theo ID của họ và hiển thị danh sách người dùng đang hoạt động. Đầu tiên chúng ta tạo giao diện 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);
}
Và trên thực tế, việc triển khai 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);
   }
}
Ở đây cần lưu ý rằng chúng tôi sử dụng tính năng nội xạ phụ thuộc (giới thiệu một thể hiện của lớp) của đối tượng TelegramuserRepository bằng cách sử dụng chú thích Autowired và trên hàm tạo. Bạn có thể làm điều này cho một biến, nhưng đây là cách tiếp cận mà nhóm Spring Framework khuyến nghị cho chúng ta.

Thêm số liệu thống kê cho bot

Tiếp theo bạn cần cập nhật lệnh /start và /stop. Khi lệnh /start được sử dụng, bạn cần lưu người dùng mới vào cơ sở dữ liệu và đặt thành active = true. Và khi có /stop thì cập nhật dữ liệu người dùng: set active = false. Hãy sửa lớp 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);
   }
}
Ở đây, chúng tôi cũng chuyển đối tượng TelegramuserService cho hàm tạo để lưu người dùng mới. Hơn nữa, bằng cách sử dụng những ưu điểm của Tùy chọn trong Java, logic sau sẽ hoạt động: nếu chúng ta có một người dùng trong cơ sở dữ liệu, chúng ta chỉ cần kích hoạt anh ta, nếu không, chúng ta sẽ tạo một người dùng đang hoạt động mới. Lệnh dừng:
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);
               });
   }
}
Chúng tôi chuyển TelegramServiceTest tới StopCommand theo cách tương tự. Logic bổ sung là thế này: nếu chúng tôi có một người dùng có ID trò chuyện như vậy, chúng tôi sẽ tắt nó, nghĩa là chúng tôi đặt active = false. Làm thế nào bạn có thể nhìn thấy điều này bằng chính mắt mình? Hãy tạo một lệnh /stat mới để hiển thị số liệu thống kê của bot. Ở giai đoạn này, đây sẽ là số liệu thống kê đơn giản có sẵn cho tất cả người dùng. Trong tương lai, chúng tôi sẽ giới hạn quyền truy cập và chỉ cấp quyền truy cập cho quản trị viên. Sẽ có một mục trong số liệu thống kê: số lượng người dùng bot đang hoạt động. Để thực hiện việc này, hãy thêm giá trị STAT("/stat") vào CommandName. Tiếp theo, tạo lớp 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));
   }
}
Mọi thứ ở đây đều đơn giản: chúng tôi nhận được danh sách tất cả người dùng đang hoạt động bằng phương thức RetrieveAllActiveUsers và nhận được kích thước của bộ sưu tập. Bây giờ chúng tôi cũng cần cập nhật các lớp tăng dần: CommandContainerJavarushTelegramBot để chúng học cách chuyển dịch vụ mới mà chúng tôi cần. Bộ lệnh:
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);
   }

}
Ở đây, chúng tôi đã thêm một lệnh mới vào bản đồ và chuyển nó qua hàm tạo TelegramUserService. Nhưng trong chính bot, chỉ có hàm tạo sẽ thay đổi:
@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService) {
   this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this), telegramUserService);
}
Bây giờ chúng tôi chuyển TelegramUserService làm đối số, thêm chú thích Autowired. Điều này có nghĩa là chúng ta sẽ nhận được nó từ Bối cảnh ứng dụng. Chúng tôi cũng sẽ cập nhật lớp HelpCommand để lệnh thống kê mới xuất hiện trong mô tả.

Kiểm tra bằng tay

Hãy khởi chạy cơ sở dữ liệu từ docker-compose-test.yml và phương thức chính trong lớp JavarushTelegramBotApplication. Tiếp theo chúng ta viết một tập lệnh:
  • /stat - chúng tôi hy vọng rằng nếu cơ sở dữ liệu trống thì sẽ không có người nào sử dụng bot này;
  • /start - khởi động bot;
  • /stat - bây giờ chúng tôi dự kiến ​​rằng bot sẽ được 1 người sử dụng;
  • /stop - dừng bot;
  • /stat - chúng tôi hy vọng rằng sẽ lại có 0 người sử dụng nó.
"Dự án Java từ A đến Z": Thêm mọi thứ liên quan đến cơ sở dữ liệu.  Phần 2 - 2Nếu kết quả của bạn giống nhau thì chúng tôi có thể nói rằng chức năng hoạt động bình thường và bot đang hoạt động bình thường. Nếu có sự cố xảy ra, điều đó không thành vấn đề: chúng tôi khởi động lại phương thức chính ở chế độ gỡ lỗi và xem rõ ràng toàn bộ đường dẫn để tìm ra lỗi là gì.

Chúng tôi viết và cập nhật các bài kiểm tra

Vì chúng tôi đã thay đổi hàm tạo nên chúng tôi cũng sẽ cần cập nhật các lớp kiểm tra. Trong lớp Tóm tắtCommandTest , chúng ta cần thêm một trường nữa - lớp telegramUserService , cần thiết cho ba lệnh:
protected TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
Tiếp theo, hãy cập nhật phương thức init() trong CommandContainer :
@BeforeEach
public void init() {
   SendBotMessageService sendBotMessageService = Mockito.mock(SendBotMessageService.class);
   TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
   commandContainer = new CommandContainer(sendBotMessageService, telegramUserService);
}
Trong StartCommand bạn cần cập nhật phương thức getCommand() :
@Override
Command getCommand() {
   return new StartCommand(sendBotMessageService, telegramUserService);
}
Cũng trong StopCommand:
@Override
Command getCommand() {
   return new StopCommand(sendBotMessageService, telegramUserService);
}
Tiếp theo, chúng ta hãy xem xét các bài kiểm tra mới. Hãy tạo một bài kiểm tra điển hình cho 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);
   }
}
Cái này đơn giản. Bây giờ hãy nói về cách chúng ta sẽ kiểm tra cách làm việc với cơ sở dữ liệu. Tất cả những gì chúng tôi làm trước đây chỉ là kiểm thử đơn vị. Kiểm tra tích hợp kiểm tra sự tích hợp giữa nhiều phần của ứng dụng. Ví dụ, các ứng dụng và cơ sở dữ liệu. Ở đây mọi thứ sẽ phức tạp hơn vì để thử nghiệm, chúng tôi cần một cơ sở dữ liệu đã triển khai. Do đó, khi chạy thử nghiệm cục bộ, chúng tôi phải có cơ sở dữ liệu chạy từ docker-compose-test.yml. Để chạy thử nghiệm này, bạn cần chạy toàn bộ ứng dụng SpringBoot. Lớp kiểm tra có chú thích SpringBootTest sẽ khởi động ứng dụng. Nhưng cách tiếp cận này sẽ không hiệu quả với chúng tôi, vì khi ứng dụng được khởi chạy, bot telegram cũng sẽ khởi chạy. Nhưng có một sự mâu thuẫn ở đây. Các thử nghiệm sẽ được chạy cục bộ trên máy của chúng tôi và công khai thông qua GitHub Actions. Để các bài kiểm tra vượt qua khi khởi chạy toàn bộ ứng dụng, chúng tôi phải chạy chúng với dữ liệu hợp lệ trên bot telegram: nghĩa là theo tên và mã thông báo của nó... Do đó, chúng tôi có hai lựa chọn:
  1. Vì vậy hãy công khai tên và token của bot và mong rằng mọi chuyện sẽ ổn, không ai lợi dụng và can thiệp vào chúng ta.
  2. Hãy nghĩ ra một cách khác.
Tôi đã chọn tùy chọn thứ hai. Kiểm tra SpringBoot có chú thích DataJpaTest , được tạo để khi kiểm tra cơ sở dữ liệu, chúng tôi chỉ sử dụng các lớp chúng tôi cần và để yên cho các lớp khác. Nhưng điều này phù hợp với chúng tôi, vì bot telegram sẽ không khởi chạy chút nào. Điều này có nghĩa là không cần phải chuyển tên và mã thông báo hợp lệ!))) Chúng tôi sẽ nhận được một bài kiểm tra trong đó chúng tôi sẽ kiểm tra xem các phương thức mà Spring Data triển khai cho chúng tôi có hoạt động như chúng tôi mong đợi hay không. Điều quan trọng cần lưu ý ở đây là chúng tôi sử dụng chú thích @ActiveProfiles("test") để chỉ định việc sử dụng hồ sơ thử nghiệm. Và đây chính xác là những gì chúng ta cần để có thể đếm các thuộc tính chính xác cho cơ sở dữ liệu của mình. Sẽ thật tốt nếu chuẩn bị sẵn cơ sở dữ liệu trước khi chạy thử nghiệm của chúng tôi. Có một cách tiếp cận như vậy cho vấn đề này: thêm chú thích Sql vào quá trình kiểm tra và chuyển cho nó một tập hợp các tên tập lệnh cần được chạy trước khi bắt đầu kiểm tra:
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
Đối với chúng tôi, chúng sẽ nằm dọc theo đường dẫn ./src/test/resources/ + đường dẫn được chỉ định trong chú thích. Đây là những gì họ trông giống như:
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);
Kết quả là thử nghiệm TelegramUserRepositoryIT của chúng tôi sẽ trông như thế này (như bạn có thể thấy, tên của thử nghiệm tích hợp sẽ khác - chúng tôi thêm CNTT chứ không phải Kiểm tra):
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());
   }
}
Chúng tôi đã viết các bài kiểm thử, nhưng câu hỏi đặt ra là: điều gì sẽ xảy ra khi chúng tôi khởi chạy quy trình CI trên GitHub? Nó sẽ không có cơ sở dữ liệu. Hiện tại thực sự sẽ chỉ có một bản dựng màu đỏ. Để làm điều này, chúng tôi có các hành động GitHub, trong đó chúng tôi có thể định cấu hình khởi chạy bản dựng của mình. Trước khi chạy thử nghiệm, bạn cần thêm khởi chạy cơ sở dữ liệu với các cài đặt cần thiết. Hóa ra, không có nhiều ví dụ trên Internet, vì vậy tôi khuyên bạn nên lưu nó ở đâu đó. Hãy cập nhật tệp .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
Bây giờ có khối Thiết lập MySQL mới . Trong đó, chúng tôi thêm MySQL vào quy trình CI của mình, đồng thời xác định các biến chúng tôi cần. Bây giờ chúng tôi đã thêm mọi thứ chúng tôi muốn. Giai đoạn cuối cùng là thúc đẩy các thay đổi và đảm bảo rằng bản dựng sẽ hoàn thành và có màu xanh.

Đang cập nhật tài liệu

Hãy cập nhật phiên bản dự án từ 0.3.0-SNAPSHOT lên 0.4.0-SNAPSHOT trong pom.xml và cũng thêm vào RELEASE_NOTES:
## 0.4.0-SNAPSHOT

*   JRTB-1: added repository layer.
Sau tất cả những điều này, chúng tôi tạo ra một yêu cầu cam kết, đẩy và kéo. Và quan trọng nhất, công trình của chúng tôi xanh!"Dự án Java từ A đến Z": Thêm mọi thứ liên quan đến cơ sở dữ liệu.  Phần 2 - 3

Liên kết hữu ích:

Tất cả các thay đổi có thể được nhìn thấy ở đây trong yêu cầu kéo đã tạo . Cảm ơn mọi người đã đọc.

Danh sách tất cả các tài liệu trong loạt bài này nằm ở đầu bài viết này.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION