Spring Boot — создание современных веб-приложений на Java

Spring Boot — это фреймворк на основе Spring, который превращает создание production-готовых приложений на Java в несколько кликов. В этом уроке мы разберёмся от создания проекта до деплоя в Docker.

Уровень: средний 19 разделов ⏱ 4–5 часов

1. Что такое Spring и Spring Boot

Spring — это мощный, но сложный фреймворк для Java, который даёт инструменты для зависимого внедрения, доступа к данным, веб-слоя и многого другого. Его слабое место — огромное количество ручной конфигурации: XML-файлы, фабрики бинов, подключение компонентов вручную.

Spring Boot — это надстройка над Spring, которая упрощает запуск приложений благодаря трём ключевым идеям:

Что потребуется для старта:

Сравнение: Spring «без Boot» против Spring Boot

АспектSpring (без Boot)Spring Boot
Запуск приложенияРазворачивать WAR на внешнем Tomcatjava -jar с встроенным сервером
КонфигурацияXML и JavaConfig вручнуюАвто-конфигурация и file application.properties
Настройка БДDataSource, JPA, транзакции вручнуюДостаточно указать URL подключения
ТестированиеСложная настройка контекстаspring-boot-starter-test из коробки
Spring Boot — это не отдельный язык, а удобная «обёртка» над Spring. Все знания о Spring, например DI и AOP, работают и здесь.

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

Самый простой способ создать проект — онлайн-генератор Spring Initializr на сайте start.spring.io. Выбери:

Необходимые зависимости для нашего урока:

ЗависимостьЗачем нужна
Spring WebREST-контроллеры и встроенный Tomcat
Spring Data JPAРабота с базой данных через репозитории
PostgreSQL DriverДрайвер для подключения к PostgreSQL (либо H2 для тестов)
Spring Boot DevToolsАвтоперезагрузка приложения при изменении кода

Нажми Generate, скачай zip-архив и распакуй его. Открой проект в IntelliJ IDEA, и структура будет такой:

demo/
├── pom.xml
└── src/main/
    ├── java/com/example/demo/
    │   ├── DemoApplication.java
    │   └── ...
    └── resources/
        ├── application.properties
        └── static/

3. Структура приложения и первый запуск

Главный класс помечается аннотацией @SpringBootApplication. Он содержит метод main, который запускает Spring-контекст:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
Аннотация @SpringBootApplication объединяет @Configuration, @EnableAutoConfiguration и @ComponentScan — то есть включает авто-конфигурацию и сканирование бинов.

Запустить приложение можно любым способом:

mvn spring-boot:run

или собрать jar и запустить его:

mvn package
java -jar target/demo-0.0.1-SNAPSHOT.jar

После запуска Spring Boot поднимет встроенный Tomcat на порту 8080. В консоли появится строка Tomcat started on port 8080. Открой http://localhost:8080 — пока увидишь страницу ошибки, потому что мы ещё не создали ни одного эндпоинта.

4. REST контроллеры

Контроллер — это класс, который обрабатывает HTTP-запросы. Аннотация @RestController объединяет @Controller и @ResponseBody: всё, что возвращает метод, автоматически превращается в JSON.

package com.example.demo.web;

import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class HelloController {

    @GetMapping("/hello")
    public Map<String, String> hello() {
        return Map.of("message", "Привет, мир!");
    }

    @GetMapping("/hello/{name}")
    public Map<String, String> greet(@PathVariable String name) {
        return Map.of("message", "Привет, " + name);
    }

    @GetMapping("/sum")
    public int sum(@RequestParam(defaultValue = "0") int a,
                   @RequestParam(defaultValue = "0") int b) {
        return a + b;
    }

    @PostMapping("/hello")
    public Map<String, String> create() {
        return Map.of("status", "создано");
    }
}
Test: GET /api/hello/Анна вернёт {"message":"Привет, Анна"}. JSON-сериализацией занимается Jackson, входящий в состав Spring Web.

5. Сервисы и внедрение зависимостей

Логику приложения лучше выносить в отдельный слой — сервисы. Для этого приложение делится на слои: контроллер → сервис → репозиторий.

package com.example.demo.service;

import com.example.demo.web.HelloController;
import org.springframework.stereotype.Service;

@Service
public class GreetingService {

    public String greet(String name) {
        return "Привет, " + name + "!";
    }
}

Теперь внедрим сервис в контроллер. Лучший способ — внедрение через конструктор:

package com.example.demo.web;

