Git проекта: https://gitverse.ru/cherepakhin/companies
Задание:
- Создать новый пустой Spring (5) Java-проект Web-приложения с поддержкой Maven, Java 11.
Проект должен поддерживать подключение к PostgreSQL (12 и выше) и механизм миграций на базе Liquibase.
Необходимые настройки проекта (в том числе и параметры подключения к БД) должны храниться в профиле.
При запуске проекта должны автоматически формироваться необходимые структуры данных в БД, а так же
должны сразу же автоматически добавляться сами объекты тестовых данных (не менее 50 организаций, которые описаны в п.3). Должно поддерживаться открытие и запуск проекта в IntelliJ Idea. - В собранном web-приложении должна быть реализована REST-архитектура, предоставляющая доступ без авторизации к следующим web-методам:
- Метод получения списка объектов данных (описаны в п.3) в формате JSON с возможностью получения как всего списка данных, так и списка данных, отфильтрованного по переданной в этот метод поисковой строке.
- Метод получения одного объекта данных (описан в п.3) в формате JSON по его уникальному идентификатору.
- Описание одного объекта данных для использования при реализации п.1, 2:
Объект данных представляет собой сведения по одной организации, в которые входят:
3.1 Полное наименование организации
3.2 Краткое наименования организации
3.3 ИНН
3.4 ОГРН
3.5 Почтовый адрес
3.6 Юридический адрес
3.5 Сведения о генеральном директоре, включая Фамилию, Имя, Отчество, дату рождения
3.6 Сведения о филиалах (может быть несколько), включая наименование, почтовый адрес,
сведения о руководителе (Фамилия, Имя отчество, дата рождения) - Все классы, методы и свойства должны иметь комментарии в формате Javadoc.
- Необходимо создать файл с описанием:
5.1 Необходимых для запуска web-приложения настроек в профиле
5.2 REST API web-приложения - Показать работу с БД разными способами (sql native запросы, qbe-запросы)
- flyway database migration
- Интеграционно протестировать
- Кэши для REST
- Docker
- log
- Профили для dev и prod
- Использован Thymeleaf
Установка версии java
в Linux:
export JAVA_HOME=/usr/lib/jvm/java-1.11.0-openjdk-amd64
в Windows:
(выполнять из cmd.exe)
C:>echo %JAVA_HOME% C:\po\jdk-21 Z:\prog\java\companies> export JAVA_HOME=c:\po\open-jdk11 ?????
Проверка:
$ ./mvnw -version Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f) Maven home: /home/vasi/.m2/wrapper/dists/apache-maven-3.6.3-bin/1iopthnavndlasol9gbrbg6bf2/apache-maven-3.6.3 Java version: 11.0.19, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64 Default locale: ru_RU, platform encoding: UTF-8 OS name: "linux", version: "5.4.0-150-generic", arch: "amd64", family: "unix"
Использование базы данных
Используется программа psql из дистрибутива PostgreSQL.
Создание базы:
psql # create database companies;
Для подключения к БД используются user/password из переменных операционной системы PG_USER, PG_PASSWORD. По умолчанию значения обеих переменных 'postgres'.
Подключение через psql:
psql --dbname=companies
Подключение через psql с бД на хосте 192.168.1.20:
psql -U vasi -d companies -h 192.168.1.20
Backup:
pg_dump -U vasi -W companies > companies.backup
Restore:
psql -U vasi -d companies < companies.backup
После восстановления предоставить права:
GRANT ALL PRIVILEGES ON DATABASE "companies" to vasi; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO vasi;
Для версионирования БД используется flyway. Автообновление БД отключено. В application.yaml установлен флаг:
flyway:
enabled: false
Миграции в src/resources/migration. При изменении структуры вручную выполнить:
./mvnw flyway:migrate -Dflyway.user=postgres -Dflyway.password=postgres -Dflyway.url=jdbc:postgresql://127.0.0.1:5432/companies
Тестирование
Прогон всех тестов (включая интеграционные):
./mvnw test
Tests run: 15, Failures: 0, Errors: 0, Skipped: 0
Прогон только интеграционных тестов:
./mvnw test -Dtest=*_IntegrationTest
Tests run: 9, Failures: 0, Errors: 0, Skipped: 0
Прогон только unit тестов:
$ ./mvnw test -Dtest=\!*_IntegrationTest
Tests run: 6, Failures: 0, Errors: 0, Skipped: 0
Из Windows:
./mvnw test -Dtest=!*_IntegrationTest .\mvnw.cmd test -Dtest=!*_IntegrationTest
Прогон конкретного тестового класса:
./mvnw test -Dtest=CompanyServiceImplTest -DfailIfNoTests=false
NOTE: No tests were executed! -DfailIfNoTests=false to ignore this error
Прогон конкретного метода конкретного тестового класса:
./mvnw test -Dtest="CompanyServiceImplTest#getAll" ./mvnw test -Dtest="EmployeeServiceImpl_IntegrationTest#findByLastNameLikeOrderByN" ./mvnw test -Dtest=CompanyServiceImplTest#getAll
Разные варианты прогона тестов:
./mvnw test -Dtest=*ImplTest -DfailIfNoTests=false ./mvnw test -Dtest=*IntegrationTest -DfailIfNoTests=false
Прогон теста в Windows:
// из cmd.exe companies>echo %JAVA_HOME% C:\po\jdk-21 // из PowerShell companies>.\mvnw.cmd test -Dtest=*_IntegrationTest companies>.\mvnw.cmd test -Dtest=!*_IntegrationTest companies>.\mvnw.cmd test
Сборка без тестов:
./mvnw package -DskipTests
Покрытие тестами
Использован jacoco. Генерация отчета:
$./mvnw test $./mvnw jacoco:report
Отчет будет в папке target/site/jacoco/index.html

