|
| 1 | +% !TEX TS-program = xelatex |
| 2 | +% !TEX encoding = UTF-8 Unicode |
| 3 | +\documentclass[a4paper,12pt]{article} |
| 4 | + |
| 5 | +% Поддержка Unicode и шрифтов для кроссплатформенности |
| 6 | +\usepackage{fontspec} |
| 7 | +\usepackage{polyglossia} |
| 8 | + |
| 9 | +% Основные шрифты (кроссплатформенные) |
| 10 | +\setmainfont{DejaVu Serif} |
| 11 | +\setsansfont{DejaVu Sans} |
| 12 | +\setmonofont{DejaVu Sans Mono} |
| 13 | + |
| 14 | +% Для polyglossia кириллица |
| 15 | +\newfontfamily\cyrillicfont{DejaVu Serif} |
| 16 | +\newfontfamily\cyrillicfontsf{DejaVu Sans} |
| 17 | +\newfontfamily\cyrillicfonttt{DejaVu Sans Mono} |
| 18 | + |
| 19 | + |
| 20 | +% Настройки русского языка |
| 21 | +\setdefaultlanguage{russian} |
| 22 | +\setotherlanguages{english} |
| 23 | +\defaultfontfeatures{Ligatures=TeX} |
| 24 | + |
| 25 | +% Геометрия страницы |
| 26 | +\usepackage[a4paper, margin=2cm]{geometry} |
| 27 | + |
| 28 | +% Цвета для подсветки кода |
| 29 | +\usepackage{xcolor} |
| 30 | +\definecolor{codegreen}{rgb}{0,0.6,0} |
| 31 | +\definecolor{codegray}{rgb}{0.5,0.5,0.5} |
| 32 | +\definecolor{codepurple}{rgb}{0.58,0,0.82} |
| 33 | +\definecolor{backcolour}{rgb}{0.95,0.95,0.92} |
| 34 | + |
| 35 | +% Подсветка синтаксиса для Java |
| 36 | +\usepackage{listings} |
| 37 | +\lstdefinestyle{java}{ |
| 38 | + backgroundcolor=\color{backcolour}, |
| 39 | + commentstyle=\color{codegreen}, |
| 40 | + keywordstyle=\color{magenta}, |
| 41 | + numberstyle=\tiny\color{codegray}, |
| 42 | + stringstyle=\color{codepurple}, |
| 43 | + basicstyle=\ttfamily\footnotesize, |
| 44 | + breakatwhitespace=false, |
| 45 | + breaklines=true, |
| 46 | + captionpos=b, |
| 47 | + keepspaces=true, |
| 48 | + numbers=left, |
| 49 | + numbersep=5pt, |
| 50 | + showspaces=false, |
| 51 | + showstringspaces=false, |
| 52 | + showtabs=false, |
| 53 | + tabsize=2, |
| 54 | + frame=single, |
| 55 | + framesep=3pt, |
| 56 | + rulecolor=\color{gray}, |
| 57 | + xleftmargin=10pt, |
| 58 | + framexleftmargin=10pt, |
| 59 | + framexrightmargin=5pt, |
| 60 | + framextopmargin=2pt, |
| 61 | + framexbottommargin=2pt |
| 62 | +} |
| 63 | + |
| 64 | +\lstset{style=java} |
| 65 | + |
| 66 | +% Заголовки разделов |
| 67 | +\usepackage{titlesec} |
| 68 | +\titleformat{\section}{\normalfont\Large\bfseries}{\thesection}{1em}{} |
| 69 | +\titleformat{\subsection}{\normalfont\large\bfseries}{\thesubsection}{1em}{} |
| 70 | + |
| 71 | +% Интервалы |
| 72 | +\usepackage{setspace} |
| 73 | +\onehalfspacing |
| 74 | + |
| 75 | +% Гиперссылки |
| 76 | +\usepackage{hyperref} |
| 77 | +\hypersetup{ |
| 78 | + colorlinks=true, |
| 79 | + linkcolor=blue, |
| 80 | + filecolor=magenta, |
| 81 | + urlcolor=cyan, |
| 82 | +} |
| 83 | + |
| 84 | +% Названия разделов на русском |
| 85 | +\addto\captionsrussian{\renewcommand{\contentsname}{Содержание}} |
| 86 | +\addto\captionsrussian{\renewcommand{\listfigurename}{Список рисунков}} |
| 87 | +\addto\captionsrussian{\renewcommand{\listtablename}{Список таблиц}} |
| 88 | + |
| 89 | +% Метаданные документа |
| 90 | +\title{Современная разработка на Spring Boot: REST API с валидацией и базой данных} |
| 91 | +\author{Конспект по разработке веб-приложений} |
| 92 | +\date{\today} |
| 93 | + |
| 94 | +\begin{document} |
| 95 | + |
| 96 | +\maketitle |
| 97 | + |
| 98 | +\tableofcontents |
| 99 | + |
| 100 | +\section{Введение} |
| 101 | +Данный конспект посвящен созданию RESTful API с использованием современного стека технологий Spring Boot. Рассматриваются ключевые компоненты для быстрой разработки веб-приложений с поддержкой базы данных, валидации данных и автоматического конфигурирования. |
| 102 | + |
| 103 | +\subsection{Технологический стек} |
| 104 | +\begin{enumerate} |
| 105 | +\item \textbf{Spring WEB} --- основной модуль для создания веб-приложений и REST API |
| 106 | + \item \textbf{H2 Database} --- in-memory база данных для разработки и тестирования |
| 107 | + \item \textbf{Spring Boot DevTools} --- инструменты для ускорения разработки (автоматическая перезагрузка) |
| 108 | + \item \textbf{Spring Data JPA} --- абстракция для работы с базами данных через JPA |
| 109 | + \item \textbf{Lombok} --- библиотека для автоматической генерации шаблонного кода |
| 110 | + \item \textbf{Validation} --- валидация входных данных с помощью Jakarta Validation |
| 111 | + \item \textbf{Java 17/21} --- современные LTS-версии Java с улучшенными возможностями |
| 112 | +\end{enumerate} |
| 113 | + |
| 114 | +\section{Модель данных} |
| 115 | +Создание сущности для работы с заметками. Используется аннотации JPA для маппинга на таблицу базы данных. |
| 116 | + |
| 117 | +\subsection{Сущность Note} |
| 118 | +\begin{lstlisting}[language=Java,caption=Модель данных: класс Note] |
| 119 | +package com.example.demo.model; |
| 120 | + |
| 121 | +import jakarta.persistence.*; |
| 122 | +import lombok.*; |
| 123 | + |
| 124 | +@Entity |
| 125 | +@Table(name = "notes") |
| 126 | +@Getter |
| 127 | +@Setter |
| 128 | +@NoArgsConstructor |
| 129 | +@AllArgsConstructor |
| 130 | +@Builder |
| 131 | +@ToString |
| 132 | +@EqualsAndHashCode |
| 133 | +public class Note { |
| 134 | + |
| 135 | + @Id |
| 136 | + @GeneratedValue(strategy = GenerationType.IDENTITY) |
| 137 | + private Long id; |
| 138 | + |
| 139 | + @Column(nullable = false,length = 100) |
| 140 | + private String title; |
| 141 | + |
| 142 | + @Column(nullable = false, columnDefinition = "TEXT") |
| 143 | + private String content; |
| 144 | +} |
| 145 | +\end{lstlisting} |
| 146 | + |
| 147 | +\textbf{Пояснения к аннотациям:} |
| 148 | +\begin{enumerate} |
| 149 | +\item \texttt{@Entity} --- помечает класс как JPA-сущность |
| 150 | + \item \texttt{@Table(name = "notes")} --- указывает имя таблицы в базе данных |
| 151 | + \item \texttt{@Id} и \texttt{@GeneratedValue} --- определяют первичный ключ и стратегию генерации |
| 152 | + \item \texttt{@Column} --- настраивает параметры колонки (обязательность, размер, тип) |
| 153 | + \item \texttt{@Getter}, \texttt{@Setter} --- генерируют геттеры и сеттеры (Lombok) |
| 154 | + \item \texttt{@NoArgsConstructor}, \texttt{@AllArgsConstructor} --- генерируют конструкторы без параметров и со всеми параметрами |
| 155 | + \item \texttt{@Builder} --- создает паттерн Builder для удобного создания объектов |
| 156 | + \item \texttt{@ToString}, \texttt{@EqualsAndHashCode} --- генерируют методы toString(), equals() и hashCode() |
| 157 | +\end{enumerate} |
| 158 | + |
| 159 | +\section{Репозиторий данных} |
| 160 | +Spring Data JPA предоставляет готовые CRUD-операции через наследование от JpaRepository. |
| 161 | + |
| 162 | +\subsection{NoteRepository} |
| 163 | +\begin{lstlisting}[language=Java,caption=Репозиторий для работы с заметками] |
| 164 | +package com.example.demo.repositories; |
| 165 | + |
| 166 | +import com.example.demo.model.Note; |
| 167 | +import org.springframework.data.jpa.repository.JpaRepository; |
| 168 | +import org.springframework.stereotype.Repository; |
| 169 | + |
| 170 | +@Repository |
| 171 | +public interface NoteRepository extends JpaRepository<Note, Long> { |
| 172 | +} |
| 173 | +\end{lstlisting} |
| 174 | + |
| 175 | +\textbf{Важное замечание:} Spring Boot автоматически сканирует все классы внутри пакета (и подпакетов) на предмет аннотаций, включая \texttt{@Repository}. Обнаруженные компоненты регистрируются в ApplicationContext, после чего происходит автоматическая инъекция зависимостей (DI). |
| 176 | + |
| 177 | +\section{DTO и валидация} |
| 178 | +Для передачи данных между клиентом и сервером используются DTO (Data Transfer Objects) с валидацией. |
| 179 | + |
| 180 | +\subsection{NoteCreateDto с валидацией} |
| 181 | +\begin{lstlisting}[language=Java,caption=DTO для создания заметки с валидацией] |
| 182 | +record NoteCreateDto( |
| 183 | + @NotEmpty |
| 184 | + @Size(max = 100, |
| 185 | + message = "Слишком длинная строка") |
| 186 | + String title, |
| 187 | + |
| 188 | + @Size(max = 10000, |
| 189 | + message = "Слишком длинная строка") |
| 190 | + String content |
| 191 | +) { |
| 192 | + |
| 193 | +} |
| 194 | +\end{lstlisting} |
| 195 | + |
| 196 | +\textbf{Аннотации валидации:} |
| 197 | +\begin{enumerate} |
| 198 | +\item \texttt{@NotNull} --- поле не должно быть null (для объектов) |
| 199 | + \item \texttt{@NotEmpty} --- поле не null и не пустая коллекция/строка |
| 200 | + \item \texttt{@NotBlank} --- не null, не пустая и не состоит только из пробелов (лучше всего для строк) |
| 201 | + \item \texttt{@Size(min=, max=)} --- ограничение на размер строки или коллекции |
| 202 | + \item \texttt{@Min/@Max} --- ограничение для числовых значений |
| 203 | + \item \texttt{@Positive/@PositiveOrZero} --- положительные числа |
| 204 | + \item \texttt{@Past/@Future} --- проверка дат |
| 205 | + \item \texttt{@Email} --- базовая проверка email-адреса |
| 206 | + \item \texttt{@Pattern(regexp = "...")} --- проверка по регулярному выражению |
| 207 | + \item \texttt{@AssertTrue/@AssertFalse} --- кастомная логическая валидация |
| 208 | +\end{enumerate} |
| 209 | + |
| 210 | +\section{Контроллер REST API} |
| 211 | +Реализация CRUD-операций для управления заметками. |
| 212 | + |
| 213 | +\subsection{NoteController} |
| 214 | +\begin{lstlisting}[language=Java,caption=REST контроллер для работы с заметками] |
| 215 | +package com.example.demo.controllers; |
| 216 | + |
| 217 | +import com.example.demo.model.Note; |
| 218 | +import com.example.demo.repositories.NoteRepository; |
| 219 | +import jakarta.validation.Valid; |
| 220 | +import lombok.RequiredArgsConstructor; |
| 221 | +import org.springframework.http.HttpStatus; |
| 222 | +import org.springframework.http.ResponseEntity; |
| 223 | +import org.springframework.web.bind.annotation.*; |
| 224 | + |
| 225 | +import java.util.List; |
| 226 | +import java.util.concurrent.atomic.AtomicReference; |
| 227 | + |
| 228 | +@RestController |
| 229 | +@RequestMapping("/api") |
| 230 | +@RequiredArgsConstructor |
| 231 | +public class NoteController { |
| 232 | + |
| 233 | + private final NoteRepository noteRepository; |
| 234 | + |
| 235 | + @GetMapping |
| 236 | + public List<Note> getAllNotes() { |
| 237 | + return noteRepository.findAll(); |
| 238 | + } |
| 239 | + |
| 240 | + //ResponseEntity для корректных статусов |
| 241 | + @PostMapping |
| 242 | + public ResponseEntity<Note> createNote(@RequestBody @Valid NoteCreateDto noteDto) { |
| 243 | + var note = Note.builder().title(noteDto.title()).content(noteDto.content()).build(); |
| 244 | + var saved = noteRepository.save(note); |
| 245 | + return new ResponseEntity(saved,HttpStatus.CREATED); |
| 246 | + } |
| 247 | + |
| 248 | + @PutMapping("/{id}") |
| 249 | + public ResponseEntity<Note> updateNote( |
| 250 | + @PathVariable Long id, // или Long, если ещё не перешёл на UUID |
| 251 | + @RequestBody @Valid NoteCreateDto dto) { // @Valid — обязательно для валидации |
| 252 | + |
| 253 | + // Возвращаем готовый ResponseEntity сразу через orElseThrow + map |
| 254 | + return noteRepository.findById(id) |
| 255 | + .map(existingNote -> { |
| 256 | + existingNote.setTitle(dto.title()); |
| 257 | + existingNote.setContent(dto.content()); |
| 258 | + Note updatedNote = noteRepository.save(existingNote); |
| 259 | + return ResponseEntity.ok(updatedNote); // 200 OK |
| 260 | + }) |
| 261 | + .orElseGet(() -> ResponseEntity.notFound().build()); // 404 Not Found |
| 262 | + } |
| 263 | + |
| 264 | + @DeleteMapping("/{id}") |
| 265 | + public ResponseEntity<Void> deleteNote(@PathVariable Long id) { |
| 266 | + noteRepository.deleteById(id); |
| 267 | + return ResponseEntity.noContent().build(); |
| 268 | + } |
| 269 | +} |
| 270 | +\end{lstlisting} |
| 271 | + |
| 272 | +\textbf{Ключевые особенности:} |
| 273 | +\begin{enumerate} |
| 274 | +\item \texttt{@RestController} --- комбинирует @Controller и @ResponseBody |
| 275 | + \item \texttt{@RequiredArgsConstructor} --- генерирует конструктор для final-полей (Lombok) |
| 276 | + \item \texttt{ResponseEntity} --- позволяет контролировать HTTP-статусы и заголовки |
| 277 | + \item \texttt{@Valid} --- активирует валидацию DTO перед обработкой |
| 278 | + \item Функциональный подход с \texttt{map()} и \texttt{orElseGet()} для обработки Optional |
| 279 | + \item Корректные HTTP-статусы: 201 Created, 200 OK, 404 Not Found, 204 No Content |
| 280 | +\end{enumerate} |
| 281 | + |
| 282 | +\section{Глобальная обработка ошибок} |
| 283 | +Обработка исключений валидации на уровне приложения. |
| 284 | + |
| 285 | +\subsection{GlobalExceptionHandler} |
| 286 | +\begin{lstlisting}[language=Java,caption=Глобальный обработчик исключений] |
| 287 | +package com.example.demo.controllers; |
| 288 | + |
| 289 | +import org.springframework.http.ResponseEntity; |
| 290 | +import org.springframework.web.bind.MethodArgumentNotValidException; |
| 291 | +import org.springframework.web.bind.annotation.ExceptionHandler; |
| 292 | +import org.springframework.web.bind.annotation.RestControllerAdvice; |
| 293 | + |
| 294 | +import java.util.HashMap; |
| 295 | +import java.util.Map; |
| 296 | + |
| 297 | +@RestControllerAdvice |
| 298 | +public class GlobalExceptionHandler { |
| 299 | + |
| 300 | + @ExceptionHandler(MethodArgumentNotValidException.class) |
| 301 | + public ResponseEntity<Map<String, String>> handleValidationExceptions( |
| 302 | + MethodArgumentNotValidException ex) { |
| 303 | + Map<String, String> errors = new HashMap<>(); |
| 304 | + ex.getBindingResult().getFieldErrors().forEach(error -> |
| 305 | + errors.put(error.getField(), error.getDefaultMessage())); |
| 306 | + |
| 307 | + return ResponseEntity.badRequest().body(errors); |
| 308 | + } |
| 309 | +} |
| 310 | +\end{lstlisting} |
| 311 | + |
| 312 | +\textbf{Принцип работы:} |
| 313 | +\begin{enumerate} |
| 314 | +\item \texttt{@RestControllerAdvice} --- применяет обработчики ко всем контроллерам |
| 315 | + \item \texttt{@ExceptionHandler} --- перехватывает конкретные типы исключений |
| 316 | + \item \texttt{MethodArgumentNotValidException} --- генерируется при провале валидации |
| 317 | + \item Формируется понятный JSON-ответ с ошибками для каждого поля |
| 318 | + \item Возвращается HTTP-статус 400 Bad Request |
| 319 | +\end{enumerate} |
| 320 | + |
| 321 | +\section{Рекомендации по разработке} |
| 322 | +\begin{enumerate} |
| 323 | +\item \textbf{Валидация} --- всегда используйте \texttt{@Valid} для DTO в контроллерах |
| 324 | + \item \textbf{HTTP-статусы} --- возвращайте соответствующие статусы для каждой операции |
| 325 | + \item \textbf{DTO} --- никогда не передавайте сущности напрямую в клиент, используйте DTO |
| 326 | + \item \textbf{Иммутабельность} --- используйте record для DTO вместо классов |
| 327 | + \item \textbf{Обработка ошибок} --- централизованная обработка исключений улучшает поддерживаемость |
| 328 | + \item \textbf{Производительность} --- для продакшена замените H2 на PostgreSQL или MySQL |
| 329 | +\end{enumerate} |
| 330 | + |
| 331 | +\section{Заключение} |
| 332 | +Представленный стек технологий позволяет быстро создавать надежные REST API с минимальным количеством шаблонного кода. Использование Lombok, Spring Data JPA и валидации значительно ускоряет разработку, а правильная обработка ошибок и HTTP-статусов делает API профессиональным и удобным для клиентов. |
| 333 | + |
| 334 | +Данный подход легко масштабируется и может быть дополнен аутентификацией, авторизацией, кэшированием и другими enterprise-функциями при необходимости. |
| 335 | + |
| 336 | +\end{document} |
0 commit comments