import com.example.demo.service.GreetingService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingController {

    private final GreetingService service;

    public GreetingController(GreetingService service) {
        this.service = service;
    }

    @GetMapping("/greet/{name}")
    public String greet(@PathVariable String name) {
        return service.greet(name);
    }
}

Это и есть инверсия управления (IoC): не мы создаём объекты через new, а Spring-контейнер создаёт бины и передаёт их туда, где они нужны.

АннотацияНазначение
@Componentобщий бин-компонент
@Serviceбин бизнес-логики (сервис)
@Repositoryбин доступа к данным
@Autowiredвнедрение зависимости (по полю или конструктору)
С версии Spring 4.3 конструктор с одним аргументом внедряется автоматически, поэтому @Autowired над конструктором можно не ставить. Использование поля с @Autowired затрудняет тестирование — предпочитай внедрение через конструктор.

6. Работа с базой данных

Spring Data JPA — это подмодуль Spring Data, который позволяет работать с базой данных через сущности и репозитории, не пиша SQL вручную. Сначала создадим сущность Author:

package com.example.demo.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Author {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Репозиторий — это интерфейс, расширяющий JpaRepository. Spring сам сгенерирует его реализацию:

package com.example.demo.repository;

import com.example.demo.model.Author;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface AuthorRepository extends JpaRepository<Author, Long> {

    Optional<Author> findByName(String name);

    boolean existsByName(String name);
}

Интерфейс JpaRepository расширяет CrudRepository и добавляет возможности пагинации и сортировки. Базовые CRUD-методы уже готовы:

МетодДействие
save(entity)INSERT или UPDATE
findById(id)поиск по первичному ключу
findAll()все записи
deleteById(id)удаление по id
count()количество записей
Имена методов репозитория декларативны: Spring Boot сам строит запрос по имени. Например, findByName превращается в WHERE name = ?.

7. Модели и DTO

Часто сущность нельзя отдавать наружу целиком из-за связей или лишних полей. Чтобы не тащить сущность в API, используют DTO (Data Transfer Object) — объекты для передачи данных.

Для связей между сущностями используй @JsonIgnore, чтобы избежать бесконечной сериализации (Book ↔ Author):

package com.example.demo.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;

@Entity
public class Book {

    @Id
    private Long id;

    private String title;

    @ManyToOne(fetch = FetchType.LAZY)
    @JsonIgnore
    private Author author;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Author getAuthor() {
        return author;
    }

    public void setAuthor(Author author) {
        this.author = author;
    }
}
@JsonIgnore на поле author запрещает Jackson сериализовать эту связь, иначе при чтении Book будет загружаться Author, который ссылается на Book — получится бесконечный цикл.

DTO с валидацией. Валидация объявляется аннотациями, а активируется @Valid в контроллере:

package com.example.demo.dto;

import jakarta.validation.constraints.NotBlank;

public class AuthorRequest {

    @NotBlank(message = "Имя не может быть пустым")
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Использование в контроллере вместе с обработкой ошибок через @ControllerAdvice:

package com.example.demo.web;

import com.example.demo.dto.AuthorRequest;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AuthorController {

    @PostMapping("/api/authors")
    public AuthorRequest create(@Valid @RequestBody AuthorRequest request) {
        return request;
    }
}

В отдельном классе @RestControllerAdvice перехватывает исключения валидации и возвращает аккуратный ответ с кодом 400 вместо краша:

package com.example.demo.web;

import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Map<String, String> handleValidation(MethodArgumentNotValidException ex) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return Map.of("error", ex.getBindingResult().getFieldError().getDefaultMessage());
    }
}

8. Конфигурация через application.properties

Все настройки лежат в файле src/main/resources/application.properties:

server.port=8080

spring.datasource.url=jdbc:postgresql://localhost:5432/demo
spring.datasource.username=postgres
spring.datasource.password=secret

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
КлючНазначение
server.portпорт встроенного Tomcat
spring.datasource.urlURL подключения к базе
spring.datasource.username / passwordлогин и пароль БД
spring.jpa.hibernate.ddl-autoсоздание/обновление схемы (create, update, validate, none)
spring.jpa.show-sqlвывод SQL в консоль

Для разных окружений используй профили. Файл application-dev.properties активируется профилем dev:

# application-prod.properties
server.port=8080
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASSWORD}
spring.jpa.hibernate.ddl-auto=validate
java -jar app.jar --spring.profiles.active=prod
Не храни реальные пароли в properties-файлах. Используй переменные окружения через синтаксис ${ИМЯ_ПЕРЕМЕННОЙ} — они задаются на сервере и не попадают в git.

9. Тестирование

