JavaRush /Java Blog /Random EN /Add everything related to the database. (Part 2) - "Java ...

Add everything related to the database. (Part 2) - "Java project from A to Z"

Published in the Random EN group
Hi all. Let me remind you: in the first part we added Flyway. Let's continue.

Adding a database to docker-compose.yml

The next step is to set up work with the database in the main docker-compose.yml. Let's add the database to the docker-compose file:
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'
I added this line to our application:
depends_on:
 - jrtb-db
This means that before starting the application, we wait for the database to start. Next, you can notice the addition of two more variables that we need to work with the database:
${BOT_DB_USERNAME}
${BOT_DB_PASSWORD}
We will get them in docker-compose in the same way as for the telegram bot - through environment variables. I did this so that we have only one place where we set the values ​​of the database username and password. We pass them to our application's docker image and to our database's docker container. Next, we need to update the Dockerfile to teach our SpringBoot to accept variables for the database.
FROM adoptopenjdk/openjdk11:ubi
ARG JAR_FILE=target/*.jar
ENV BOT_NAME=test.codegym_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"]
Now add the database variables to the Dockerfile:
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
Variable values ​​will be different. The ones we'll pass into the Dockerfile, however, require default values, which is why I've included some. We expand the last line with two elements, with the help of which we will pass the DB username and password to the application launch:
"-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}"
The last line in the Dockerfile (which starts with ENTRYPOINT) should be without wrapping elements. If you make a transfer, this code will not work. The last step is to update the start.sh file to pass in the variables for the database.
#!/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
We already know how to add environment variables before running docker-compose. To do this, you just need to execute export var_name=var_value.. Therefore, we add only two lines:
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'
This is where we set the database username and password. Of course, it would be possible to pass these variables when running the bash script, as we do for the name and token of the bot. But I think it's overkill. To actually access the database, you need to know the IP of the server where the database will be deployed and be on the list of allowed IP addresses for the request. As for me, this is already enough. The foundation has been laid: now you can do things that are more understandable for the developer - write code. Before that, we were doing what DevOps engineers do - setting up the environment.

Adding a Repository Layer

Typically, an application has three layers:
  1. Controllers are the entry points to the application.
  2. Services are the place where business logic works. We already partly have this: SendMessageService is an explicit representative of the business logic.
  3. Repositories are a place to work with a database. In our case, this is a telegram bot.
Now we will add the third layer - repositories. Here we will use a project from the Spring ecosystem - Spring Data. You can read about what it is in this article on Habré . We need to know and understand a few things:
  1. We will not work with JDBC: we will work immediately with higher abstractions. That is, store POJOs that correspond to tables in the database. We will call such classes entity , as they are officially called in the Java Persistence API (this is a common set of interfaces for working with a database through an ORM, that is, an abstraction for working with JDBC). We will have an entity class that we will save in the database, and they will be written to exactly the table that we need. We will receive the same objects when searching the database.
  2. Spring Data suggests using their set of interfaces: JpaRepository , CrudRepository , etc... There are other interfaces, the full list can be found here . The beauty is that you can use their methods without implementing them(!). Moreover, there is a certain pattern, using which you can write new methods in the interface, and they will be implemented automatically.
  3. Spring makes our development as easy as possible. To do this, we need to create our own interface and inherit from the above. And so that Spring knows that it needs to use this interface, add the Repository annotation.
  4. If we need to write a method for working with the database, which does not exist, then this is also not a problem - we will write it. I'll show you what to do and how.
As part of this article, we will work with adding TelegramUser along the entire path and show this part using his example. The rest we will expand on other tasks. That is, when executing the /start command, we will write active = true to the database of our user. This will mean that the user is using the bot. If the user is already in the database, we will update the active = true field. When executing the /stop command, we will not delete the user, but only update the active field to false so that if the user wants to use the bot again, he can start it and continue from where he left off. And so that when testing it is visible that something is happening, we will create a /stat command: it will display the number of active users. Create a repositorypackage next to bot, command, service packages. In this package, we create another one - entity . In the entity package, create the TelegramUser class:
package com.github.codegymcommunity.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;
}
Here you can see that we have all the annotations from the javax.persistence package. These are general annotations that are used across all ORM implementations. By default, Spring Data Jpa uses Hibernate, although other implementations can be used. Here is a list of annotations we use:
  • Entity - indicates that this is an entity for working with the database;
  • Table - here we define the name of the table;
  • Id - the annotation says which field will be the Primary Key in the table;
  • Column - define the name of the field from the table.
Next, we create an interface for working with the database. Usually, the names of such interfaces are written according to the pattern - EntiryNameRepository. We will have a TelegramuserRepository:
package com.github.codegymcommunity.jrtb.repository;

import com.github.codegymcommunity.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();
}
Here you can see how I added the findAllByActiveTrue() method , which I don't implement anywhere. But that won't stop him from working. Spring Data will understand that it needs to get all the records from the tg_user table that have the field active = true . We add a service for working with the TelegramUser entity (we use dependency inversion from SOLID in the context of the fact that services of other entities cannot directly communicate with the repository of another entity - only through the service of that entity). We create TelegramUserService in the service package, which for now will have several methods: save the user, get the user by his ID and display the list of active users. First, we create the TelegramUserService interface:
package com.github.codegymcommunity.jrtb.service;

import com.github.codegymcommunity.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);
}
And, in fact, the implementation of TelegramUserServiceImpl:
package com.github.codegymcommunity.jrtb.service;

import com.github.codegymcommunity.jrtb.repository.TelegramUserRepository;
import com.github.codegymcommunity.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);
   }
}
It should be noted here that we are using dependency injection (introducing an instance of a class) of the TelegramuserRepository object using the Autowired annotation , moreover, on the constructor. You can do this for a variable as well, but this is the approach that the Spring Framework team recommends to us.

Adding statistics for the bot

Next, you need to update the /start and /stop commands. When using the /start command, you need to save a new user in the database and set active = true for him. And when there is /stop, update the user data: set active = false. Let's fix the StartCommand class :
package com.github.codegymcommunity.jrtb.command;

import com.github.codegymcommunity.jrtb.repository.entity.TelegramUser;
import com.github.codegymcommunity.jrtb.service.SendBotMessageService;
import com.github.codegymcommunity.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);
   }
}
Here we also pass the TelegramuserService object to the constructor, with the help of which we will save the new user. Further, using the charms of Optional in Java, the following logic works: if we have a user in the database, we just make it active, if not, we create a new active one. StopCommand:
package com.github.codegymcommunity.jrtb.command;

import com.github.codegymcommunity.jrtb.repository.entity.TelegramUser;
import com.github.codegymcommunity.jrtb.service.SendBotMessageService;
import com.github.codegymcommunity.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);
               });
   }
}
In StopCommand, we pass TelegramServiceTest in the same way. Additional logic is as follows: if we have a user with such a chat ID, we deactivate it, that is, we set active = false. How can you see it with your own eyes? Let's make a new /stat command that will display the bot's statistics. At this stage, it will be simple statistics available to all users. In the future, we will limit it and make access only for administrators. There will be one entry in the statistics: the number of active users of the bot. To do this, add the value STAT("/stat") to the CommandName. Next, we create the StatCommand class:
package com.github.codegymcommunity.jrtb.command;

import com.github.codegymcommunity.jrtb.service.SendBotMessageService;
import com.github.codegymcommunity.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));
   }
}
Everything is simple here: we get a list of all active users using the retrieveAllActiveUsers method and get the size of the collection. Also, now we need to update the classes in ascending order: CommandContainer and JavarushTelegramBot so that they learn how to pass the new service we need. command container:
package com.github.codegymcommunity.jrtb.command;

import com.github.codegymcommunity.jrtb.service.SendBotMessageService;
import com.github.codegymcommunity.jrtb.service.TelegramUserService;
import com.google.common.collect.ImmutableMap;

import static com.github.codegymcommunity.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);
   }

}
Here we have added a new command to the map and passed it through the TelegramUserService constructor. But in the bot itself, only the constructor will change:
@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService) {
   this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this), telegramUserService);
}
Now we are passing as an argument TelegramUserService, adding the Autowired annotation. This means that we will get it from the Application Context. We will also update the HelpCommand class so that a new statistics command appears in the description.

Manual testing

Let's start the database from docker-compose-test.yml and the main method in the JavarushTelegramBotApplication class. Next, we write a set of commands:
  • /stat - we expect that with an empty database, the people using this bot will be zero;
  • / start - start the bot;
  • /stat - now we expect that the bot will be used by 1 person;
  • /stop - stop the bot;
  • /stat - we expect that again there will be 0 people to use.
"Java project from A to Z": Add everything related to the database.  Part 2 - 2If your result is the same, we can say that the functionality has worked correctly and the bot is working. If something goes wrong, it doesn't matter: we restart the main method in debug mode and go through the entire path clearly to find what the error was.

Writing and updating tests

Since we have changed the constructors, we will need to update the test classes as well. In the AbstractCommandTest class, we need to add one more field - the hacked TelegramUserService class , which is needed for three commands:
protected TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
Next, in the CommandContainer, update the init() method :
@BeforeEach
public void init() {
   SendBotMessageService sendBotMessageService = Mockito.mock(SendBotMessageService.class);
   TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
   commandContainer = new CommandContainer(sendBotMessageService, telegramUserService);
}
In StartCommand, you need to update the getCommand() method:
@Override
Command getCommand() {
   return new StartCommand(sendBotMessageService, telegramUserService);
}
Also in StopCommand:
@Override
Command getCommand() {
   return new StopCommand(sendBotMessageService, telegramUserService);
}
Let's move on to new tests. Create a sample test for StatCommand :
package com.github.codegymcommunity.jrtb.command;

import static com.github.codegymcommunity.jrtb.command.CommandName.STAT;
import static com.github.codegymcommunity.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);
   }
}
This is from simple. Now let's talk about how we will test the work with the database. Everything we've done so far has been unit tests. An integration test tests integration between multiple parts of an application. For example, applications and databases. Here everything will be more complicated, because for testing we need a deployed database. Therefore, when we run our tests locally, we must have the database running from docker-compose-test.yml. To run this test, you need to run the entire SpringBoot application. For the test class there is an annotation SpringBootTest, which will launch the application. But this approach will not work for us, because when the application is launched, the telegram bot will also be launched. But there is a contradiction here. Tests will be run both locally on our machine and publicly via GitHub Actions. In order for the tests to pass with the launch of the entire application, we must run them with valid data for the telegram bot: that is, by its name and token ... Therefore, we have two options:
  1. So make the name and token of the bot public and hope that everything will be fine, no one will use it and interfere with us.
  2. Think of another way.
I chose the second option. In SpringBoot testing, there is an annotation DataJpaTest , which was created so that when testing the database, we use only the classes we need and do not touch others. But it suits us, because the telegram bot will not start at all. This means that you do not need to pass a valid name and token to it!))) Let's get a test in which we check that the methods that Spring Data implements for us work as we expect. It is important to note here that we are using the @ActiveProfiles("test") annotationset the use of the profile test. And this is exactly what we need so that we consider the correct properties for our database. It would be nice to have the database prepared before running our tests. To do this, there is this approach: add a Sql annotation to the test and pass it a collection of script names that need to be run before starting the test:
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
With us, they will lie along the path ./src/test/resources/ + the path specified in the annotation. Here's what they look like:
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);
This is how our TelegramUserRepositoryIT test will look like as a result (as you can see, the name for integration testing will be different - we add not Test, but IT):
package com.github.codegymcommunity.jrtb.repository;

import com.github.codegymcommunity.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());
   }
}
The tests have been written, but the question arises: what will happen to the launch of our CI process on GitHub? It doesn't have a database. At the moment, it will really be just a red build. To do this, we have GitHub actions, in which we can configure the launch of our build. Before running the tests, you need to add the launch of the database with the necessary settings. As it turned out, there are not so many examples on the Internet, so I advise you to keep this somewhere for yourself. Update the .github/workflows/maven.yml file:
# 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
Now there is a new Set up MySQL block . In it, we add MySQL to our CI process, along the way defining the variables we need. Now we have added everything we wanted. The last step is to push the changes and see if the build passes and is green.

Updating the documentation

Update the project version from 0.3.0-SNAPSHOT to 0.4.0-SNAPSHOT in pom.xml and add to RELEASE_NOTES also:
## 0.4.0-SNAPSHOT

*   JRTB-1: added repository layer.
After all this, we create a commit, push and pull request. And most importantly, our build is green!"Java project from A to Z": Add everything related to the database.  Part 2 - 3

Useful links:

All changes can be seen here in the generated pull request . Thank you all for reading.

List of all materials in the series at the beginning of this article.

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