Пример отчета по конкретному классу:

Красным или желтым выделены непротестированные участки кода.
Запуск проекта
$./mvnw clean spring-boot:run
Для удобства есть скрипт run.sh, который запускает проект через Maven wrapper с нужным профилем:
./run.sh # профиль dev (по умолчанию) ./run.sh prod # профиль prod ./run.sh --help # справка
Скрипт выполняет ./mvnw spring-boot:run -Dspring-boot.run.profiles=<profile>. Приложение будет доступно на http://localhost:8080/api.
Интерфейс
Основной экран:

Стили общие для всех страниц: src/main/resources/static/css/app.css.
Web-интерфейс выполнен в стиле Gmail: шапка с логотипом, поисковой строкой и кнопкой Compose, список с карточками, аватарами и действиями, карточка просмотра и формы редактирования.
Поиск по спискам выполняется через параметр q: http://localhost:8080/api/companies?q=foo.
Логирование
Сделана настройка вывода протоколв работы через logback в папку logs. Настройки в файле resources/logback-spring.xml
Конфигурации (spring profiles)
Конфигурация для разработки расположена в application-dev.yml, для production версии в application-prod.yml. С какой конфигурацией запускать указано в application.yml
spring:
profiles:
active: dev
Профиль можно указать при запуске:
$java -jar target/companies-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod
Validate DTO
http POST :8080/api/employee/validate < src/test/test_jsons/empl_err1.json
HTTP/1.1 400
Connection: close
Content-Type: application/json
Date: Mon, 23 Oct 2023 07:24:22 GMT
Transfer-Encoding: chunked
{
"errors": [
"field: firstname, error: Firstname empty"
],
"message": "Errors in input dto: EmployeeDto{n=null, firstname='', lastname='LASTNAME_31', fathername='FATHERNAME_31', birthday='2023/01/01'}",
"status": "BAD_REQUEST"
}
Ручная проверка REST
Проверка ответа при ошибке:
$ http :8080/api/company/1000 HTTP/1.1 502 Connection: keep-alive Content-Length: 25 Content-Type: text/plain;charset=UTF-8 Company not found id=1000
app.log:
ERROR [http-nio-8080-exec-1] ru.perm.v.companies.rest.CompanyRest: Company not found id=1000
ResponseEntity
Показаны разные способы возврата ошибки.
1) Простой:
CompanyRest.deleteById(...)
@DeleteMapping("/{id}")
public ResponseEntity<Long> deleteById(@PathVariable Long id) {
try {
companyService.deleteById(id);
return ResponseEntity.ok(id);
} catch (Exception e) {
log.error(e.getMessage());
return new ResponseEntity(e.getMessage(), HttpStatus.BAD_GATEWAY);
}
}
2) И через ApiError:
EmployeeRest.update():
if (errors.size() > 0) {
ApiError apiError =
new ApiError(HttpStatus.BAD_REQUEST, String.format("Errors in input dto: %s", dto), errors);
return new ResponseEntity(
apiError, new HttpHeaders(), apiError.getStatus());
}
CI/CD
Скрипт Jenkinsfile для сборки для Jenkins. Вставлены некоторые команды для отладки и понятия работы Jenkinsfile:
pipeline {
agent any
options {internal
durabilityHint 'MAX_SURVIVABILITY'
}
stages {
stage('git clone') {
steps {
sh 'pwd'
sh 'rm -rf compaines'
sh 'git clone https://github.com/cherepakhin/companies.git'
sh 'ls'
}
}
stage('unit tests') {
steps {
sh 'ls'
sh 'cd companies;chmod +x mvnw;ls -al;pwd'
sh 'pwd;ls -al;cd companies;./mvnw test -Dtest=!*_IntegrationTest'
sh 'ls'
}
}
stage('deploy to nexus') {
steps {
sh 'ls'
sh 'cd companies;ls;./mvnw -Dmaven.test.skip=true deploy'
sh 'ls'
}
}
}
}
Примечание: каждый sh в stage начинает работу от рабочего каталога проекта Jenkins (см. stage('unit tests')):
sh 'ls' sh 'cd companies;chmod +x mvnw;ls -al;pwd' sh 'pwd;ls -al;cd companies;./mvnw test -Dtest=!*_IntegrationTest' sh 'ls'

Jenkins user: vasi
Jenkins password: pass
Deploy:
./mvnw deploy
Примечания
Для гибкой работы с СУБД используется QueryDSL
Ссылки
Разное
keyboard shortcuts:
Alt-F12 - переход в терминал
Разные варианты LIKE https://www.baeldung.com/spring-jpa-like-queries
Неожиданно стал СИЛЬНО ТОРМОЗИТЬ запуск тестов в Idea. Удалил папку .idea.
Просмотр и установка переменных в Windows из PowerShell:
C:>$env:PG_USER="vasi" C:>ls env: C:>> ls env:PG_USER Name Value ---- ----- PG_USER vasi
Перестройка миграций flyway(при ошибках "Flyway: Found non-empty schema(s) "public" without schema history table! Use baseline() - on Empty database") установить в application.yaml:
spring:
flyway:
baseline-on-migrate = true