Зависимость spring-boot-starter-test уже включает JUnit 5, Mockito и MockMvc. Интеграционный тест со ссылкой на весь контекст пишется аннотацией @SpringBootTest:

package com.example.demo.web;

import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest
@AutoConfigureMockMvc
class GreetingControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void greetsByName() throws Exception {
        mockMvc.perform(get("/greet/Иван"))
               .andExpect(status().isOk())
               .andExpect(content().string(containsString("Иван")));
    }
}

Для теста сервиса изолированно используют @MockBean или чистый Mockito. Тестируем логику без поднятия всего приложения:

package com.example.demo.service;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.example.demo.repository.AuthorRepository;
import org.junit.jupiter.api.Test;

class AuthorServiceTest {

    @Test
    void findsAuthorByName() {
        AuthorRepository repository = mock(AuthorRepository.class);
        when(repository.existsByName("Анна")).thenReturn(true);

        boolean exists = repository.existsByName("Анна");

        assertEquals(true, exists);
    }
}
Раздели подходы: юнит-тесты проверяют логику одного класса (быстро, без контекста), интеграционные тесты проверили связку контроллер + контекст через MockMvc.

10. Миграции и базы

Настройка ddl-auto=update удобна в разработке, но опасна в проде: она молча меняет схему и не даёт истории изменений. Решение — инструменты миграций:

Добавь зависимость Flyway в pom.xml:

<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>

Файл миграции в src/main/resources/db/migration/V1__create_authors.sql:

CREATE TABLE author (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL
);
В проде поставить ddl-auto=validate — тогда Hibernate лишь проверит, что схема совпадает с сущностями, а всеми изменениями занимается Flyway.
Миграции версионируются (V1, V2, ...) и применяются по порядку. Однажды применённые файлы нельзя менять — их история хранится в таблице flyway_schema_history.

11. Spring Boot + Docker

Обычная практика — собирать Docker-образ с multi-stage build: сначала компиляция в Maven-образие, затем лёгкий runtime-образ с JRE. Файл Dockerfile:

FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests

FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

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

docker build -t demo-app .
docker run -p 8080:8080 demo-app
Multi-stage build уменьшает размер образа: в runtime попадает только готовый jar и JRE, а не весь Maven и исходники.

12. Деплой и прод

Spring Boot упаковывается в два формата:

ФорматОписание
jar (по умолчанию)самодостаточный исполняемый файл со встроенным Tomcat
warдля развёртывания на внешнем сервере приложений

Для деплоя в прод обязательно:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Actuator отдаёт готовые production-эндпоинты. Проверка живости приложения:

curl http://localhost:8080/actuator/health

Ответ {"status":"UP"} означает, что приложение работает. Именно этот эндпоинт используется в health check'ах Docker и оркестраторов:

# в docker-compose.yml
healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
  interval: 30s
  timeout: 5s
  retries: 3
Включи детальный статус зависимости DB через management.endpoint.health.show-details=always — healthcheck покажет, доступна ли база данных.

13. Управление конфигурациями окружений

Профили Spring Boot позволяют держать один код под разные окружения: локально, тесты, прод. Активация профиля меняет настройки и бины без правки кода.

# application.yml — общее
app:
  name: shop-service
  retry: 3
# application-prod.yml
spring:
  datasource:
    url: jdbc:postgresql://prod-db:5432/shop
    username: ${DB_USER}
    password: ${DB_PASSWORD}
java -jar shop.jar \
  --spring.profiles.active=prod
# или:
SPRING_PROFILES_ACTIVE=prod java -jar shop.jar
Пароли и токены в application.properties — утечка. Внедряйте их переменными окружения или secret-хранилищами (Vault, Docker Secrets).
Профиль test обычно поднимает H2 in-memory БД: тесты не зависят от внешней базы и выполняются мгновенно.

14. Spring Security: JWT-аутентификация

Практичные API требуют аутентификации. Простой и современный вариант — токен JWT: подписанный JSON с идентификатором пользователя и сроком жизни.

spring-boot-starter-security
jjwt-api, jjwt-impl, jjwt-jackson

POST /api/login { "email": "...", "password": "..." }
→ Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            .csrf(AbstractHttpConfigurer::disable)
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/login", "/api/register").permitAll()
                .anyRequest().authenticated())
            .sessionManagement(sm ->
                sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .build();
    }
}
Никогда не передавайте секрет подписи в клиент: любое расшифрование JWT отдаст заполненные данные. Секрет держите только на сервере.

15. Мониторинг и Actuator

Приложение в проде обязано рассказывать о здоровье. Spring Boot Actuator открывает метрики и проверки состояния через специальные эндпоинты.

