Java Spring Boot: разработка REST-приложений от основы до деплоя

Курс знакомит с Spring Boot и практикой создания серверных приложений на Java: от настройки проекта и DI до разработки REST API. Вы научитесь работать с базой данных через Spring Data JPA, настраивать безопасность, тестировать и готовить приложение к развертыванию.

1. Введение в Spring Boot: зависимости, автоконфигурация, запуск проекта

Введение в Spring Boot: зависимости, автоконфигурация, запуск проекта

Spring Boot — это надстройка над Spring, которая ускоряет создание приложений за счёт стартовых зависимостей, автоконфигурации и удобного запуска. В этом курсе мы будем разрабатывать REST-приложение от первых эндпоинтов до сборки и деплоя. Эта статья закладывает фундамент: как создаётся проект, что такое starters, как Spring Boot понимает, какие бины включать, и как всё это запустить.

Что именно делает Spring Boot

Spring Boot обычно решает три практические задачи:

  • Упрощает управление зависимостями через стартеры (например, веб, база данных, тестирование)
  • Включает автоконфигурацию: пытается автоматически настроить компоненты на основе того, что есть в classpath и в настройках
  • Даёт готовую схему запуска: main()-метод, встроенный сервер (например, Tomcat), упаковка в исполняемый jar
  • Важно: Spring Boot не заменяет Spring и не отменяет понимание DI/IoC. Он делает типовые настройки по умолчанию, которые можно переопределять.

    Создание проекта

    Самый простой способ создать проект — через Spring Initializr.

  • Spring Initializr
  • Официальная документация Spring Boot
  • Типичный набор для старта REST-приложения:

  • Spring Web (MVC, встроенный сервер, JSON)
  • Spring Boot DevTools (ускорение разработки: автоперезапуск, удобные дефолты)
  • Validation (валидация входных DTO)
  • Spring Boot Actuator (метрики, health-check, диагностика)
  • Если в курсе вы будете использовать базу данных, то позже добавятся зависимости вроде Spring Data JPA и драйвер (например, PostgreSQL).

    Зависимости и стартеры

    Что такое starter

    Starter — это зависимость вида spring-boot-starter-*, которая подтягивает согласованный набор библиотек. Например:

  • spring-boot-starter-web подтягивает Spring MVC, Jackson для JSON и встроенный сервер
  • spring-boot-starter-test подтягивает JUnit, Spring Test и полезные библиотеки для тестирования
  • Плюс стартеров в том, что вы не собираете вручную совместимые версии десятков библиотек.

    Управление версиями: parent и BOM

    Spring Boot управляет версиями транзитивных зависимостей через механизм dependency management.

  • В Maven это обычно делается через spring-boot-starter-parent
  • В Gradle — через плагин Spring Boot и управление платформой
  • Практический смысл: вы указываете версии редко, потому что Spring Boot уже задаёт совместимый набор.

    Полезные источники:

  • Maven: Introduction to the POM
  • Gradle: Managing Dependencies
  • Maven и Gradle: как это выглядит

    | Что сравниваем | Maven | Gradle | |---|---|---| | Файл сборки | pom.xml | build.gradle или build.gradle.kts | | Подход | Декларативный XML | DSL (Groovy/Kotlin) | | Команда запуска | mvn spring-boot:run | ./gradlew bootRun | | Сборка jar | mvn package | ./gradlew bootJar |

    В рамках курса можно использовать любой инструмент — важнее понять принципы зависимостей и запуска.

    Автоконфигурация Spring Boot

    Идея автоконфигурации

    Автоконфигурация — это набор конфигураций, которые Spring Boot подключает автоматически, если выполняются условия.

    Упрощённая логика такая:

  • Если в зависимостях есть Spring MVC и вы не запретили веб-режим, приложение поднимет веб-контекст
  • Если найден Jackson, будет настроена сериализация JSON
  • Если найден Tomcat, он станет встроенным сервером по умолчанию
  • Если вы добавили JPA и источник данных, Boot попытается настроить EntityManager и репозитории
  • При этом Spring Boot старается не мешать, если вы явно задали настройку: ваши бины и настройки обычно имеют приоритет над дефолтными.

    !Диаграмма показывает, как Spring Boot по зависимостям и условиям подключает нужные конфигурации.

    @SpringBootApplication и что внутри

    Точка входа в типичном приложении — класс с main() и аннотацией @SpringBootApplication.

    Эта аннотация — композиция, которая в том числе:

  • включает сканирование компонентов (чтобы находить @Controller, @Service, @Component и т.д.)
  • включает автоконфигурацию
  • позволяет запускать приложение как обычную Java-программу
  • Минимальный пример:

    Важно: расположение этого класса в корневом пакете проекта влияет на сканирование компонентов. Обычно главный класс кладут в верхний пакет, например com.example.app, а остальные — в подпакеты.

    Условные конфигурации

    Автоконфигурации в Spring Boot подключаются по условиям. На концептуальном уровне чаще всего встречаются такие типы условий:

  • наличие класса в classpath (например, подключён ли драйвер БД)
  • наличие или отсутствие бина (например, если вы не создали свой ObjectMapper, будет создан дефолтный)
  • значение property (например, включён ли actuator)
  • Практический вывод: если “вдруг что-то настроилось само”, почти всегда причина в том, что зависимость появилась в classpath, и условие автоконфигурации сработало.

    Первый запуск проекта

    Запуск из IDE

    Обычно достаточно:

  • открыть проект
  • дождаться импорта зависимостей
  • запустить main() в классе с @SpringBootApplication
  • Если всё успешно, в логах будет видно, на каком порту поднялся сервер (по умолчанию 8080).

    Запуск из командной строки

    Для Maven:

    Для Gradle:

    Сборка исполняемого jar

    Для Maven:

    Для Gradle:

    Исполняемый jar обычно уже содержит всё нужное, включая встроенный сервер.

    Минимальный REST-эндпоинт для проверки

    Чтобы убедиться, что веб-часть работает, добавим простой контроллер.

    После запуска откройте в браузере или через curl:

    Если вы получили строку Hello, Spring Boot, значит:

  • приложение стартует
  • встроенный сервер работает
  • Spring MVC настроен
  • контроллер найден сканированием компонентов
  • Конфигурация приложения: application.properties и application.yml

    Spring Boot читает настройки из стандартных файлов конфигурации:

  • src/main/resources/application.properties
  • src/main/resources/application.yml
  • Пример настройки порта:

    Эквивалент в YAML:

    Также часто используются профили (например, dev и prod), чтобы иметь разные настройки для локальной разработки и продакшена. К профилям мы вернёмся позже, когда будем готовить приложение к деплою.

    Типичные проблемы на старте

  • Порт занят: меняйте server.port или освобождайте 8080
  • Класс контроллера “не находится”: проверьте структуру пакетов и расположение класса с @SpringBootApplication
  • Неожиданные зависимости: стартеры тянут много транзитивных библиотек — это нормально, но важно понимать, что именно подключено
  • Что дальше по курсу

    Дальше мы будем расширять приложение:

  • разберём основы REST в Spring MVC (контроллеры, DTO, параметры, коды ответа)
  • подключим валидацию
  • настроим слои приложения (controller/service/repository)
  • позже добавим базу данных, миграции, тестирование и подготовку к деплою
  • 2. Архитектура и DI: компоненты, конфигурация, профили, свойства

    Архитектура и DI: компоненты, конфигурация, профили, свойства

    В предыдущей статье мы запустили первое Spring Boot-приложение и увидели, как автоконфигурация и сканирование компонентов “поднимают” веб-приложение почти без ручных настроек. Теперь пора понять, как правильно организовать код и как Spring связывает объекты между собой.

    Эта тема держится на четырёх опорах:

  • архитектура по слоям (где живёт бизнес-логика, где контроллеры, где инфраструктура)
  • DI и контейнер Spring (кто создаёт объекты и как они получают зависимости)
  • конфигурация через бины и классы конфигурации
  • профили и свойства (как отличать настройки dev/prod и управлять поведением приложения)
  • !Диаграмма показывает слои приложения и то, что зависимости создаются контейнером Spring и внедряются в классы автоматически.

    Архитектура REST-приложения по слоям

    Для учебного и большинства прикладных REST-сервисов удобно держать код в нескольких слоях:

  • Controller: принимает HTTP-запрос, валидирует входные данные (минимально), вызывает сервис, возвращает HTTP-ответ
  • Service: бизнес-логика, правила, сценарии, транзакции (позже), оркестрация действий
  • Repository: доступ к базе данных (позже через Spring Data)
  • Client/Integration: вызовы внешних сервисов (REST, очереди, и т.д.)
  • Configuration: конфигурационные классы и бины
  • Практический смысл слоёв:

  • контроллеры становятся тонкими и тестируемыми
  • бизнес-логика не размазывается по HTTP и инфраструктуре
  • репозиторий не знает про HTTP и DTO контроллера
  • Пример структуры пакетов (типовой вариант):

  • com.example.app
  • com.example.app.controller
  • com.example.app.service
  • com.example.app.repository
  • com.example.app.config
  • com.example.app.dto
  • Важно помнить связь с первой статьёй: класс с @SpringBootApplication лучше размещать в корневом пакете (com.example.app), чтобы сканирование компонентов охватило подпакеты.

    DI и IoC в Spring: что это значит на практике

    IoC (Inversion of Control) означает, что не ваш код управляет созданием и связыванием объектов, а контейнер Spring.

    DI (Dependency Injection) означает, что зависимости (например, сервис внутри контроллера) передаются извне, а не создаются через new внутри класса.

    Почему это выгодно:

  • легче тестировать (можно подменять зависимости)
  • проще менять реализацию (интерфейс + другая реализация)
  • меньше скрытых связей и “магии” в коде
  • Что такое bean

    Bean — это объект, который:

  • создан контейнером Spring
  • зарегистрирован в контейнере
  • может быть внедрён в другие бины
  • Контейнер хранит бины в контексте приложения и управляет их жизненным циклом.

    Как Spring создаёт и связывает бины

    На базовом уровне Spring делает две вещи:

  • находит кандидатов в бины (сканирование компонентов и конфигурации)
  • создаёт объекты и внедряет зависимости (автоматически)
  • Основной рекомендуемый стиль — внедрение через конструктор.

    Пример связки Controller → Service:

    Ключевые моменты:

  • @RestController и @Service делают классы кандидатами в бины
  • Spring создаст GreetingService, затем создаст GreetingController и передаст туда GreetingService
  • Стереотипы компонентов

    В Spring часто используются аннотации-стереотипы:

  • @Component — базовая аннотация компонента
  • @Service — компонент сервисного слоя (по смыслу)
  • @Repository — компонент слоя доступа к данным (по смыслу; также даёт полезные особенности для DAO)
  • @Controller / @RestController — веб-слой
  • Технически, @Service, @Repository, @Controller — это специализированные формы @Component. Их смысл в читаемости и некоторых доп. механизмах фреймворка.

    Почему именно конструктор

    Конструкторное внедрение полезно тем, что:

  • зависимость становится обязательной (её нельзя забыть)
  • объект можно сделать более “неизменяемым” за счёт final полей
  • удобнее писать unit-тесты
  • Spring умеет сам “догадаться”, какой конструктор использовать, если он один. Поэтому @Autowired на конструкторе часто не нужен.

    Конфигурация: @Configuration и @Bean

    Не все бины обязаны быть классами с @Service или @Component. Иногда нужно создать бин вручную:

  • вы подключаете библиотеку, классы которой нельзя аннотировать
  • вы хотите явно настроить объект (например, HTTP-клиент)
  • вы хотите выбрать реализацию в зависимости от условий
  • Для этого используют классы конфигурации.

    @Configuration

    @Configuration помечает класс, который содержит фабричные методы бинов.

    Пример:

    Теперь Clock можно внедрять в другие компоненты:

    @Bean

    @Bean отмечает метод, который возвращает объект, регистрируемый как бин в контейнере.

    Важно:

  • имя бина по умолчанию — имя метода (clock)
  • тип бина — тип возвращаемого значения (Clock)
  • Конфликт нескольких бинов одного типа

    Если у вас два бина одного типа и Spring не понимает, какой внедрять, возникнет ошибка неоднозначности.

    Типовые решения:

  • внедрять по имени через @Qualifier
  • пометить один бин как основной через @Primary
  • Эти механизмы особенно актуальны, когда появляется несколько реализаций одного интерфейса.

    Профили: dev, test, prod

    Профиль — это способ включать или выключать бины и настройки в зависимости от окружения.

    Примеры, где полезны профили:

  • в dev включить более подробные логи
  • в prod подключить реальные интеграции, а в dev — заглушки
  • в test использовать in-memory хранилища или тестовые реализации
  • @Profile для бинов

    Можно пометить бин, который должен быть активен только в определённом профиле.

    Если профиль dev не активен, такого бина не будет в контейнере.

    Как активировать профиль

    Основные способы:

  • через application.properties:
  • через переменную окружения SPRING_PROFILES_ACTIVE
  • через параметр запуска:
  • В реальных проектах spring.profiles.active обычно не “зашивают” в основной конфиг, а задают окружением при деплое.

    Свойства приложения: application.properties, YAML и приоритеты

    Spring Boot читает настройки из источников свойств (property sources): файлов конфигурации, переменных окружения, аргументов командной строки и т.д.

    Файлы конфигурации

    Основные файлы:

  • src/main/resources/application.properties
  • src/main/resources/application.yml
  • Профильные варианты:

  • application-dev.properties или application-dev.yml
  • application-prod.properties или application-prod.yml
  • Пример: общий порт и dev-логирование.

    application.yml:

    application-dev.yml:

    Если активен профиль dev, Spring Boot дополнительно подхватит application-dev.yml.

    Приоритет источников (упрощённо)

    Когда одно и то же свойство задано в нескольких местах, действует принцип “кто выше по приоритету, тот и победил”. Практически полезно помнить:

  • аргументы командной строки обычно перекрывают файлы
  • переменные окружения удобно использовать при деплое
  • профильные файлы перекрывают базовый application.yml для активного профиля
  • За полными деталями приоритетов лучше обращаться к официальному описанию, потому что источников много и порядок детально регламентирован.

    Доступ к свойствам в коде: @Value и @ConfigurationProperties

    @Value

    @Value подходит для точечного чтения одного значения.

    yaml app: integration: base-url: "https://api.example.com" timeout-ms: 2000 java package com.example.app.config;

    import org.springframework.boot.context.properties.ConfigurationProperties;

    @ConfigurationProperties(prefix = "app.integration") public class IntegrationProperties {

    private String baseUrl; private long timeoutMs;

    public String getBaseUrl() { return baseUrl; }

    public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; }

    public long getTimeoutMs() { return timeoutMs; }

    public void setTimeoutMs(long timeoutMs) { this.timeoutMs = timeoutMs; } } java package com.example.app;

    import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationPropertiesScan;

    @SpringBootApplication @ConfigurationPropertiesScan public class AppApplication { public static void main(String[] args) { SpringApplication.run(AppApplication.class, args); } } java package com.example.app.service;

    import com.example.app.config.IntegrationProperties; import org.springframework.stereotype.Service;

    @Service public class IntegrationService {

    private final IntegrationProperties properties;

    public IntegrationService(IntegrationProperties properties) { this.properties = properties; }

    public String baseUrl() { return properties.getBaseUrl(); } } ``

    Плюсы @ConfigurationProperties:

  • настройки собраны в одном месте
  • типы данных (числа, строки) приводятся автоматически
  • проще документировать и поддерживать
  • Как это связано с автоконфигурацией Spring Boot

    Spring Boot активно использует те же механизмы, что и вы:

  • регистрирует бины через конфигурационные классы
  • включает/выключает части конфигурации на основании условий и свойств
  • читает свойства из application.yml, окружения и аргументов
  • Поэтому, понимая компоненты, бины, конфигурацию, профили и свойства, вы начинаете управлять приложением осознанно, а не “на удаче”.

    Практические рекомендации по стилю

  • держите контроллеры тонкими: HTTP-вопросы в контроллере, бизнес-правила в сервисе
  • используйте внедрение через конструктор
  • начинайте с аннотаций-стереотипов (@Service, @RestController), а @Configuration и @Bean добавляйте, когда действительно нужно
  • для групп настроек используйте @ConfigurationProperties, для единичных значений допустим @Value`
  • профили применяйте для различий окружений, а не для “ветвления логики” внутри бизнес-кода
  • Материалы для углубления

  • Spring Framework Reference Documentation (Core)
  • Spring Boot Reference Documentation
  • Spring Boot Features: Externalized Configuration
  • Spring Boot Features: Profiles
  • 3. Создание REST API: контроллеры, валидация, обработка ошибок, документация

    Создание REST API: контроллеры, валидация, обработка ошибок, документация

    В прошлых статьях мы разобрали, как Spring Boot стартует за счёт стартеров и автоконфигурации, а также как устроены слои приложения и DI. Теперь соберём практический каркас REST API: научимся писать контроллеры, валидировать входные данные, централизованно обрабатывать ошибки и документировать API так, чтобы им было удобно пользоваться.

    !Жизненный цикл HTTP-запроса и место валидации и обработки ошибок

    Контроллеры Spring MVC в REST-приложении

    Роль контроллера в слоистой архитектуре

    Контроллер — это входная точка HTTP. По смыслу он отвечает за:

  • разбор HTTP-запроса (путь, параметры, тело)
  • запуск валидации входных DTO
  • вызов сервисного слоя
  • формирование корректного HTTP-ответа (статус, заголовки, тело)
  • Бизнес-правила (например, “нельзя удалить активного пользователя”) лучше держать в сервисе, чтобы логика не зависела от HTTP и проще тестировалась.

    Базовые аннотации

  • @RestController — контроллер, который возвращает данные (обычно JSON). Это @Controller плюс @ResponseBody.
  • @RequestMapping — общий префикс маршрута и настройки для всех методов.
  • @GetMapping, @PostMapping, @PutMapping, @PatchMapping, @DeleteMapping — привязка метода к HTTP-методу и пути.
  • Официальная справка:

  • Spring Framework Reference: Spring Web MVC
  • Пример CRUD-контроллера

    Ниже пример контроллера, который управляет “пользователями”. Для простоты используем in-memory сервис, а базу данных подключим позже.

    Что здесь важно:

  • @RequestBody говорит Spring прочитать тело запроса (JSON) и преобразовать в Java-объект с помощью Jackson (его подтягивает spring-boot-starter-web).
  • @PathVariable извлекает часть пути /api/users/{id}.
  • @ResponseStatus задаёт HTTP-статус ответа, если вы возвращаете объект или void.
  • @Valid запускает Bean Validation для DTO.
  • Когда использовать ResponseEntity

    ResponseEntity полезен, когда нужно управлять ответом более гибко:

  • вернуть разные статусы в зависимости от ситуации
  • добавить заголовки (например, Location при создании)
  • вернуть пустое тело со статусом
  • Пример создания ресурса с заголовком Location:

    DTO и модели: почему разделять

    В REST API обычно разделяют:

  • DTO входа (request): то, что приходит от клиента
  • DTO выхода (response): то, что вы отдаёте клиенту
  • внутренние модели домена: то, с чем работает бизнес-логика
  • Это снижает связность и защищает API от случайного раскрытия лишних полей.

    Валидация входных данных

    Что такое Bean Validation

    Bean Validation — это стандарт Java для декларативной валидации объектов аннотациями. В Spring Boot 3 используется пакет jakarta.validation.

    Справка:

  • Hibernate Validator documentation
  • Пример DTO с ограничениями

    Что означают ограничения:

  • @NotBlank запрещает null, пустую строку и строку из пробелов
  • @Size(max = 50) ограничивает длину строки
  • @Email проверяет формат email (как правило, достаточно для прикладной проверки)
  • Чтобы валидация сработала в контроллере, объект должен быть помечен @Valid.

    Валидация path и query параметров

    Иногда нужно валидировать не тело, а параметры:

  • @RequestParam — query параметры
  • @PathVariable — параметры пути
  • Для этого обычно:

  • Включают @Validated на контроллер (или метод)
  • Ставят ограничения на параметры
  • Если id меньше 1, Spring выбросит исключение валидации параметра, и мы должны корректно превратить его в понятный ответ (см. раздел про обработку ошибок).

    Обработка ошибок: статусы, формат и централизованный подход

    Почему нельзя “просто вернуть текст ошибки”

    Если ошибки возвращаются в разных форматах, клиентам сложно:

  • автоматически обрабатывать ошибки
  • показывать пользователю понятные сообщения
  • понимать, какая именно часть запроса неверна
  • Хорошая практика — сделать единый формат ошибки для всего API и генерировать его централизованно.

    Типовые HTTP-статусы для REST

    | Ситуация | Статус | Когда применять | |---|---:|---| | Успешное чтение | 200 | GET возвращает данные | | Успешное создание | 201 | POST создал ресурс | | Успешное удаление без тела | 204 | DELETE, иногда PUT/PATCH | | Некорректный запрос | 400 | Невалидный JSON, не прошла валидация, неверные параметры | | Не авторизован | 401 | Нет/неверные учётные данные | | Доступ запрещён | 403 | Пользователь есть, но прав недостаточно | | Не найдено | 404 | Ресурс не существует | | Конфликт | 409 | Нарушение ограничений, конфликт состояний | | Ошибка сервера | 500 | Необработанное исключение, баг, недоступна зависимость |

    Доменные исключения: пример

    Сервисный слой часто выбрасывает доменные исключения:

    Централизованная обработка через @RestControllerAdvice

    @RestControllerAdvice позволяет перехватывать исключения из контроллеров и возвращать единый формат ответа.

    В Spring Boot 3 можно использовать ProblemDetail (ориентирован на RFC 7807).

  • RFC 7807: Problem Details for HTTP APIs
  • Spring Framework Reference: Exceptions
  • Пример обработчика:

    Практические идеи:

  • для ожидаемых доменных ситуаций делайте свои исключения и маппите их на 404/409
  • для валидации формируйте структуру, удобную фронтенду: словарь поле -> сообщение
  • для 500 не возвращайте детали исключения клиенту, чтобы не раскрывать внутренности
  • Документация API: OpenAPI и Swagger UI

    Когда API становится больше пары эндпоинтов, документация перестаёт быть опцией. На практике нужен:

  • машиночитаемый контракт (OpenAPI)
  • интерактивная UI-страница для тестирования (Swagger UI)
  • Подключение springdoc-openapi

    Для Spring Boot 3 (Spring Framework 6) часто используют springdoc-openapi.

  • springdoc-openapi на GitHub
  • Maven:

    Gradle:

    После старта приложения обычно доступны:

  • Swagger UI: /swagger-ui/index.html
  • OpenAPI JSON: /v3/api-docs
  • Минимальная настройка метаданных

    Можно задать название и версию API через аннотацию @OpenAPIDefinition.

    Документирование эндпоинтов

    На практике обычно документируют:

  • назначение операции
  • возможные статусы ответа
  • формат ошибок
  • Практические рекомендации по стилю REST API

  • используйте существительные во множественном числе для коллекций: /api/users
  • применяйте HTTP-методы по назначению: GET, POST, PUT/PATCH, DELETE
  • возвращайте корректные статусы: 201 для создания, 204 для удаления без тела
  • валидируйте входные DTO через @Valid и держите ограничения в одном месте (в DTO)
  • делайте единый формат ошибок через @RestControllerAdvice
  • подключайте OpenAPI, чтобы контракт был видимым и проверяемым
  • Как это продолжит курс

    В следующем развитии проекта эти идеи станут “скелетом”, на который ляжет инфраструктура:

  • подключение базы данных и репозиториев
  • транзакции в сервисном слое
  • интеграционные и модульные тесты контроллеров
  • подготовка приложения к окружениям и деплою (профили, конфиги, наблюдаемость)
  • 4. Работа с данными: Spring Data JPA, транзакции, миграции, оптимизация запросов

    Работа с данными: Spring Data JPA, транзакции, миграции, оптимизация запросов

    В предыдущих статьях мы построили каркас REST API: контроллеры, DTO, валидация, единый формат ошибок и документация. Следующий шаг для реального сервиса — устойчивое хранение данных и предсказуемое поведение при конкурентных запросах.

    В этой статье мы добавим слой работы с базой данных на базе Spring Data JPA, разберём транзакции (что именно они гарантируют и где их ставить), подключим миграции схемы и поговорим о типовых проблемах производительности вроде N+1 запросов.

    !Где проходит граница транзакции и как слои связаны между собой

    Что такое JPA и что именно делает Spring Data JPA

    JPA простыми словами

    JPA (Jakarta Persistence API) — это стандарт, который описывает, как:

  • сопоставлять Java-объекты и таблицы базы данных
  • читать и сохранять сущности
  • писать запросы (JPQL) поверх объектов
  • На практике чаще всего используется провайдер JPA Hibernate, который выполняет реальную работу.

    Полезные источники:

  • Документация Spring Data JPA
  • Документация Hibernate ORM
  • Spring Data JPA

    Spring Data JPA добавляет удобство поверх JPA:

  • вы описываете репозиторий как интерфейс, а реализацию Spring генерирует сам
  • получаете готовые CRUD-методы: save, findById, deleteById
  • можно создавать запросы по имени метода: findByEmail
  • можно писать явные запросы через @Query
  • есть пагинация и сортировка через Pageable
  • Подключение зависимостей и базовая конфигурация

    Зависимости

    Для типичного проекта с PostgreSQL и миграциями:

  • spring-boot-starter-data-jpa
  • драйвер PostgreSQL
  • Flyway или Liquibase
  • Maven-фрагмент (пример):

    application.yml

    Минимальная настройка подключения:

    Ключевые параметры:

  • spring.jpa.hibernate.ddl-auto=validate говорит: схема должна соответствовать сущностям, но не создавай и не изменяй её автоматически. Это хорошо сочетается с миграциями.
  • spring.jpa.open-in-view=false отключает паттерн Open Session in View: это заставляет вас не «лениво» подгружать данные в контроллере, а формировать нужный ответ в сервисном слое в рамках транзакции.
  • Официальная справка по свойствам:

  • Spring Boot: Data Properties
  • Сущности: как моделировать таблицы в коде

    Entity и идентификатор

    Сущность — это класс, экземпляры которого могут быть сохранены в таблицу.

    Пример простой сущности пользователя:

    Что важно:

  • @Entity включает класс в управление JPA.
  • @Table задаёт имя таблицы.
  • @Id и @GeneratedValue описывают первичный ключ.
  • конструктор без аргументов нужен JPA (обычно protected).
  • Связи между сущностями

    JPA умеет связывать сущности отношениями:

  • @OneToMany / @ManyToOne
  • @OneToOne
  • @ManyToMany
  • Важно понимать термин ленивая загрузка (lazy loading): по умолчанию многие связи подгружаются не сразу, а при обращении к полю. Если к этому моменту сессия уже закрыта (часто это происходит вне транзакции), вы получите ошибку.

    Практический вывод для REST:

  • не отдавайте сущности напрямую из контроллера
  • формируйте DTO в сервисе, внутри транзакции
  • Репозитории Spring Data JPA

    Базовый репозиторий

    Что вы получаете сразу:

  • findAll(), findById(id), save(entity), deleteById(id)
  • дополнительные методы по имени: findByEmail
  • Пагинация и сортировка

    Если вы возвращаете список через API, почти всегда нужна пагинация.

    Пример использования в сервисе:

    Явные запросы через @Query

    Иногда запрос по имени метода становится слишком сложным или вам нужен join.

    Транзакции: что гарантируют и где их ставить

    Что такое транзакция

    Транзакция — это набор операций с базой данных, который:

  • либо фиксируется целиком (commit)
  • либо полностью откатывается (rollback)
  • Транзакции нужны, чтобы:

  • изменения были согласованными
  • конкурентные запросы не приводили к «полудописанным» данным
  • Официальная документация:

  • Spring Framework: Transaction Management
  • @Transactional и границы транзакции

    В слоистой архитектуре транзакции обычно ставят на сервисный слой, а не на контроллеры и не на репозитории.

    Пример:

    Что даёт @Transactional:

  • открывает транзакцию перед выполнением метода
  • при успешном завершении фиксирует изменения
  • при исключении откатывает изменения (по умолчанию откат происходит для непроверяемых исключений, наследников RuntimeException)
  • readOnly = true полезен для операций чтения:

  • помогает явно отделять чтение от записи
  • может дать оптимизации на стороне провайдера JPA и базы данных (зависит от конфигурации)
  • Важный нюанс про прокси и вызовы внутри класса

    Spring чаще всего реализует @Transactional через прокси. Если транзакционный метод вызывается изнутри того же класса напрямую, прокси может не сработать.

    Практический вывод:

  • транзакционные сценарии лучше выносить в отдельные сервисы
  • или следить, чтобы вызов шёл через Spring-бин, а не через this.method()
  • Миграции схемы: Flyway

    Зачем нужны миграции

    Если вы разрабатываете REST API, схема базы данных будет меняться вместе с кодом. Миграции решают проблему управляемых изменений:

  • изменения схемы версионируются
  • на новом окружении схема поднимается автоматически
  • легко понять, какая версия схемы сейчас применена
  • Официальные источники:

  • Документация Flyway
  • Spring Boot: Flyway
  • Структура миграций Flyway

    По умолчанию Flyway ищет SQL-миграции в src/main/resources/db/migration.

    Соглашения по именам файлов:

  • V1__init.sql, V2__add_users.sql — версионные миграции
  • R__some_view.sql — повторяемые миграции (например, для представлений)
  • Пример V1__init.sql:

    Практические рекомендации:

  • миграции должны быть добавлением вперёд, а не редактированием уже применённых файлов
  • индексируйте поля, по которым часто ищете (email в примере)
  • не полагайтесь на ddl-auto=create в продакшене
  • Типовые проблемы производительности и как их решать

    Проблема N+1 запросов

    N+1 возникает, когда:

  • вы делаете один запрос за списком сущностей
  • затем для каждой сущности делается отдельный запрос за связью
  • Это часто случается из-за ленивых связей и обхода коллекций в цикле.

    !Как выглядит проблема N+1 и как меняется картина после оптимизации

    Типовые решения:

  • использовать join fetch в запросе
  • использовать @EntityGraph для указания связей, которые нужно загрузить заранее
  • использовать пакетную загрузку (batch fetching) как компромисс
  • Пример join fetch:

    DTO вместо «случайной» сериализации сущностей

    Если вернуть JPA-сущность напрямую из контроллера, вы рискуете:

  • получить ленивую загрузку вне транзакции
  • случайно раскрыть поля, которые не должны быть в API
  • столкнуться с рекурсией в JSON на двусторонних связях
  • Поэтому устойчивый подход:

  • В репозитории получаете сущности с нужными связями.
  • В сервисе преобразуете сущности в DTO.
  • В контроллере возвращаете DTO.
  • Индексы и форма запроса

    Оптимизация запросов почти всегда упирается в две вещи:

  • как написан запрос
  • какие индексы есть на таблице
  • Рекомендации общего уровня:

  • для полей поиска и join-колонок часто нужен индекс
  • выбирайте только нужные данные: используйте пагинацию, не тяните «всё и сразу»
  • включайте логирование SQL на время анализа, но аккуратно используйте его в продакшене
  • Пагинация как базовая защита API

    Пагинация помогает:

  • не перегружать базу большими выборками
  • не возвращать огромные ответы клиенту
  • держать задержки предсказуемыми
  • В REST API обычно возвращают:

  • список элементов страницы
  • параметры страницы (page, size)
  • мета-информацию (totalElements, totalPages), если нужна
  • Spring Data Page содержит эти данные из коробки.

    Как связать всё это с нашими контроллерами и обработкой ошибок

    Из статьи про REST мы уже используем:

  • DTO для входа и выхода
  • централизованную обработку ошибок через @RestControllerAdvice
  • При подключении базы данных добавляется важный слой:

  • доменные исключения (например, «не найдено»)
  • исключения конфликтов (например, нарушение уникальности)
  • Практический подход:

  • в сервисе ловить и переводить инфраструктурные ошибки в доменные (где это уместно)
  • в @RestControllerAdvice маппить доменные ошибки на 404/409
  • Практические рекомендации

  • размещайте @Transactional на сервисах, а не на контроллерах
  • отключайте open-in-view и формируйте DTO внутри транзакции
  • используйте миграции (Flyway или Liquibase), а ddl-auto держите в режиме validate
  • начинайте с пагинации на любых списках
  • при подозрении на проблемы производительности первым делом проверяйте N+1 и наличие индексов
  • Материалы для углубления

  • Документация Spring Data JPA
  • Spring Framework: Transaction Management
  • Документация Flyway
  • Spring Boot Reference Documentation
  • 5. Безопасность, тестирование и деплой: Spring Security, тесты, Docker, мониторинг

    Безопасность, тестирование и деплой: Spring Security, тесты, Docker, мониторинг

    Мы уже построили REST API (контроллеры, DTO, валидация, обработка ошибок, OpenAPI) и подключили базу данных через Spring Data JPA (транзакции, миграции, оптимизация запросов). Чтобы сервис стал производственным, обычно не хватает четырёх вещей:

  • безопасности: кто может вызывать эндпоинты и с какими правами
  • тестов: уверенность, что изменения не ломают поведение
  • деплоя: повторяемая сборка и запуск в окружениях
  • наблюдаемости: здоровье, метрики и диагностика в рантайме
  • В этой статье соберём практический набор подходов, который масштабируется от учебного проекта до реального сервиса.

    !Диаграмма показывает место Spring Security и границы слоёв приложения

    Безопасность: Spring Security для REST API

    Базовые понятия

  • Аутентификация: кто пользователь (например, логин/пароль, токен).
  • Авторизация: что ему можно (доступ к эндпоинтам и операциям).
  • Spring Security обрабатывает HTTP-запросы через цепочку фильтров и решает две задачи:

  • установить Authentication (кто пришёл)
  • проверить правила доступа (можно ли выполнять запрос)
  • Официальная документация:

  • Документация Spring Security
  • Зависимость

    Если вы используете Maven:

    Минимальная конфигурация SecurityFilterChain

    В Spring Security современный подход для настройки HTTP-безопасности в Boot 3 это бин SecurityFilterChain.

    Пример для REST API, где:

  • публичны GET /api/public/** и Swagger
  • остальные эндпоинты требуют аутентификацию
  • включена stateless модель (без серверной сессии)
  • Пояснения:

  • csrf.disable() обычно уместно для чистого REST API без cookie-сессий (CSRF в первую очередь защищает браузерные cookie-сценарии).
  • SessionCreationPolicy.STATELESS фиксирует подход: сервер не хранит состояние пользователя в сессии.
  • httpBasic() удобен для разработки, внутренних сервисов или быстрых демо.
  • Документация по конфигурированию:

  • Spring Security: Authorization
  • JWT и OAuth2 Resource Server (частый продакшен-вариант)

    Во внешних API вместо Basic Auth часто используют JWT-токены (например, от Keycloak, Auth0, Cognito). Тогда приложение выступает как resource server.

    Зависимость:

    Конфигурация:

    Пример настроек application.yml для проверки токенов по issuer-uri:

    Справка:

  • Spring Security: OAuth2 Resource Server
  • Разграничение доступа: URL-правила и Method Security

    На практике удобно сочетать два уровня:

  • правила на уровне маршрутов (authorizeHttpRequests)
  • правила на уровне методов сервиса (@PreAuthorize)
  • Пример методной авторизации:

    Почему это полезно:

  • контроллер остаётся тонким
  • бизнес-правило доступа живёт рядом с бизнес-операцией
  • методную безопасность проще переиспользовать (например, если появятся другие входные точки)
  • Документация:

  • Spring Security: Method Security
  • Пароли: только хеширование

    Если вы храните пользователей в базе, пароль нельзя хранить в открытом виде. Минимум, что нужно сделать в Spring:

    Справка:

  • Spring Security: Password Storage
  • CORS и REST

    Если ваш фронтенд работает с другого домена, потребуется CORS. Удобно настраивать централизованно, а не на каждом контроллере.

    Документация:

  • Spring Framework: CORS
  • Ошибки 401/403 в едином формате

    В статье про REST мы делали единый формат ошибок через @RestControllerAdvice. Для Spring Security важно различать:

  • 401 Unauthorized: нет/неверная аутентификация
  • 403 Forbidden: аутентификация есть, прав недостаточно
  • Для полного контроля формата часто настраивают AuthenticationEntryPoint и AccessDeniedHandler. Это уже деталь проекта, но смысл простой: безопасность тоже должна выдавать предсказуемые ответы.

    Тестирование: что и как проверять

    Что тестировать в REST-приложении

    Рекомендуемая “пирамида” тестов:

  • модульные тесты сервисов (быстрые, без Spring)
  • slice-тесты web-слоя и data-слоя (Spring поднимается частично)
  • интеграционные тесты (поднимается приложение почти целиком, иногда с реальной БД)
  • Официальная документация:

  • Spring Boot: Testing
  • Модульные тесты сервисов (JUnit + Mockito)

    Идея: тестируем бизнес-логику сервиса отдельно, подменяя репозиторий моками.

    Здесь мы проверяем именно бизнес-решения сервиса, а не детали Spring.

    Тестирование контроллеров: @WebMvcTest и MockMvc

    Если нужно проверить:

  • маршрутизацию (/api/users/{id})
  • валидацию @Valid
  • формат ошибок
  • сериализацию JSON
  • то удобно использовать @WebMvcTest, который поднимает только web-слой.

    Примечание: если вы в проекте делаете доменные исключения и маппинг их на 404 через @RestControllerAdvice, то в этом тесте стоит ожидать 404 и проверять тело ответа.

    Тестирование репозиториев: @DataJpaTest

    @DataJpaTest поднимает JPA-слой и конфигурацию для репозиториев, удобно для проверки запросов и маппинга сущностей.

    Интеграционные тесты с реальной БД: Testcontainers

    Чтобы тесты были ближе к продакшену, часто используют Testcontainers: он поднимает настоящую БД в Docker-контейнере на время теста.

    Документация:

  • Testcontainers
  • Testcontainers: JUnit 5
  • Ключевой плюс: вы тестируете миграции, SQL-диалект и поведение транзакций на реальной СУБД (например, PostgreSQL), а не на in-memory.

    Docker и деплой: повторяемая сборка и запуск

    Что должно быть параметризуемым

    Перед контейнеризацией убедитесь, что приложение настраивается через переменные окружения или аргументы:

  • SPRING_PROFILES_ACTIVE (например, dev, prod)
  • SPRING_DATASOURCE_URL, SPRING_DATASOURCE_USERNAME, SPRING_DATASOURCE_PASSWORD
  • настройки логирования
  • Это продолжение темы про профили и свойства: приложение не должно требовать правки кода для смены окружения.

    Dockerfile для Spring Boot jar

    Простой и понятный вариант:

    Сборка и запуск:

    Документация:

  • Docker: Dockerfile reference
  • docker-compose: приложение + PostgreSQL

    Для локальной разработки и интеграционных окружений удобно поднимать сразу несколько сервисов.

    Документация:

  • Docker Compose
  • !Схема контейнеров и их связей в docker-compose

    Практика деплоя

    Базовый чек-лист перед деплоем:

  • миграции включены и применяются автоматически (Flyway)
  • spring.jpa.hibernate.ddl-auto=validate
  • spring.jpa.open-in-view=false
  • секреты передаются через окружение или секрет-хранилище, а не в репозитории
  • health/readiness эндпоинты включены для оркестратора
  • Мониторинг и наблюдаемость: Actuator и метрики

    Spring Boot Actuator

    Actuator добавляет диагностические эндпоинты: здоровье приложения, метрики, информацию о среде.

    Зависимость:

    Документация:

  • Spring Boot Actuator
  • Включение эндпоинтов и безопасная публикация

    Важно: не публикуйте “всё подряд” наружу.

    Пример минимальной конфигурации:

    Что это даёт:

  • /actuator/health для проверки состояния
  • readiness/liveness пробы (удобно для Kubernetes)
  • /actuator/prometheus для сбора метрик Prometheus
  • Метрики: Micrometer + Prometheus

    В Spring Boot метрики собираются через Micrometer. Для экспорта в Prometheus добавьте:

    Документация:

  • Micrometer
  • Prometheus
  • Grafana
  • !Схема потока метрик и проверок здоровья

    Health и готовность

    Для деплоя важно различать:

  • liveness: процесс жив
  • readiness: приложение готово принимать трафик (например, есть соединение с БД)
  • Actuator умеет отдавать эти состояния, а оркестратор на их основе решает, когда включать инстанс в балансировку.

    Что логировать и как искать проблемы

    Минимальный полезный набор:

  • входящие запросы и основные параметры (без секретов)
  • ошибки с корреляционным идентификатором (request id)
  • SQL-логирование включать только для диагностики и аккуратно, чтобы не утечь персональные данные
  • Дополнительно, если проект растёт, обычно добавляют распределённую трассировку через OpenTelemetry, но это уже следующий уровень зрелости.

    Итог

    После этой статьи ваш REST-сервис получает полный “контур эксплуатации”:

  • безопасность на уровне HTTP и методов
  • набор тестов от быстрых модульных до интеграционных с реальной БД
  • контейнеризацию и запуск через переменные окружения и профили
  • наблюдаемость через health-check и метрики
  • Это делает приложение не просто учебным, а готовым к реальному окружению, где его нужно обновлять, мониторить и защищать.