implementation 'org.springframework.boot:spring-boot-starter-actuator'
# применить в application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
ЭндпоинтДанные
/actuator/healthстатус приложения и зависимостей (БД, mq)
/actuator/infoпроизвольная информация (версия, git)
/actuator/metricsJVM-, HTTP- и бизнес-метрики
/actuator/prometheusметрики в формате Prometheus
Собирайте метрики в Prometheus, а дашборды рисуйте в Grafana: тогда падение скорости и ошибки видны до жалоб пользователей. Количество 5xx — первый индикатор проблем.
Защитите Actuator-эндпоинты (минимум авторизацией) и отключите ненужные эндпоинты через exclude: /actuator/loggers без защиты — дыра.

16. Логирование: SLF4J и Logback

Хорошие логи — единственный способ понять, что происходило в проде до падения. Стандарт Spring — SLF4J (абстракция) поверх Logback (реализация по умолчанию).

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Service
public class OrderService {
    private static final Logger log =
        LoggerFactory.getLogger(OrderService.class);

    public Order create(CreateOrderRequest req) {
        log.info("Создание заказа: user={}, total={}",
                 req.userId(), req.total());
        try {
            return doCreate(req);
        } catch (PaymentException e) {
            log.error("Оплата не прошла: order={}, reason={}",
                      req.orderId(), e.getMessage(), e);
            throw e;
        }
    }
}
УровеньКогда использовать
DEBUGдетали разработки, в проде выключено
INFOзначимые события: старт, создание, вход
WARNнештатные, но обрабатываемые ситуации
ERRORисключения, требующие вмешательства
# src/main/resources/logback-spring.xml (фрагмент)
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"/>
<root level="INFO">
    <appender-ref ref="CONSOLE"/>
</root>
Не логируйте пароли, токены и персональные данные целиком: это утечка в логах и нарушение требований к персональным данным. Исключение — маскированные фрагменты.

17. Планировщики: @Scheduled

Очистка кэша в полночь, выгрузка отчётов каждый час, проверка неоплаченных заказов — фоновые задачи в Spring Boot запускаются по расписанию.

@Configuration
@EnableScheduling
public class SchedulerConfig { }

@Service
public class ReportsJob {
    private final ReportService reports;

    @Scheduled(cron = "0 0 2 * * *")         // ежедневно в 02:00
    public void nightlyReport() { reports.buildDaily(); }

    @Scheduled(fixedDelay = 60_000)          // через 60 с после конца
    public void cleanupExpired() { ... }
}
СвойствоСмысл
fixedRateкаждые N мс от начала запуска
fixedDelayкаждые N мс после завершения
cronрасписание в формате cron
zoneчасовой пояс расписания
fixedDelay предпочтительнее fixedRate для задач с переменной длительностью: перекрытия запусков не будет. Для распределённого запуска добавляйте блокировки через Redis.
Сетевые вызовы в планировщиках оборачивайте в try/catch и логируйте: необработанное исключение в @Scheduled остановит планировщик, и последующие запуски тихо пропадут.

18. Документация API: springdoc OpenAPI

Документация API не должна жить отдельно от кода — её генерируют из дескрипторов OpenAPI по самим контроллерам.

// build.gradle / pom.xml
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui'
@Operation(summary = "Создать заказ",
           description = "Создаёт заказ и резервирует товар")
@ApiResponses({
    @ApiResponse(responseCode = "201", description = "Заказ создан"),
    @ApiResponse(responseCode = "400", description = "Невалидные данные")
})
@PostMapping("/orders")
public OrderDto create(@Valid @RequestBody CreateOrderRequest req) { ... }

Интерактивная спецификация появляется на /swagger-ui.html, JSON-схема — на /v3/api-docs. Её импортируют в Postman и используют для генерации клиента.

Описывайте схемы через DTO: спецификация тогда точно отражает то, что отдаёт API, и Jackson не превратится в источник сюрпризов для потребителей.
Swagger UI умеет выполнять запросы прямо из браузера: настройте кнопку Authorize с JWT-токеном из раздела 14

19. Testcontainers: тесты с реальной базой

Интеграционные тесты на H2 «почти совпадают» с проду, но отличия диалекта способны обмануть: часть функций не поддерживается, типы ведут себя иначе. Testcontainers поднимает настоящие PostgreSQL, Redis и Kafka в Docker прямо в тесте.

@Testcontainers
@SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class OrderRepositoryTest {

    @Container
    static PostgreSQLContainer<?> postgres =
        new PostgreSQLContainer<>("postgres:16")
            .withDatabaseName("test")
            .withUsername("test")
            .withPassword("test");

    @DynamicPropertySource
    static void props(DynamicPropertyRegistry r) {
        r.add("spring.datasource.url", postgres::getJdbcUrl);
        r.add("spring.datasource.username", postgres::getUsername);
        r.add("spring.datasource.password", postgres::getPassword);
    }

    @Test
    void findsByEmail() { ... }
}

Контейнер поднимается один раз на тестовый класс, а реальные зависимости дают уверенность, что миграции из раздела 10 применились именно к той СУБД, что работает в проде.

Testcontainers требует работающий Docker: локально это обычно есть, а в ограниченном CI предусмотрите правила запуска или отдельный профиль.

20. JSON на свой вкус: настройка Jackson

По умолчанию Jackson сериализует JSR-310 даты как массивы [2026, 9, 19] — а фронтенду нужна строка. Тонкая настройка JSON — базовая задача каждой команды, работающей через REST.

# application.yml
spring.jackson:
  ser.write-dates-as-timestamps: false   # ISO-8601 строкой
  default-property-inclusion: non_null  # не отдавать null-поля
  property-naming-strategy: SNAKE_CASE # поля без camelCase
НастройкаЭффект
write-dates-as-timestamps: falseдаты строками ISO
property-naming-strategyимена полей в JSON
default-property-inclusionnull/empty пропуск
При схеме API с примерами поездки на курс REST API: возвращайте абстрактный DTO, а не entity — меньше утечек данных и связности.
Не отдавайте entity наружу: циклические связи зацикливают JSON, а ленивые коллекции без доступа стреляют LazyInitializationException.

21. Пагинация, сортировка и фильтры

Реальный список в REST не бывает «просто все строки». Spring Data делает пагинацию и сортировку почти бесплатными, а фильтрацию наращивают Specification-ами.

@GetMapping("/products")
public Page<ProductDto> list(@PageableDefault(size=20) Pageable pageable) {
    return repo.findAll(pageable).map(ProductDto::from);
}
// SPECIFICATION: фильтр динамический
Specification<Product> spec = (root, q, cb) ->
    cb.and(cb.equal(root.get("category"), "tech"),
           cb.greaterThan(root.get("price"), 1000));
repo.findAll(spec, pageable);
Ключ запросаНазначение
page, sizeномер и размер страницы
sort=price,descсортировка по полю
filter по полямусловия (Specification)
Сортировка по неизвестному полю бросает исключение — не отдавайте клиенту сырое 500. Мапьте допустимые поля и отвечайте 400.

22. Жизненный цикл при деплое: liveness и readiness

При развороте в Kubernetes критично различать два состояния: приложение живо (JVM работает) и приложение готово принимать трафик (подключились БД, внешние сервисы). Actuator отдаёт оба эндпоинта.

management:
  endpoint.health.probes.enabled: true
  health.livenessstate.enabled: true
  health.readinessstate.enabled: true

Поды кластера стучатся в /actuator/health/liveness и /actuator/health/readiness. Liveness «прожил» → перезапустили deadlock; readiness не готов → просто не направляем трафик, приложение лечится само.

server.shutdown: graceful
spring.lifecycle.timeout-per-shutdown-phase: 20s
ЭндпоинтОтвет
/health/livenessUP / DOWN
/health/readinessACCEPTING_TRAFFIC / REFUSING_TRAFFIC
Нагружайте readiness внешней БД: если БД упала, Kubernetes не считает под «готовым» и перестаёт лить трафик в пакет.

23. Spring Boot 3/4: виртуальные потоки и native image

Spring Boot 3 открыл два направления производительности: виртуальные потоки (JVM 21+) снимают лимит «поток на запрос», а GraalVM native image сокращает старт до миллисекунд.

# application.yml
spring.threads.virtual.enabled: true
ПодходСтартПамятьВремя компиляции
классический JVM1–3 сексреднемгновенно
native image~50 мснизкаяминуты
mvn -Pnative native:compile   # требуется GraalVM + native-build-tools
В native многие библиотеки (Jackson с рефлексией, некоторые JDBC-драйверы) требуют hints. Прежде чем внедрять, сверьтесь со списком поддерживаемых стека, иначе релиз упадёт с NoSuchMethodError.

Что дальше

Ты прошёл путь от пустого проекта до готового к деплою приложения. Рекомендуемые следующие шаги: глубже разобрать Spring и JUnit, изучить безопасность (Spring Security), а затем перейти к работе с Docker и базами данных PostgreSQL.

← Вернуться к курсам