---
title: "Java Spring — Хранение данных"
url: "https://romankryvolapov.com/ru/java-spring-data-storage/"
description: "Хранение данных в Spring: ACID и уровни изоляции, @Transactional, JPA против JDBC, Spring Data, кеширование в Redis, Flyway и индексы."
language: ru
updated: 2026-01-21
---
## Что такое ACID (Atomicity, Consistency, Isolation, Durability) {#Что-такое-ACID-Atomicity-Consistency-Isolation-Durability}

Набор свойств, которые гарантируют надёжность и предсказуемость транзакций в системах управления базами данных (СУБД). Аббревиатура расшифровывается как:

### Atomicity (Атомарность)

Транзакция выполняется целиком или не выполняется вообще.\
Если произошла ошибка на любом этапе, система откатывает все изменения, внесённые в рамках этой транзакции.

**Пример:**\
если вы переводите деньги с одного счёта на другой, операция списания и операция зачисления должны пройти вместе. Если зачисление не удалось, то и списание отменяется.

### Consistency (Согласованность)

После завершения транзакции база должна оставаться в корректном и согласованном состоянии.\
Это означает соблюдение всех ограничений (уникальность, внешние ключи, правила и т. д.).

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

### Isolation (Изолированность)

Одновременные транзакции не должны мешать друг другу.\
Каждая транзакция должна выполняться так, будто она одна в системе.\
СУБД использует уровни изоляции (Read Uncommitted, Read Committed, Repeatable Read, Serializable), чтобы управлять этим свойством.

**Пример:**\
два пользователя не должны видеть промежуточные результаты друг друга.

### Durability (Надёжность / Постоянство)

После успешного завершения транзакции её изменения не будут потеряны, даже если произойдёт сбой (например, отключение электричества).\
СУБД записывает изменения в журнал или на диск, чтобы обеспечить восстановление.

**Пример:**\
если операция перевода завершена, деньги точно "уйдут" и "придут" — даже при падении сервера.

Вместе эти свойства делают возможным надёжное управление данными, особенно в условиях высокой нагрузки или сбоев. Они — основа транзакционной логики в большинстве реляционных баз данных (PostgreSQL, MySQL, Oracle и др.).

## Что такое CRUD (Create, Read, Update, Delete) {#Что-такое-CRUD-Create-Read-Update-Delete}

CRUD — это аббревиатура, обозначающая основные операции, которые можно выполнять с данными в базе данных. CRUD расшифровывается как Create, Read, Update, Delete:

**Create (Создание):**\
Операция добавления новых данных в базу данных. В контексте Spring Data это может быть метод save, который сохраняет новую сущность в базе данных.

**Read (Чтение):**\
Операция получения данных из базы данных. В Spring Data это методы типа findById, findAll и кастомные методы для поиска по различным критериям, например, findByName.

**Update (Обновление):**\
Операция изменения существующих данных в базе данных. В Spring Data для этого также используется метод save, который обновляет сущность, если она уже существует.

**Delete (Удаление):**\
Операция удаления данных из базы данных. В Spring Data это методы deleteById и delete.

В Spring Data есть несколько стандартных методов для работы с репозиториями. Эти методы можно использовать для базовых операций CRUD (Create, Read, Update, Delete). Примеры использования репозиториев с Kotlin и Gradle Groovy показаны ниже.

Основные стандартные методы в Spring Data репозиториях:

**save(S entity: T): T:**\
сохраняет или обновляет сущность.

**findById(id: ID): Optional:**\
ищет сущность по её ID.

**findAll(): List:**\
возвращает все сущности.

**deleteById(id: ID):**\
удаляет сущность по её ID.

**delete(entity: T):**\
удаляет указанную сущность.

**existsById(id: ID): Boolean:**\
проверяет существование сущности по её ID.

**count(): Long:**\
возвращает количество сущностей.

**Пример реализации, используются стандартные методы и один кастомный:**

```kotlin
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id


@Entity
data class Product(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,
    val name: String,
    val price: Double
)
```

```kotlin
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository


@Repository
interface ProductRepository : JpaRepository<Product, Long> {

    fun findByName(name: String): List<Product>

}
```

```kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.*


@RestController
@RequestMapping("/products")
class ProductController(@Autowired val productRepository: ProductRepository) {

    // Create
    @PostMapping
    fun createProduct(@RequestBody product: Product): Product {
        return productRepository.save(product)
    }

    // Read all
    @GetMapping
    fun getAllProducts(): List<Product> {
        return productRepository.findAll()
    }

    // Read by ID
    @GetMapping("/{id}")
    fun getProductById(@PathVariable id: Long): Product? {
        return productRepository.findById(id).orElse(null)
    }

    // Read by name
    @GetMapping("/search")
    fun getProductsByName(@RequestParam name: String): List<Product> {
        return productRepository.findByName(name)
    }

    // Update
    @PutMapping("/{id}")
    fun updateProduct(@PathVariable id: Long, @RequestBody updatedProduct: Product): Product? {
        return productRepository.findById(id).map { existingProduct ->
            val newProduct = existingProduct.copy(name = updatedProduct.name, price = updatedProduct.price)
            productRepository.save(newProduct)
        }.orElse(null)
    }

    // Delete by ID
    @DeleteMapping("/{id}")
    fun deleteProduct(@PathVariable id: Long) {
        productRepository.deleteById(id)
    }

    // Delete
    @DeleteMapping
    fun deleteProduct(@RequestBody product: Product) {
        productRepository.delete(product)
    }

    // Check existence by ID
    @GetMapping("/exists/{id}")
    fun existsById(@PathVariable id: Long): Boolean {
        return productRepository.existsById(id)
    }

    // Count all products
    @GetMapping("/count")
    fun countProducts(): Long {
        return productRepository.count()
    }
}
```

## Что такое JPA (Java Persistence API) и Hibernate {#Что-такое-JPA-Java-Persistence-API-и-Hibernate}

Hibernate и JPA (Java Persistence API) — это два тесно связанных термина в мире Java-проектов, связанных с объектно-реляционным отображением (ORM). Важно понимать их различия и взаимосвязь.

**JPA (Java Persistence API):**\
JPA — это стандартная спецификация, разработанная для обеспечения ORM в приложениях Java. Она определяет интерфейсы и аннотации для управления персистентными данными и их связями. JPA не является конкретной реализацией; это просто набор правил и стандартов, которые должны быть реализованы конкретными ORM-фреймворками.

**Основные характеристики JPA:**\
Спецификация для ORM.\
Не содержит реализацию.\
Определяет аннотации и интерфейсы, такие как @Entity, @Table, EntityManager, и т.д.\
Обеспечивает независимость от поставщика конкретного ORM.

**Hibernate:**\
Hibernate — это ORM-фреймворк, который является одной из реализаций спецификации JPA. Он предоставляет конкретные инструменты и механизмы для работы с базами данных на основе JPA, а также включает в себя дополнительные возможности, выходящие за рамки JPA.

**Основные характеристики Hibernate:**\
Конкретная реализация ORM, соответствующая JPA.\
Дополнительные возможности, такие как кэширование, расширенные типы ассоциаций, более гибкие критерии запросов и т.д.\
Собственные аннотации и конфигурации (например, @Cache, @BatchSize).\
Расширяемость и множество дополнительных инструментов для оптимизации производительности.\
Взаимодействие между JPA и Hibernate\
Когда вы используете JPA, вы пишете код, который следует спецификации JPA. Этот код будет работать с любой реализацией JPA, включая Hibernate, EclipseLink и другие. Hibernate, в свою очередь, предоставляет конкретные инструменты для выполнения JPA-кода, а также добавляет свои собственные возможности.

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

```xml
// Hibernate-specific configuration (hibernate.cfg.xml)
<hibernate-configuration>
    <session-factory>
        <!-- Database connection settings -->
        <property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/mydb</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">password</property>
        <!-- JDBC connection pool settings ... using built-in test pool -->
        <property name="hibernate.c3p0.min_size">5</property>
        <property name="hibernate.c3p0.max_size">20</property>
        <property name="hibernate.c3p0.timeout">300</property>
        <property name="hibernate.c3p0.max_statements">50</property>
        <property name="hibernate.c3p0.idle_test_period">3000</property>
        <!-- Echo all executed SQL to stdout -->
        <property name="hibernate.show_sql">true</property>
        <!-- Drop and re-create the database schema on startup -->
        <property name="hibernate.hbm2ddl.auto">update</property>

        <!-- Names the annotated entity class -->
        <mapping class="com.example.User"/>
    </session-factory>
</hibernate-configuration>
```

```java
// JPA Entity
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;


@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

// JPA Repository
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;

public class UserRepository {

    @PersistenceContext
    private EntityManager entityManager;

    @Transactional
    public void save(User user) {
        entityManager.persist(user);
    }

    public User find(Long id) {
        return entityManager.find(User.class, id);
    }

}
```

Таким образом, JPA — это спецификация для ORM, а Hibernate — это фреймворк, который реализует эту спецификацию и добавляет свои возможности.

## Что такое Spring Data JPA {#Что-такое-Spring-Data-JPA}

Spring Data JPA — это часть проекта Spring Data, предназначенная для упрощения работы с базами данных на основе JPA (Java Persistence API). Это высокоуровневая библиотека, которая предоставляет абстракцию над JPA и упрощает разработку, снижая объем шаблонного кода, необходимого для выполнения основных операций с базой данных, таких как создание, чтение, обновление и удаление (CRUD). Spring Data JPA предлагает множество полезных функций, таких как автоматическая генерация запросов, поддержка методов запросов на основе имен, а также интеграция с различными механизмами хранения данных.

**Основные преимущества Spring Data JPA:**

**Упрощение работы с базой данных:**\
Уменьшает количество шаблонного кода, необходимого для выполнения операций с базой данных.

**Поддержка стандартных операций CRUD:**\
Предоставляет встроенные методы для выполнения основных операций с данными.

**Автоматическая генерация запросов:**\
Позволяет автоматически генерировать запросы на основе имен методов репозитория.

**Поддержка сложных запросов:**\
Поддерживает написание JPQL, Native SQL и Criteria API для более сложных запросов.

**Расширяемость:**\
Легко расширяется и настраивается под нужды приложения.

**Основные компоненты Spring Data JPA:**

**Repository:**\
Интерфейсы, которые предоставляют методы для выполнения операций с базой данных.

**Entity:**\
Классы, представляющие сущности базы данных.

**JPQL и Native Queries:**\
Возможность использования Java Persistence Query Language и нативных SQL-запросов для выполнения операций с базой данных.

**Custom Queries:**\
Возможность создания пользовательских методов для выполнения специфических операций с базой данных.

## Что такое JDBC (Java Database Connectivity) {#Что-такое-JDBC-Java-Database-Connectivity}

Java Database Connectivity (JDBC) — это API для языка программирования Java, который определяет, как клиент может получать доступ к базе данных. JDBC является частью Java Standard Edition и предоставляет разработчикам стандартный интерфейс для взаимодействия с различными реляционными базами данных.

**Основные характеристики JDBC:**

**Низкоуровневый API:**\
JDBC предоставляет низкоуровневый доступ к базам данных, что позволяет разработчикам выполнять SQL-запросы напрямую и получать результаты.

**SQL-запросы:**\
JDBC позволяет выполнять SQL-запросы (SELECT, INSERT, UPDATE, DELETE) и другие операции с базой данных.

**Управление соединениями:**\
JDBC управляет подключениями к базе данных, что включает открытие и закрытие соединений, обработку транзакций и управление пулом соединений.

**Совместимость с различными СУБД:**\
JDBC поддерживает работу с различными реляционными базами данных, такими как MySQL, PostgreSQL, Oracle, SQL Server и другие.\
Основные компоненты JDBC

**JDBC Driver:**\
Драйвер JDBC является реализацией интерфейсов JDBC для конкретной базы данных. Он обеспечивает связь между Java-приложением и базой данных.

**Существует четыре типа драйверов JDBC:**\
тип 1 (JDBC-ODBC Bridge)\
тип 2 (Native API)\
тип 3 (Network Protocol)\
тип 4 (Thin Driver, Pure Java Driver).

**Connection:**\
Интерфейс Connection представляет соединение с базой данных. Он используется для отправки запросов и управления транзакциями.

**Statement:**\
Интерфейс Statement используется для выполнения статических SQL-запросов и возвращения результатов.\
Существует три типа Statement: Statement, PreparedStatement (для выполнения предкомпилированных SQL-запросов) и CallableStatement (для вызова хранимых процедур).

**ResultSet:**\
Интерфейс ResultSet представляет результирующий набор данных, возвращаемый SQL-запросом. Он позволяет перемещаться по результатам и извлекать данные.

**SQLException:**\
Класс SQLException используется для обработки ошибок и исключений, возникающих при работе с JDBC.

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

```java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;


public class JdbcExample {

    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/mydatabase";
        String username = "root";
        String password = "password";
        try (Connection connection = DriverManager.getConnection(url, username, password);
             Statement statement = connection.createStatement();
             ResultSet resultSet = statement.executeQuery("SELECT id, name FROM users")) {
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                System.out.println("User ID: " + id + ", Name: " + name);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}
```

## Чем JPA (Java Persistence API) отличается от JDBC (Java Database Connectivity) {#Чем-JPA-Java-Persistence-API-отличается-от-JDBC-Java-Database-Connectivity}

Java Persistence API (JPA) и Java Database Connectivity (JDBC) — это два разных подхода к работе с базами данных в Java. Вот основные различия между ними:

**JDBC (Java Database Connectivity):**

**Уровень абстракции:**\
JDBC предоставляет низкоуровневый API для взаимодействия с базами данных.\
Работа через JDBC требует написания SQL-запросов вручную и обработки результатов запросов через ResultSet.

**Прямой доступ к базе данных:**\
JDBC позволяет разработчику напрямую работать с базой данных, используя SQL-запросы.\
Разработчик должен сам управлять подключениями, транзакциями и обработкой ошибок.

**Меньше абстракции:**\
JDBC предоставляет меньший уровень абстракции, что делает код более подробным и сложным для поддержки.\
Требует написания большого количества кода для выполнения простых операций с базой данных.

**Гибкость:**\
JDBC позволяет использовать все возможности конкретной СУБД, включая специфические для нее функции и расширения.

**JPA (Java Persistence API):**

**Уровень абстракции:**\
JPA предоставляет более высокоуровневый API для работы с объектно-реляционным отображением (ORM).\
Разработчики работают с объектами Java вместо написания SQL-запросов напрямую.

**Объектно-реляционное отображение:**\
JPA автоматически отображает объекты Java на таблицы базы данных.\
Это позволяет работать с базой данных на уровне объектов, что упрощает разработку и поддержку кода.

**Управление состоянием:**\
JPA поддерживает управление состоянием объектов, автоматическое отслеживание изменений и синхронизацию с базой данных.\
JPA автоматически обрабатывает транзакции, кеширование и другие задачи.

**Меньше кода:**\
JPA значительно уменьшает объем кода, необходимого для выполнения операций с базой данных.\
Использование аннотаций и конфигурационных файлов позволяет легко настраивать и изменять модель данных.

**Кроссплатформенность:**\
JPA предоставляет абстракцию, которая позволяет работать с различными СУБД без изменения кода.

**Пример для сравнения:**\
\
**JDBC:**

```kotlin
public List<User> getUsers() {
    List<User> users = new ArrayList<>();
    try (Connection connection = DriverManager.getConnection(url, username, password);
         Statement statement = connection.createStatement();
         ResultSet resultSet = statement.executeQuery("SELECT id, name FROM users")) {
        while (resultSet.next()) {
            User user = new User();
            user.setId(resultSet.getInt("id"));
            user.setName(resultSet.getString("name"));
            users.add(user);
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return users;
}
```

**JPA:**

```kotlin
public List<User> getUsers() {
    EntityManager em = entityManagerFactory.createEntityManager();
    return em.createQuery("SELECT u FROM User u", User.class).getResultList();
}
```

**Совместное использование JPA и JDBC:**\
может быть полезно в случаях, когда нужно выполнить сложные SQL-запросы, не поддерживаемые JPA, или для оптимизации производительности при работе с большими объемами данных. Вот пример, демонстрирующий, как можно использовать JPA для управления сущностями и JDBC для выполнения специфических запросов.

```kotlin
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    implementation 'com.h2database:h2' // H2 для тестирования
}
```

```java
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;


@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    // getters and setters
}
```

```java
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}
```

```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;


@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public void createUser(String name) {
        User user = new User();
        user.setName(name);
        userRepository.save(user);
    }

    public List<User> getAllUsersJPA() {
        return userRepository.findAll();
    }

    public List<User> getAllUsersJDBC() {
        String sql = "SELECT id, name FROM User";
        return jdbcTemplate.query(sql, new UserRowMapper());
    }

    private static class UserRowMapper implements RowMapper<User> {
        @Override
        public User mapRow(ResultSet rs, int rowNum) throws SQLException {
            User user = new User();
            user.setId(rs.getLong("id"));
            user.setName(rs.getString("name"));
            return user;
        }
    }

}
```

```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;


@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping("/users")
    public void createUser(@RequestParam String name) {
        userService.createUser(name);
    }

    @GetMapping("/users/jpa")
    public List<User> getUsersJPA() {
        return userService.getAllUsersJPA();
    }

    @GetMapping("/users/jdbc")
    public List<User> getUsersJDBC() {
        return userService.getAllUsersJDBC();
    }

}
```

**Сущность и репозиторий JPA:**\
Класс User представляет сущность базы данных, а интерфейс\
UserRepository используется для стандартных операций CRUD с помощью JPA.

**Сервисный слой:**\
Класс UserService содержит бизнес-логику. В методе createUser используется JPA для создания нового пользователя. Методы getAllUsersJPA и getAllUsersJDBC демонстрируют использование JPA и JDBC соответственно для получения списка пользователей.

**Контроллер:**\
Класс UserController предоставляет REST API для взаимодействия с сервисом. Методы getUsersJPA и getUsersJDBC вызывают соответствующие методы сервиса для получения данных с использованием JPA и JDBC.

Таким образом, вы можете использовать преимущества обеих технологий: удобство и абстракцию JPA для стандартных операций и гибкость JDBC для сложных запросов.

## Что такое JPQL и Criteria API {#Что-такое-JPQL-и-Criteria-API}

**PQL и Criteria API в Spring Data JPA:**\
JPQL (Java Persistence Query Language) и Criteria API предоставляют гибкие способы выполнения запросов к базе данных с использованием JPA. Оба подхода поддерживаются Spring Data JPA и могут использоваться в зависимости от требований приложения.

**JPQL (Java Persistence Query Language):**\
JPQL — это объектно-ориентированный язык запросов, который работает с объектами JPA, а не с таблицами базы данных. Запросы на JPQL напоминают SQL, но оперируют сущностями, их атрибутами и отношениями.

**Пример использования JPQL:**\
Рассмотрим пример, в котором мы будем использовать JPQL для выполнения запросов. В этом примере метод findByNameJPQL использует аннотацию @Query для выполнения JPQL-запроса, который находит пользователей по имени.

```kotlin
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.stereotype.Repository


@Repository
interface UserRepository : JpaRepository<User, Long> {

    @Query("SELECT u FROM User u WHERE u.name = :name")
    fun findByNameJPQL(name: String): List<User>

}
```

**Criteria API:**\
Criteria API предоставляет типобезопасный и объектно-ориентированный способ создания запросов к базе данных. Он особенно полезен, когда запросы должны строиться динамически.

**Пример использования Criteria API:**\
Для использования Criteria API нам нужно использовать EntityManager, который управляет операциями JPA.\
Создаем интерфейс для определения кастомных методов.

```kotlin
interface UserRepositoryCustom {
    fun findByNameCriteria(name: String): List<User>
}
```

Реализуем интерфейс, используя Criteria API.

```kotlin
import org.springframework.stereotype.Repository
import javax.persistence.EntityManager
import javax.persistence.PersistenceContext
import javax.persistence.criteria.CriteriaBuilder
import javax.persistence.criteria.CriteriaQuery
import javax.persistence.criteria.Root


@Repository
class UserRepositoryCustomImpl : UserRepositoryCustom {

    @PersistenceContext
    private lateinit var entityManager: EntityManager

    override fun findByNameCriteria(name: String): List<User> {
        val criteriaBuilder: CriteriaBuilder = entityManager.criteriaBuilder
        val criteriaQuery: CriteriaQuery<User> = criteriaBuilder.createQuery(User::class.java)
        val root: Root<User> = criteriaQuery.from(User::class.java)
        criteriaQuery.select(root).where(criteriaBuilder.equal(root.get<String>("name"), name))
        val query = entityManager.createQuery(criteriaQuery)
        return query.resultList
    }

}
```

Расширяем основной репозиторий кастомным интерфейсом.

```kotlin
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository


@Repository
interface UserRepository : JpaRepository<User, Long>, UserRepositoryCustom
```

## Как подключаться к базам данных в Spring {#Как-подключаться-к-базам-данных-в-Spring}

В Spring Boot можно легко настроить подключение к разным типам баз данных, используя файлы конфигурации application.properties. Ниже приведены примеры настройки подключения к различным популярным базам данных: H2, MySQL, PostgreSQL, Oracle, MariaDB.

**Подключение к H2 (в памяти):**\
H2 — это встроенная база данных, которая часто используется для разработки и тестирования.

```kotlin
dependencies {
    implementation("com.h2database:h2")
}
```

```properties
# H2 Database Configuration
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=update
```

**Подключение к MySQL:**

```kotlin
dependencies {
    implementation("mysql:mysql-connector-java")
}
```

```properties
# MySQL Database Configuration
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.username=myuser
spring.datasource.password=mypassword
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.jpa.hibernate.ddl-auto=update
```

**Подключение к PostgreSQL:**

```kotlin
dependencies {
    implementation("org.postgresql:postgresql")
}
```

```properties
# PostgreSQL Database Configuration
spring.datasource.url=jdbc:postgresql://localhost:5432/mydatabase
spring.datasource.driverClassName=org.postgresql.Driver
spring.datasource.username=myuser
spring.datasource.password=mypassword
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update
```

**Подключение к Oracle:**

```kotlin
dependencies {
    implementation("com.oracle.database.jdbc:ojdbc8")
}
```

```properties
# Oracle Database Configuration
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:orcl
spring.datasource.driverClassName=oracle.jdbc.OracleDriver
spring.datasource.username=myuser
spring.datasource.password=mypassword
spring.jpa.database-platform=org.hibernate.dialect.Oracle12cDialect
spring.jpa.hibernate.ddl-auto=update
```

**Подключение к MongoDB (NoSQL):**

```kotlin
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-mongodb")
}
```

```properties
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=mydatabase
```

```kotlin
import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.mapping.Document


@Document(collection = "users")
data class User(
    @Id
    val id: String? = null,
    val name: String,
    val email: String
)
```

```kotlin
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.stereotype.Repository


@Repository
interface UserRepository : MongoRepository<User, String> {
    fun findByName(name: String): User
}
```

**Подключение к MariaDB:**

```kotlin
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.mariadb.jdbc:mariadb-java-client'
    runtimeOnly 'org.springframework.boot:spring-boot-devtools'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
```

```properties
spring.datasource.url=jdbc:mariadb://localhost:3306/mydatabase
spring.datasource.username=root
spring.datasource.password=yourpassword
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
```

**Подключение к Couchbase (NoSQL):**

```kotlin
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-couchbase'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
    implementation 'org.jetbrains.kotlin:kotlin-reflect'
    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
```

```properties
spring.couchbase.connection-string=127.0.0.1
spring.couchbase.username=your_username
spring.couchbase.password=your_password
spring.couchbase.bucket.name=your_bucket
```

```kotlin
import org.springframework.context.annotation.Configuration
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration


@Configuration
class CouchbaseConfig : AbstractCouchbaseConfiguration() {
    override fun getConnectionString(): String {
        return "127.0.0.1"
    }
    override fun getUserName(): String {
        return "your_username"
    }
    override fun getPassword(): String {
        return "your_password"
    }
    override fun getBucketName(): String {
        return "your_bucket"
    }
}
```

```kotlin
import com.example.model.User
import org.springframework.data.couchbase.repository.CouchbaseRepository
import org.springframework.stereotype.Repository


@Repository
interface UserRepository : CouchbaseRepository<User, String> {
    fun findByEmail(email: String): List<User>
}
```

**Настройка пула соединений:**\
Независимо от используемой базы данных, можно настроить пул соединений для улучшения производительности.

```properties
# Connection Pool Configuration
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
```

## Какие есть уровни изоляции транзакций {#Какие-есть-уровни-изоляции-транзакций}

Уровни изоляции транзакций определяют степень видимости изменений данных, сделанных одной транзакцией, для других параллельно выполняющихся транзакций. Это важно для управления конкурентным доступом к данным и предотвращения различных аномалий при одновременном выполнении транзакций. В SQL стандарте и большинстве реляционных СУБД определены четыре уровня изоляции транзакций:

```text
Уровень изоляции:    | Грязное чтение | Неповторяющееся чтение | Фантомное чтение
---------------------------------------------------------------------------------
READ UNCOMMITTED     |       +        |            +           |        +
READ COMMITTED       |       -        |            +           |        +
REPEATABLE READ      |       -        |            -           |        +
SERIALIZABLE         |       -        |            -           |        -
```

**Read Uncommitted (Чтение неподтвержденных данных):**\
Описание: Этот уровень изоляции позволяет транзакции читать данные, которые были изменены, но еще не подтверждены (не зафиксированы) другими транзакциями.

**Аномалии:**\
Грязное чтение (Dirty Read): Чтение данных, которые могут быть откатаны в другой транзакции.\
Неповторяющееся чтение (Non-repeatable Read): Чтение данных, которые могут быть изменены другой транзакцией между двумя чтениями.\
Фантомное чтение (Phantom Read): Чтение строк, которые могут быть добавлены или удалены другой транзакцией.

**Read Committed (Чтение подтвержденных данных):**\
Описание: Транзакция может читать только те данные, которые были подтверждены (зафиксированы) другими транзакциями. Это предотвращает грязное чтение.

**Аномалии:**\
Неповторяющееся чтение (Non-repeatable Read): Чтение данных, которые могут быть изменены другой транзакцией между двумя чтениями.\
Фантомное чтение (Phantom Read): Чтение строк, которые могут быть добавлены или удалены другой транзакцией.

**Repeatable Read (Повторяющееся чтение):**\
Описание: Этот уровень изоляции гарантирует, что данные, прочитанные в начале транзакции, не будут изменены другими транзакциями до завершения текущей транзакции. Это предотвращает как грязное чтение, так и неповторяющееся чтение.

**Аномалии:**\
Фантомное чтение (Phantom Read): Чтение строк, которые могут быть добавлены или удалены другой транзакцией.

**Serializable (Сериализуемость):**\
Описание: Самый высокий уровень изоляции, при котором транзакции выполняются так, как если бы они выполнялись последовательно, одна за другой, а не параллельно. Это предотвращает все виды аномалий.

**Аномалии:**\
Не допускаются. Гарантирует отсутствие грязного чтения, неповторяющегося чтения и фантомного чтения.

**Пояснение аномалий:**

**Грязное чтение (Dirty Read):**\
Одна транзакция читает данные, измененные другой транзакцией, которая еще не была зафиксирована. Если вторая транзакция откатывается, то первая транзакция использует некорректные данные.

**Неповторяющееся чтение (Non-repeatable Read):**\
Данные, которые были прочитаны одной транзакцией, могут изменяться другими транзакциями, что приводит к тому, что повторное чтение тех же данных возвращает разные результаты.

**Фантомное чтение (Phantom Read):**\
Одна транзакция читает набор строк, которые соответствуют определенному условию. Если другая транзакция добавляет или удаляет строки, соответствующие этому условию, то повторное выполнение запроса в первой транзакции возвращает другой набор строк.

**Примеры использования уровней изоляции в Spring:**\
В Spring уровень изоляции транзакций можно задать с помощью аннотации @Transactional, есть и другие способы

```kotlin
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Isolation
import org.springframework.transaction.annotation.Transactional


@Service
class MyService {
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    fun performReadUncommittedOperation() {
    }
}
```

## Какие есть параметры у аннотации @Transactional {#Какие-есть-параметры-у-аннотации-Transactional}

**propagation (тип: Propagation):**\
Определяет, как транзакция должна распространяться на методы, вызываемые внутри текущей транзакции.

**Значения:**

**Propagation.REQUIRED (по умолчанию):**\
Использует текущую транзакцию или создает новую, если текущая отсутствует.

**Propagation.REQUIRES_NEW:**\
Всегда создает новую транзакцию, приостанавливая текущую, если она существует.

**Propagation.SUPPORTS:**\
Использует текущую транзакцию, если она существует, в противном случае выполняется без транзакции.

**Propagation.NOT_SUPPORTED:**\
Выполняет метод вне транзакции, приостанавливая текущую транзакцию, если она существует.

**Propagation.MANDATORY:**\
Требует существования текущей транзакции, выбрасывает исключение, если ее нет.

**Propagation.NEVER:**\
Выполняется без транзакции, выбрасывает исключение, если текущая транзакция существует.

**Propagation.NESTED:**\
Создает вложенную транзакцию, если существует текущая транзакция.

```kotlin
@Transactional(propagation = Propagation.REQUIRED)
fun myTransactionalMethod() {

```

**isolation (тип: Isolation):**\
Определяет уровень изоляции транзакции.

**Значения:**

**Isolation.DEFAULT (по умолчанию):**\
Использует уровень изоляции базы данных.

**Isolation.READ_UNCOMMITTED:**\
Самый низкий уровень изоляции, позволяет “грязное чтение”.

**Isolation.READ_COMMITTED:**\
Гарантирует, что данные, считанные в транзакции, уже зафиксированы.

**Isolation.REPEATABLE_READ:**\
Гарантирует, что данные, считанные в транзакции, не изменятся до ее завершения.

**Isolation.SERIALIZABLE:**\
Самый высокий уровень изоляции, гарантирует полную изоляцию транзакций.

```kotlin
@Transactional(isolation = Isolation.REPEATABLE_READ)
fun myTransactionalMethod() {
    // Код будет выполнен в рамках транзакции с уровнем изоляции REPEATABLE_READ
}
```

**timeout (тип: Int):**\
Определяет максимальное время (в секундах), которое транзакция может выполняться до принудительного отката.\
Значение по умолчанию -1 означает отсутствие ограничения по времени.\
Если транзакция не завершится за указанное время, будет выброшено исключение TransactionTimedOutException.

```kotlin
@Transactional(timeout = 30)
fun myTransactionalMethod() {
    // Код должен быть выполнен в течение 30 секунд
}
```

**readOnly (тип: Boolean):**\
Указывает, что транзакция предназначена только для чтения. Значение по умолчанию false.\
Установка в true позволяет оптимизировать производительность, так как транзакция не будет содержать изменений.

```kotlin
@Transactional(readOnly = true)
fun myReadOnlyMethod() {
    // Код выполняется в режиме только для чтения
}
```

**rollbackFor (тип: Array`<Class<out Throwable>>`):**\
Указывает массив исключений, при возникновении которых должна произойти откат транзакции.

```kotlin
@Transactional(rollbackFor = [RuntimeException::class, IOException::class])
fun myMethod() {
    // Транзакция будет откатана, если будут выброшены RuntimeException или IOException
}
```

**rollbackForClassName (тип: Array`<String>`):**\
Указывает массив имен исключений (в виде строк), при возникновении которых должна произойти откат транзакции.

**noRollbackFor (тип: Array`<Class<out Throwable>>`):**\
Указывает массив исключений, при возникновении которых транзакция не должна откатываться.

```kotlin
@Transactional(noRollbackFor = [CustomException::class])
fun myMethod() {
    // Транзакция не будет откатана, если будет выброшен CustomException
}
```

**noRollbackForClassName (тип: Array`<String>`):**\
Указывает массив имен исключений (в виде строк), при возникновении которых транзакция не должна откатываться.

**transactionManager:**\
Указывает, какой транзакционный менеджер использовать, если их несколько

```kotlin
@Transactional(transactionManager = "customTransactionManager")
fun myMethod() {
    // Метод будет использовать customTransactionManager
}
```

**value:**\
Синоним для transactionManager. Можно использовать для указания транзакционного менеджера.

```kotlin
@Transactional("customTransactionManager")
fun myMethod() {
    // Метод будет использовать customTransactionManager
}
```

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

```kotlin
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Isolation
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional


@Service
class MyService {

    @Transactional(
        propagation = Propagation.REQUIRED,
        isolation = Isolation.REPEATABLE_READ,
        timeout = 30,
        readOnly = false,
        rollbackFor = [RuntimeException::class],
        noRollbackFor = [CustomException::class],
        transactionManager = "customTransactionManager"
    )
    fun myTransactionalMethod() {
    }
}
```

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

Для мониторинга транзакций в логах в Spring приложениях, необходимо настроить логирование соответствующих компонентов Spring и Hibernate (если используется JPA/Hibernate). Основное внимание следует уделить началу, коммиту, откату транзакций и потенциальным ошибкам.

```yaml
logging:
  level:
    org:
      springframework:
        transaction: DEBUG
      hibernate:
        SQL: DEBUG
        transaction: DEBUG
        type:
          descriptor:
            sql:
              BasicBinder: TRACE
```

Пример конфигурации логирования для Logback (logback-spring.xml):

```xml
<configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml"/>
    <logger name="org.springframework.transaction" level="DEBUG"/>
    <logger name="org.hibernate.SQL" level="DEBUG"/>
    <logger name="org.hibernate.transaction" level="DEBUG"/>
    <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>
```

**Что искать в логах:**

**Начало транзакции:**\
Сообщения, указывающие на начало новой транзакции.\
Например: “Creating new transaction with name \[…\]”

**Коммит транзакции:**\
Сообщения о фиксации транзакции.\
Например: “Initiating transaction commit for \[…\]”

**Откат транзакции:**\
Сообщения о выполнении отката транзакции.\
Например: “Initiating transaction rollback for \[…\]”

**Ошибки и исключения:**\
Сообщения об ошибках и исключениях, связанных с транзакциями.\
Например: “Rolling back transaction because of exception \[…\]”

**Инструменты для мониторинга:**

**Spring Boot Actuator:**\
Позволяет мониторить состояние вашего приложения, включая информацию о транзакциях.

**Application Performance Monitoring (APM) Tools:**\
Инструменты такие как New Relic, Dynatrace, или AppDynamics предоставляют детальную информацию о транзакциях, запросах, производительности и ошибках.

## Как использовать Redis в Spring {#Как-использовать-Redis-в-Spring}

Redis (Remote Dictionary Server) — это высокопроизводительный, in-memory key-value хранилище, которое поддерживает множество структур данных, таких как строки, списки, множества, хеши и сортированные множества. Redis часто используется для кэширования данных, управления сессиями, очередей сообщений, чатов, счетчиков и других сценариев, требующих высокой производительности и низкой задержки.

Интеграция Spring Boot с Redis может быть выполнена с использованием Spring Data Redis, который предоставляет удобный и мощный API для работы с Redis. Вот пошаговый пример, как настроить и использовать Redis в Spring Boot приложении.

```kotlin
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}
```

```properties
spring.redis.host=localhost
spring.redis.port=6379
```

```kotlin
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.data.redis.serializer.GenericToStringSerializer


@Configuration
class RedisConfig {

    @Bean
    fun redisTemplate(connectionFactory: RedisConnectionFactory): RedisTemplate<String, Any> {
        val template = RedisTemplate<String, Any>()
        template.setConnectionFactory(connectionFactory)
        template.keySerializer = GenericToStringSerializer(String::class.java)
        template.valueSerializer = GenericToStringSerializer(Any::class.java)
        return template
    }

}
```

```kotlin
import java.io.Serializable


data class Person(
    val id: String,
    val name: String,
    val age: Int
) : Serializable
```

```kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.stereotype.Service


@Service
class PersonService(@Autowired private val redisTemplate: RedisTemplate<String, Any>) {

    private val hashKey = "Person"

    fun save(person: Person) {
        redisTemplate.opsForHash<String, Person>().put(hashKey, person.id, person)
    }

    fun findById(id: String): Person? {
        return redisTemplate.opsForHash<String, Person>().get(hashKey, id)
    }

    fun findAll(): List<Person> {
        return redisTemplate.opsForHash<String, Person>().values(hashKey) as List<Person>
    }

    fun delete(id: String) {
        redisTemplate.opsForHash<String, Person>().delete(hashKey, id)
    }

}
```

```kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.*


@RestController
@RequestMapping("/persons")
class PersonController(@Autowired private val personService: PersonService) {

    @PostMapping
    fun savePerson(@RequestBody person: Person) {
        personService.save(person)
    }

    @GetMapping("/{id}")
    fun getPerson(@PathVariable id: String): Person? {
        return personService.findById(id)
    }

    @GetMapping
    fun getAllPersons(): List<Person> {
        return personService.findAll()
    }

    @DeleteMapping("/{id}")
    fun deletePerson(@PathVariable id: String) {
        personService.delete(id)
    }

}
```

## Как в Spring обрабатывать изменения в базе данных {#Как-в-Spring-обрабатывать-изменения-в-базе-данных}

В Spring-приложении вы можете использовать подходы для мониторинга изменений в базе данных и выполнения действий, таких как отправка запроса, при изменении значения в базе данных. Один из популярных способов достижения этого — использование триггеров в базе данных вместе с Spring Events или, если используется NoSQL база данных, таких как MongoDB, использование его функциональности Change Streams.

**Пример для реляционной базы данных (например, PostgreSQL) с использованием триггеров и слушателей:**

**Создание триггера в базе данных:**\
Создайте триггер в базе данных для отслеживания изменений в таблице.

**Пример для PostgreSQL:**\
Создайте функцию триггера

```sql
CREATE OR REPLACE FUNCTION notify_table_update()
RETURNS TRIGGER AS $$
BEGIN
    PERFORM pg_notify('table_update', NEW.id::text);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;
```

**Создайте триггер, который вызывает эту функцию при обновлении строки в таблице:**

```sql
CREATE TRIGGER table_update_trigger
AFTER UPDATE ON your_table
FOR EACH ROW
EXECUTE FUNCTION notify_table_update();
```

**Настройка слушателя в Spring Boot:**\
Используйте JDBC для прослушивания уведомлений от базы данных.

```properties
spring.datasource.url=jdbc:postgresql://localhost:5432/your_database
spring.datasource.username=your_username
spring.datasource.password=your_password
spring.datasource.driver-class-name=org.postgresql.Driver
```

```kotlin
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.stereotype.Service
import javax.annotation.PostConstruct


@Service
class NotificationListener(private val jdbcTemplate: JdbcTemplate) {

    @PostConstruct
    fun listenForNotifications() {
        Thread {
            try {
                jdbcTemplate.dataSource?.connection?.use { connection ->
                    connection.createStatement().use { statement ->
                        statement.execute("LISTEN table_update")
                    }
                    while (true) {
                        connection.unwrap(org.postgresql.PGConnection::class.java).getNotifications()?.let { notifications ->
                            for (notification in notifications) {
                                println("Received notification: ${notification.parameter}")
                                // Отправьте запрос при получении уведомления
                                sendRequest(notification.parameter)
                            }
                        }
                        Thread.sleep(1000) // Установите задержку между проверками
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }.start()
    }

    private fun sendRequest(id: String) {
        // Логика отправки запроса
        println("Sending request for id: $id")
        // Например, можно использовать RestTemplate или WebClient для отправки HTTP-запроса
    }

}
```

**Пример для MongoDB с использованием Change Streams:**\
Если вы используете MongoDB, вы можете использовать Change Streams для отслеживания изменений.

```properties
spring.data.mongodb.uri=mongodb://localhost:27017/your_database
```

```kotlin
import com.mongodb.client.MongoClients
import com.mongodb.client.model.changestream.ChangeStreamDocument
import org.bson.Document
import org.springframework.stereotype.Service
import javax.annotation.PostConstruct


@Service
class ChangeStreamListener {

    @PostConstruct
    fun listenForChanges() {
        Thread {
            try {
                MongoClients.create("mongodb://localhost:27017").use { mongoClient ->
                    val database = mongoClient.getDatabase("your_database")
                    val collection = database.getCollection("your_collection")
                    val changeStream = collection.watch()
                    for (change in changeStream) {
                        println("Received change: ${change.fullDocument}")
                        // Отправьте запрос при получении изменения
                        sendRequest(change.fullDocument)
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }.start()
    }

    private fun sendRequest(document: Document?) {
        // Логика отправки запроса
        println("Sending request for document: $document")
        // Например, можно использовать RestTemplate или WebClient для отправки HTTP-запроса
    }

}
```

## Какие есть репозитории в Spring {#Какие-есть-репозитории-в-Spring}

```text
Repository
    ├── CrudRepository
    │     ├── PagingAndSortingRepository
    │     │     ├── JpaRepository
    │     │     └── MongoRepository
    │     └── ReactiveCrudRepository
    │           └── R2dbcRepository
```

**CrudRepository:**\
предоставляет основные методы CRUD (создание, чтение, обновление, удаление) для работы с базой данных. Это базовый интерфейс для всех репозиториев в Spring Data.

```kotlin
import org.springframework.data.repository.CrudRepository


interface PersonRepository : CrudRepository<Person, Long>
```

**JpaRepository:**\
расширяет CrudRepository и PagingAndSortingRepository, предоставляя дополнительные методы для работы с JPA (Java Persistence API), такие как методы для пакетных операций и методы для работы с флашированием.

```kotlin
import org.springframework.data.jpa.repository.JpaRepository


interface PersonRepository : JpaRepository<Person, Long>
```

**PagingAndSortingRepository:**\
расширяет CrudRepository и предоставляет методы для выполнения операций с сортировкой и пагинацией (разбиение на страницы).

```kotlin
import org.springframework.data.repository.PagingAndSortingRepository


interface PersonRepository : PagingAndSortingRepository<Person, Long>
```

**MongoRepository:**\
используется для работы с MongoDB. Он расширяет PagingAndSortingRepository и предоставляет методы для работы с MongoDB.

```kotlin
import org.springframework.data.mongodb.repository.MongoRepository


interface PersonRepository : MongoRepository<Person, String>
```

**ReactiveCrudRepository:**\
используется для реактивного программирования с поддержкой Project Reactor. Он предоставляет асинхронные методы для CRUD операций.

```kotlin
import org.springframework.data.repository.reactive.ReactiveCrudRepository
import reactor.core.publisher.Mono


interface PersonRepository : ReactiveCrudRepository<Person, Long> {

    fun findByFirstName(firstName: String): Mono<Person>

}
```

**R2dbcRepository:**\
используется для работы с базами данных с использованием R2DBC (Reactive Relational Database Connectivity).

```kotlin
import org.springframework.data.r2dbc.repository.R2dbcRepository
import reactor.core.publisher.Mono


interface PersonRepository : R2dbcRepository<Person, Long> {

    fun findByLastName(lastName: String): Mono<Person>

}
```

## Что такое Jenkins {#Что-такое-Jenkins}

Jenkins — это популярный инструмент для непрерывной интеграции (Continuous Integration, CI) и непрерывного развертывания (Continuous Deployment, CD). Он используется для автоматизации различных этапов разработки программного обеспечения, таких как сборка, тестирование и развертывание приложений. Jenkins — это проект с открытым исходным кодом, который поддерживает множество плагинов для интеграции с различными инструментами и сервисами.

**Основные возможности Jenkins:**

**Автоматизация процессов:**\
Jenkins позволяет автоматизировать сборку, тестирование и развертывание приложений. Это помогает уменьшить ручной труд и снизить вероятность ошибок.

**Плагины:**\
Jenkins поддерживает более 1500 плагинов, которые позволяют интегрировать его с множеством инструментов и сервисов, таких как Git, Maven, Gradle, Docker, Kubernetes и многими другими.

**Поддержка различных языков и технологий:**\
Jenkins поддерживает множество языков программирования и технологий, что делает его универсальным инструментом для CI/CD.

**Масштабируемость:**\
Jenkins можно настроить для работы в распределенной среде с использованием агентов (nodes) для выполнения задач на различных машинах.

**Интеграция с системами контроля версий:**\
Jenkins может интегрироваться с различными системами контроля версий, такими как Git, Subversion, Mercurial и другими.

**Веб-интерфейс и API:**\
Jenkins предоставляет удобный веб-интерфейс для управления и мониторинга задач, а также RESTful API для интеграции с другими системами.

**Пример использования Jenkins для Spring Boot проекта:**

**Установка Jenkins:**\
Вы можете установить Jenkins с помощью Docker, скачав дистрибутив с официального сайта или используя пакетный менеджер вашей операционной системы.

```text
docker run -p 8080:8080 -p 50000:50000 jenkins/jenkins:lts
```

**Настройка Jenkins:**\
Откройте браузер и перейдите на http://localhost:8080.\
Введите пароль администратора, который можно найти в файле /var/jenkins_home/secrets/initialAdminPassword внутри контейнера.\
Следуйте инструкциям мастера установки для завершения настройки Jenkins.\
Шаг 2: Настройка Jenkins для сборки Spring Boot приложения

**Установка плагинов:**\
В Jenkins перейдите в Manage Jenkins -> Manage Plugins.\
Установите следующие плагины:\
Gradle Plugin\
Git Plugin

**Настройка Gradle:**\
Убедитесь, что Gradle установлен на вашем Jenkins-сервере. Если нет, установите его.\
В Jenkins перейдите в Manage Jenkins -> Global Tool Configuration.\
В разделе Gradle добавьте новую установку Gradle, указав имя и путь к Gradle (или выберите автоматическую установку).

**Создание нового задания (Job):**\
В Jenkins перейдите на главную страницу и нажмите New Item.\
Введите имя проекта и выберите Freestyle project.\
Нажмите OK.

**Настройка источника кода:**\
В разделе Source Code Management выберите Git.\
Укажите URL репозитория вашего проекта и учетные данные для доступа.

**Настройка сборки:**\
В разделе Build нажмите Add build step и выберите Invoke Gradle script.\
В поле Switches введите clean build.\
В поле Tasks введите build.

**Настройка триггеров сборки:**\
В разделе Build Triggers выберите способ, как Jenkins будет запускать сборку:\
Poll SCM: Для опроса репозитория на изменения.\
Build periodically: Для запуска сборок по расписанию.\
GitHub hook trigger for GITScm polling: Для автоматического запуска сборки при пуше в репозиторий.

**Настройка пост-сборочных действий:**\
Вы можете добавить пост-сборочные действия, такие как отправка уведомлений или развертывание приложения.

## Что такое Nexus {#Что-такое-Nexus}

Nexus — это репозиторий менеджер, используемый для хранения, управления и распространения артефактов, таких как библиотеки, пакеты и другие компоненты, используемые в процессе разработки программного обеспечения. Он поддерживает множество форматов, включая Maven, npm, NuGet, Docker, и многие другие.

**Основные возможности Nexus:**

**Хранение артефактов:**\
Nexus предоставляет централизованное место для хранения всех артефактов, используемых в процессе разработки, таких как библиотеки, плагины и другие зависимости.

**Управление зависимостями:**\
Nexus помогает управлять зависимостями проектов, обеспечивая доступ к необходимым артефактам и их версиям.

**Кэширование удаленных репозиториев:**\
Nexus может кэшировать артефакты из удаленных репозиториев, что помогает уменьшить задержки и повысить производительность.

**Поддержка множества форматов:**\
Nexus поддерживает различные форматы репозиториев, такие как Maven, npm, NuGet, Docker и другие.

**Управление доступом:**\
Nexus предоставляет гибкую систему управления доступом, позволяя контролировать, кто может загружать и скачивать артефакты.

**Интеграция с CI/CD:**\
Nexus легко интегрируется с инструментами для непрерывной интеграции и развертывания (CI/CD), такими как Jenkins, для автоматизации процессов сборки и развертывания.

**Вы можете установить Nexus с использованием Docker:**

```text
docker run -d -p 8081:8081 --name nexus sonatype/nexus3
```

После запуска контейнера Nexus, откройте браузер и перейдите на http://localhost:8081. Используйте следующие данные для входа по умолчанию:\
Логин: admin\
Пароль: вы можете найти пароль в файле admin.password, расположенном в директории /nexus-data/admin.password внутри контейнера.

## Что такое План запроса (Query Plan) {#Что-такое-План-запроса-Query-Plan}

План запроса (Query Plan) — это набор инструкций, созданных системой управления базами данных (СУБД), которые описывают, как СУБД выполнит SQL-запрос. План запроса содержит информацию о порядке выполнения операций, используемых индексов, методах доступа к данным и других аспектах, которые влияют на производительность запроса.

**Основные компоненты плана запроса:**

**Последовательность операций:**\
Операции могут включать сканирование таблицы, использование индексов, соединения таблиц, сортировку и агрегирование данных.

**Используемые индексы:**\
План запроса указывает, какие индексы используются для ускорения доступа к данным.

**Методы доступа к данным:**\
Методы могут включать полное сканирование таблицы, индексированное сканирование, поиск по первичному ключу и т. д.

**Порядок соединения таблиц:**\
План запроса описывает порядок, в котором таблицы соединяются, и методы соединения (например, вложенные циклы, хеш-соединение, слияние).

В PostgreSQL можно использовать команду EXPLAIN, чтобы получить план запроса:

```sql
EXPLAIN SELECT * FROM users WHERE id = 1;
```

**Пример плана запроса:**

```text
Seq Scan on users
(cost=0.00..35.50 rows=1 width=64)
Filter: (id = 1)
```

**Seq Scan:**\
Указывает на последовательное сканирование таблицы users.

**cost=0.00..35.50:**\
Оценка стоимости выполнения запроса. Первое значение (0.00) — это стоимость старта, а второе (35.50) — полная стоимость выполнения запроса.

**rows=1:**\
Ожидаемое количество строк, возвращаемых запросом.

**width=64:**\
Ожидаемый средний размер строки в байтах.

**Filter:**\
Указывает на условие фильтрации (id = 1).

**Оптимизация запросов с использованием плана запроса:**\
Анализ плана запроса позволяет выявить узкие места и принять меры для оптимизации запросов. Вот несколько примеров оптимизаций:

**Использование индексов:**\
Создание индексов на столбцах, используемых в условиях WHERE, JOIN и ORDER BY.

**Избегание полного сканирования таблицы:**\
Использование индексированных сканирований вместо полного сканирования таблицы для улучшения производительности.

**Оптимизация соединений таблиц:**\
Выбор подходящего метода соединения таблиц (например, хеш-соединение, слияние) в зависимости от объема данных.\\

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

**Создание индекса:**

```sql
CREATE INDEX idx_users_id ON users (id);
```

**Получение плана запроса после создания индекса:**

```sql
EXPLAIN SELECT * FROM users WHERE id = 1;
```

**Пример плана запроса после создания индекса:**

```text
Index Scan using idx_users_id on users
(cost=0.28..8.30 rows=1 width=64)
Index Cond: (id = 1)
```

**Index Scan:**\
Указывает на использование индексированного сканирования по индексу idx_users_id.

**cost=0.28..8.30:**\
Оценка стоимости выполнения запроса, которая значительно уменьшилась по сравнению с последовательным сканированием таблицы.

**Index Cond:**\
Указывает на условие использования индекса (id = 1).

## Как использовать индексы в базе данных Spring {#Как-использовать-индексы-в-базе-данных-Spring}

Пример создания индекса в Spring Data JPA

```kotlin
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'com.h2database:h2'
    implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
    implementation 'org.jetbrains.kotlin:kotlin-reflect'
    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
```

```properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
```

Используйте аннотацию @Table и @Index для создания индексов на уровне сущности.

```kotlin
import javax.persistence.*


@Entity
@Table(name = "users", indexes = [Index(name = "idx_user_email", columnList = "email")])
data class User(

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,

    @Column(nullable = false)
    val name: String,

    @Column(nullable = false, unique = true)
    val email: String

)
```

**Пример SQL-запроса для проверки индекса:**

```sql
SHOW INDEX FROM users;
```

## Какие могут быть аномалии при работе с базой данных {#Какие-могут-быть-аномалии-при-работе-с-базой-данных}

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

**Основные типы аномалий включают:**\
Dirty Read\
Non-Repeatable Read\
Phantom Read\
Lost Update\
Double Update Anomaly

**Грязное чтение (Dirty Read):**\
Возникает, когда одна транзакция читает данные, которые были изменены другой транзакцией, но ещё не зафиксированы (не завершены). Если вторая транзакция откатывается, первая транзакция содержит неверные данные.

Пример:**\**
Транзакция A обновляет значение столбца, но не фиксирует изменения.\
Транзакция B читает обновленное значение.\
Транзакция A откатывается.\
Транзакция B остается с некорректными данными.

**Неповторяемое чтение (Non-repeatable Read):**\
Случается, когда одна транзакция дважды читает одну и ту же запись, а другая транзакция изменяет или удаляет эту запись между этими двумя чтениями. В результате, первое и второе чтение одной и той же записи возвращают разные результаты.

**Пример:**\
Транзакция A читает значение столбца.\
Транзакция B обновляет значение столбца и фиксирует изменения.\
Транзакция A снова читает значение столбца и видит другое значение.

**Фантомное чтение (Phantom Read):**\
Происходит, когда одна транзакция дважды выполняет одно и то же запрос для выбора набора строк, а другая транзакция между этими запросами добавляет или удаляет строки, соответствующие критериям запроса. В результате, первый и второй запросы возвращают разные наборы строк.

**Пример:**\
Транзакция A выполняет запрос, возвращающий набор строк.\
Транзакция B вставляет новую строку, которая соответствует условиям запроса транзакции A, и фиксирует изменения.\
Транзакция A снова выполняет тот же запрос и видит дополнительную строку.

**Потерянное обновление (Lost Update):**\
Происходит, когда две транзакции одновременно читают одну и ту же запись и затем обновляют её. Последнее обновление перезаписывает предыдущее, и изменения первой транзакции теряются.

**Пример:**\
Транзакция A читает значение столбца.\
Транзакция B читает то же значение столбца.\
Транзакция A обновляет значение и фиксирует изменения.\
Транзакция B обновляет значение и фиксирует изменения, перезаписывая изменения, сделанные транзакцией A.

**Двойное обновление (Double Update Anomaly):**\
Эта аномалия происходит, когда одно и то же обновление применяется дважды к одной и той же строке из-за повторного выполнения транзакции.

**Пример:**\
Транзакция A увеличивает значение столбца на 1.\
Транзакция A завершается с ошибкой и автоматически повторяется.\
Значение столбца увеличивается на 2 вместо 1.

**Необоснованное зависание (Uncommitted Dependency):**\
Возникает, когда одна транзакция зависает из-за того, что ожидает завершения другой транзакции, которая держит блокировку на необходимом ресурсе. Это может привести к снижению производительности и ухудшению отклика системы.

**Перекрёстные обновления (Write Skew):**\
Происходит, когда две транзакции одновременно изменяют различные, но связанные между собой данные, что приводит к неконсистентному состоянию системы. Например, если две транзакции проверяют условия и на их основе обновляют данные, возможно нарушение согласованности, если условия проверяются до обновления данных.

**Управление аномалиями с помощью уровней изоляции:**\
Различные уровни изоляции транзакций определены стандартом SQL для управления этими аномалиями

**Isolation.READ UNCOMMITTED:**\
Наименьший уровень изоляции. Позволяет грязные чтения, неповторяющиеся чтения и фантомные чтения.

**Isolation.READ COMMITTED:**\
Запрещает грязные чтения. Позволяет неповторяющиеся чтения и фантомные чтения.

**Isolation.REPEATABLE READ:**\
Запрещает грязные чтения и неповторяющиеся чтения. Позволяет фантомные чтения.

**Isolation.SERIALIZABLE:**\
Наивысший уровень изоляции. Запрещает грязные чтения, неповторяющиеся чтения и фантомные чтения.

**Пример настройки уровня изоляции в Spring:**

```kotlin
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Isolation
import org.springframework.transaction.annotation.Transactional


@Service
class MyService {

    @Transactional(isolation = Isolation.SERIALIZABLE)
    fun performTransactionalOperation() {
    }

}
```

## Какие есть виды блокировки в базе данных {#Какие-есть-виды-блокировки-в-базе-данных}

**По модальности блокировки:**

**Shared Locks (Разделяемые блокировки):**\
позволяет нескольким транзакциям одновременно читать данные. Однако ни одна из этих транзакций не может изменять данные, пока держится разделяемая блокировка.

**Пример:**\
Транзакция A берет Shared Lock на строку.\
Транзакция B может также взять Shared Lock на ту же строку и читать данные.\
Транзакция C не может взять Exclusive Lock на эту строку, пока держатся Shared Locks транзакций A и B.

```sql
-- Транзакция A берет разделяемую блокировку
BEGIN TRANSACTION;
SELECT * FROM MyTable WITH (HOLDLOCK, ROWLOCK);
```

```kotlin
import org.springframework.data.jpa.repository.Lock
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.CrudRepository
import javax.persistence.LockModeType


interface MyRepository : CrudRepository<MyEntity, Long> {

    @Lock(LockModeType.PESSIMISTIC_READ)
    @Query("SELECT e FROM MyEntity e WHERE e.id = :id")
    fun findWithSharedLock(id: Long): MyEntity?

}
```

**Exclusive Locks (Исключительные блокировки):**\
позволяет транзакции как читать, так и изменять данные. Только одна транзакция может иметь исключительную блокировку на ресурсе, и никакая другая транзакция не может взять разделяемую или исключительную блокировку на этот ресурс, пока держится исключительная блокировка.

**Пример:**\
Транзакция A берет Exclusive Lock на строку.\
Транзакция B не может взять Shared Lock или Exclusive Lock на эту строку, пока держится Exclusive Lock транзакции A.

```sql
-- Транзакция B берет исключительную блокировку
BEGIN TRANSACTION;
UPDATE MyTable WITH (ROWLOCK) SET Column = 'Value';
```

```kotlin
import org.springframework.data.jpa.repository.Lock
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.CrudRepository
import javax.persistence.LockModeType


interface MyRepository : CrudRepository<MyEntity, Long> {

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT e FROM MyEntity e WHERE e.id = :id")
    fun findWithExclusiveLock(id: Long): MyEntity?

}
```

**Пример использования блокировок в транзакциях Spring:**

```kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import javax.persistence.EntityManager
import javax.persistence.LockModeType


@Service
class MyService @Autowired constructor(
    private val myRepository: MyRepository,
    private val entityManager: EntityManager
) {

    @Transactional
    fun performSharedLockOperation(id: Long) {
        val entity = myRepository.findWithSharedLock(id)
        // Ваш код здесь
    }

    @Transactional
    fun performExclusiveLockOperation(id: Long) {
        val entity = myRepository.findWithExclusiveLock(id)
        // Ваш код здесь
    }

    @Transactional
    fun performExclusiveLockOperationWithEntityManager(id: Long) {
        val entity = entityManager.find(MyEntity::class.java, id, LockModeType.PESSIMISTIC_WRITE)
        // Ваш код здесь
    }

}
```

**По уровню затрагиваемых данных:**

**Блокировка строки (Row Lock):**\
Блокирует отдельную строку в таблице. Это позволяет нескольким пользователям работать с разными строками одной и той же таблицы одновременно.

**Блокировка страницы (Page Lock):**\
Блокирует страницу данных, содержащую несколько строк. Эффективна при массовых операциях с данными, но может приводить к большему количеству конфликтов по сравнению с блокировкой строк.

**Блокировка таблицы (Table Lock):**\
Блокирует всю таблицу. Это предотвращает любые другие операции с этой таблицей, пока блокировка активна. Используется, когда нужно выполнить операции, затрагивающие большую часть или всю таблицу.

**По способу управления:**

**Оптимистическая блокировка:**\
Подход, при котором система предполагает, что конфликты маловероятны, и не устанавливает блокировку до момента фиксации изменений. Вместо этого при попытке сохранения изменений проверяется, не были ли изменены данные с момента их последнего чтения.

**Пессимистическая блокировка:**\
Подход, при котором система предполагает, что конфликты вероятны, и устанавливает блокировки на данные во время чтения, удерживая их до окончания транзакции. Это минимизирует риск конфликтов, но может привести к уменьшению производительности и возникновению взаимных блокировок.

**Также есть:**

**Intent Lock:**\
Используется для указания намерений по установке блокировок на более низких уровнях. Например, если транзакция собирается блокировать строки внутри страницы, она сначала установит блокировку намерений на уровне страницы.

**Deadlock:**\
Взаимная блокировка возникает, когда две или более транзакций ожидают, пока другая освободит ресурс, который им нужен. СУБД обычно обнаруживают взаимные блокировки и автоматически прерывают одну из транзакций для разрешения конфликта.

## Какие есть способы управления транзакциями в Spring {#Какие-есть-способы-управления-транзакциями-в-Spring}

В Spring Framework управление транзакциями может быть реализовано несколькими способами. Основные способы включают декларативное и программное управление транзакциями.

**Декларативное управление транзакциями:**

**@Transactional:**\
Используется для указания методов или классов, которые должны выполняться в рамках транзакции.\
Можно настроить параметры, такие как уровень изоляции, распространение, тайм-аут и т.д.

```kotlin
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional


@Service
class MyService {

    @Transactional
    fun executeTransactionalMethod() {
        // Здесь код будет выполнен в рамках транзакции
    }

}
```

**XML-конфигурация:**\
Возможна конфигурация транзакционного менеджера и аспектов в XML.

```xml
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<tx:advice id="txAdvice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<aop:config>
    <aop:pointcut id="serviceOperation" expression="execution(* com.example.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation"/>
</aop:config>
```

**Программное управление транзакциями:**\
Программное управление транзакциями подразумевает явное использование PlatformTransactionManager для управления транзакциями в коде.

```kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.TransactionDefinition
import org.springframework.transaction.TransactionStatus
import org.springframework.transaction.support.DefaultTransactionDefinition


@Service
class MyTransactionalService(@Autowired private val transactionManager: PlatformTransactionManager) {

    fun executeTransactionalOperation() {
        val transactionDefinition = DefaultTransactionDefinition()
        transactionDefinition.name = "exampleTransaction"
        transactionDefinition.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED
        val transactionStatus: TransactionStatus = transactionManager.getTransaction(transactionDefinition)
        try {
            // Выполнение бизнес-логики
            // Например, вставка данных в базу данных
            transactionManager.commit(transactionStatus)
        } catch (ex: Exception) {
            transactionManager.rollback(transactionStatus)
            throw ex
        }
    }

}
```

**Способы управления транзакциями в зависимости от контекста:**

**JDBC-транзакции:**\
Используется DataSourceTransactionManager.\
Подходит для управления транзакциями на уровне JDBC.

```kotlin
@Bean
fun transactionManager(dataSource: DataSource): PlatformTransactionManager {
    return DataSourceTransactionManager(dataSource)
}
```

**JPA-транзакции:**\
Используется JpaTransactionManager.\
Подходит для управления транзакциями на уровне JPA.

```kotlin
@Bean
fun transactionManager(entityManagerFactory: EntityManagerFactory): PlatformTransactionManager {
    return JpaTransactionManager(entityManagerFactory)
}
```

**Hibernate-транзакции:**\
Используется HibernateTransactionManager.\
Подходит для управления транзакциями на уровне Hibernate.

```kotlin
@Bean
fun transactionManager(sessionFactory: SessionFactory): PlatformTransactionManager {
    return HibernateTransactionManager(sessionFactory)
}
```

**JTA-транзакции:**\
Используется JtaTransactionManager.\
Подходит для управления распределенными транзакциями в корпоративных приложениях.

```kotlin
@Bean
fun transactionManager(): PlatformTransactionManager {
    return JtaTransactionManager()
}
```

**TransactionOperations и TransactionTemplate:**\
Обеспечивает программное управление транзакциями с упрощенным синтаксисом.\
TransactionOperations — это интерфейс в Spring, который определяет основные операции для работы с транзакциями. TransactionTemplate — это конкретная реализация этого интерфейса.

**Основные методы TransactionOperations:**

**`<T>` execute(TransactionCallback`<T>` action):**\
Выполняет транзакцию и возвращает результат.

**`<T>` executeWithoutResult(TransactionCallbackWithoutResult action):** \
Выполняет транзакцию без возвращаемого результата.

**TransactionStatus:**\
предоставляет методы для управления текущим состоянием транзакции. Этот объект передается в метод транзакционного колбэка и может быть использован для контроля над ходом транзакции, включая установку точки сохранения (savepoint), откат транзакции и проверку её состояния.

**Основные методы TransactionStatus:**

**setRollbackOnly():**\
Помечает текущую транзакцию для отката. После вызова этого метода транзакция будет откатана при завершении, даже если метод завершится успешно.

**isNewTransaction():**\
Возвращает true, если текущий статус транзакции представляет собой новую транзакцию, а не существующую.

**hasSavepoint():**\
Возвращает true, если в текущей транзакции установлен какой-либо savepoint.

**createSavepoint():**\
Создает новый savepoint в текущей транзакции. Savepoint позволяет откатить часть транзакции без отката всей транзакции.

**releaseSavepoint(Object savepoint):**\
Освобождает (удаляет) указанный savepoint.

**rollbackToSavepoint(Object savepoint):**\
Откатывает транзакцию до указанного savepoint.

```kotlin
import org.springframework.stereotype.Service
import org.springframework.transaction.support.TransactionCallback
import org.springframework.transaction.support.TransactionCallbackWithoutResult
import org.springframework.transaction.support.TransactionTemplate
import org.springframework.transaction.TransactionStatus


@Service
class MyService(private val transactionTemplate: TransactionTemplate) {

    fun executeInTransaction() {
        transactionTemplate.execute(object : TransactionCallbackWithoutResult() {
            override fun doInTransactionWithoutResult(status: TransactionStatus) {
                println("Executing in transaction")
                // Если нужно, можно вызвать status.setRollbackOnly() для отката транзакции
                status.rollbackToSavepoint(savepoint)
            }
        })
    }

    fun executeInTransactionWithResult(): String {
        return transactionTemplate.execute(TransactionCallback { status ->
            println("Executing in transaction with result")
            "Transaction Result"
        }) ?: "Default Result"
    }
}
```

**TransactionSynchronizationManager:**\
Предоставляет низкоуровневый доступ для управления синхронизацией транзакций.

```kotlin
import org.springframework.transaction.support.TransactionSynchronizationManager


class MyService {

    fun someMethod() {
        if (TransactionSynchronizationManager.isActualTransactionActive()) {
            // Текущая транзакция активна
        }
    }

}
```

## Что такое R2DBC (Reactive Relational Database Connectivity) {#Что-такое-R2DBC-Reactive-Relational-Database-Connectivity}

Для работы с базой данных в реактивном стиле в WebFlux вы можете использовать проект Spring Data R2DBC (Reactive Relational Database Connectivity). Этот проект позволяет работать с реляционными базами данных в реактивном стиле.

```kotlin
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'
    implementation 'io.r2dbc:r2dbc-postgresql'
}
```

```yaml
spring:
  r2dbc:
    url: r2dbc:postgresql://localhost:5432/mydatabase
    username: myuser
    password: mypassword
```

```kotlin
import org.springframework.data.annotation.Id
import org.springframework.data.relational.core.mapping.Table


@Table("users")
data class User(
    @Id val id: Long? = null,
    val name: String,
    val email: String
)
```

```kotlin
import org.springframework.data.repository.reactive.ReactiveCrudRepository
import reactor.core.publisher.Mono


interface UserRepository : ReactiveCrudRepository<User, Long> {

    fun findByEmail(email: String): Mono<User>

}
```

```kotlin
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono


@Service
class UserService(private val userRepository: UserRepository) {
    fun getAllUsers(): Flux<User> = userRepository.findAll()

    fun getUserById(id: Long): Mono<User> = userRepository.findById(id)

    fun createUser(user: User): Mono<User> = userRepository.save(user)

    fun updateUser(id: Long, user: User): Mono<User> = userRepository.findById(id)
            .flatMap {
                val updatedUser = it.copy(name = user.name, email = user.email)
                userRepository.save(updatedUser)
            }

    fun deleteUser(id: Long): Mono<Void> = userRepository.deleteById(id)
}
```

```kotlin
import org.springframework.web.bind.annotation.*
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono


@RestController
@RequestMapping("/users")
class UserController(private val userService: UserService) {

    @GetMapping
    fun getAllUsers(): Flux<User> = userService.getAllUsers()

    @GetMapping("/{id}")
    fun getUserById(@PathVariable id: Long): Mono<User> = userService.getUserById(id)

    @PostMapping
    fun createUser(@RequestBody user: User): Mono<User> = userService.createUser(user)

    @PutMapping("/{id}")
    fun updateUser(@PathVariable id: Long, @RequestBody user: User): Mono<User> = userService.updateUser(id, user)

    @DeleteMapping("/{id}")
    fun deleteUser(@PathVariable id: Long): Mono<Void> = userService.deleteUser(id)

}
```

**Асинхронные библиотеки:**\
Убедитесь, что используете асинхронные драйверы для вашей базы данных. В случае PostgreSQL это r2dbc-postgresql.

**Обработка ошибок:**\
Важно учитывать обработку ошибок и управление транзакциями в реактивных приложениях.

## Что такое Flyway, миграции {#Что-такое-Flyway-миграции}

Ппопулярный инструмент для управления версионностью базы данных. Он позволяет организовать миграции базы данных, то есть последовательность изменений (например, создание таблиц, изменение схемы, наполнение данными и т.д.), и автоматизировать их применение.

**Основные возможности Flyway:**

**Версионность миграций:**\
Каждая миграция идентифицируется уникальным номером версии, что позволяет отслеживать изменения в базе данных и гарантировать, что они применяются в правильном порядке.

**Поддержка различных баз данных:**\
Flyway поддерживает множество баз данных, таких как PostgreSQL, MySQL, Oracle, SQL Server и многие другие.

**Интеграция с различными инструментами:**\
Flyway легко интегрируется с популярными сборщиками проектов и фреймворками, такими как Maven, Gradle, Spring Boot и др.

**Автоматическое обнаружение и выполнение миграций:**\
Flyway автоматически обнаруживает новые миграции и применяет их к базе данных.

**Поддержка различных типов миграций:**\
Flyway поддерживает SQL и Java миграции, что позволяет гибко управлять изменениями в базе данных.

```kotlin
dependencies {
    implementation 'org.flywaydb:flyway-core'
}
```

```yaml
logging:
  level:
    org.springframework.data.r2dbc: DEBUG
spring:
  r2dbc:
    url: r2dbc:postgresql://localhost:5432/database
    username: user
    password: 123
  flyway:
    url: jdbc:postgresql://localhost:5432/database
    user: user
    password: 123
    locations: classpath:db/migration
jwt:
  secret: very-very-secret-key-should-be-almost-infinity
  expiration: 86400
```

где

```yaml
logging:
  level:
    org.springframework.data.r2dbc: DEBUG
```

устанавливает уровень логирования для пакета org.springframework.data.r2dbc на уровень DEBUG. Это значит, что будут выводиться детализированные сообщения о выполнении операций с базой данных через R2DBC.

```yaml
spring:
  r2dbc:
    url: r2dbc:postgresql://localhost:5432/database
    username: user
    password: 123
```

**Параметры конфигурируют подключение к базе данных PostgreSQL с использованием R2DBC (Reactive Relational Database Connectivity):**

**spring.r2dbc.url:**\
URL для подключения к базе данных через R2DBC.

**spring.r2dbc.username:**\
Имя пользователя для подключения к базе данных.

**spring.r2dbc.password:**\
Пароль для подключения к базе данных.

```yaml
flyway:
    url: jdbc:postgresql://localhost:5432/database
    user: user
    password: 123
    locations: classpath:db/migration
```

**Параметры конфигурируют Flyway для управления миграциями базы данных:**

**spring.flyway.url:**\
URL для подключения к базе данных через JDBC.

**spring.flyway.user:**\
Имя пользователя для подключения к базе данных.

**spring.flyway.password:**\
Пароль для подключения к базе данных.

Важно отметить, что Flyway использует JDBC для выполнения миграций, даже если само приложение использует R2DBC для работы с базой данных.

```yaml
jwt:
  secret: very-very-secret-key-should-be-almost-infinity
  expiration: 86400
```

**Параметры конфигурируют JWT для аутентификации и авторизации:**

**jwt.secret:**\
Секретный ключ для подписи JWT. Этот ключ должен быть надежным и секретным, чтобы обеспечить безопасность токенов.

**jwt.expiration:**\
Время жизни токена в секундах. В данном случае, токен будет действителен в течение 86400 секунд (24 часа).

Миграции должны располагаться в директории src/main/resources/db/migration и следовать определенному формату именования: V__.sql

**Пример миграции V1__Create_user_table.sql:**

```sql
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE
);
```

Flyway автоматически выполнит миграции при запуске приложения Spring Boot. Если в базе данных будут обнаружены новые миграции, они будут применены.

## Где в Spring приложении следует хранить приватные данные {#Где-в-Spring-приложении-следует-хранить-приватные-данные}

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

**Spring Boot Configuration Properties:**\
Используйте файл application.properties или application.yml, но не храните в них сами ключи, а лишь указывайте пути к ним.

**Environment Variables:**\
Окружающие переменные (environment variables) являются безопасным способом хранения конфиденциальной информации.

```kotlin
@Value("\${my.secret.key}")
private lateinit var mySecretKey: String
```

**External Configuration Server:**\
Используйте Spring Cloud Config Server, который позволяет централизованно управлять конфигурацией и безопасно хранить ключи. Данные могут быть зашифрованы и защищены.

**Vault:**\
HashiCorp Vault — это мощное решение для хранения секретов. Spring Cloud Vault позволяет интегрировать Vault с вашим приложением.

```yaml
spring:
  cloud:
    vault:
      uri: http://127.0.0.1:8200
      token: s.xxxxxxxx
      kv:
        enabled: true
        backend: secret
        default-context: application
```

**Java Keystore (JKS):**\
Хранение ключей в JKS является хорошей практикой. Пример загрузки ключа из хранилища JKS:

```kotlin
import java.io.FileInputStream
import java.security.KeyStore


val keyStore = KeyStore.getInstance("JKS")
val fis = FileInputStream("keystore.jks")
keyStore.load(fis, "keystore-password".toCharArray())
val privateKey = keyStore.getKey("alias", "key-password".toCharArray()) as PrivateKey
```

**AWS Secrets Manager / Azure Key Vault / Google Secret Manager:**\
Если ваше приложение разворачивается в облаке, используйте облачные сервисы для хранения секретов.

```kotlin
import com.amazonaws.services.secretsmanager.AWSSecretsManager
import com.amazonaws.services.secretsmanager.model.GetSecretValueRequest


@Autowired
private lateinit var awsSecretsManager: AWSSecretsManager

fun getSecret(): String {
    val getSecretValueRequest = GetSecretValueRequest().withSecretId("mySecretId")
    val getSecretValueResult = awsSecretsManager.getSecretValue(getSecretValueRequest)
    return getSecretValueResult.secretString
}
```

**Шифрование данных:**\
Независимо от места хранения, данные должны быть зашифрованы. Используйте библиотеки, такие как Jasypt, для шифрования данных.

```kotlin
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration


@Configuration
class JasyptConfig {

    @Bean
    fun stringEncryptor(): PooledPBEStringEncryptor {
        val encryptor = PooledPBEStringEncryptor()
        val config = SimpleStringPBEConfig().apply {
            password = "encryption-password"
            algorithm = "PBEWithMD5AndDES"
        }
        encryptor.setConfig(config)
        return encryptor
    }

}
```

## Как в Spring можно использовать кеширование {#Как-в-Spring-можно-использовать-кеширование}

**Конфигурация кеша:**\
Включение кеширования в конфигурационном классе с помощью аннотации @EnableCaching.\
Настройка кеша в ресурсных файлах (например, application.yml).

**Использование аннотаций кеширования:**\
Аннотации, такие как @Cacheable, @CachePut, @CacheEvict, и @Caching, указывают, какие методы должны быть кешированы, обновлены или очищены в кеш-памяти.

**Обработка аннотаций:**\
Spring обрабатывает эти аннотации с помощью прокси (Proxy) или аспектов (Aspects), добавляя дополнительный код для работы с кешем перед вызовом методов.

**Работа с кешем:**\
При первом вызове метода, помеченного аннотацией @Cacheable, выполняется исходный метод и результат сохраняется в кеш.\
При последующих вызовах того же метода с теми же параметрами результат извлекается из кеша, минуя выполнение исходного метода.

**Обновление и удаление данных в кеше:**\
Аннотация @CachePut обновляет данные в кеше после выполнения метода.\
Аннотация @CacheEvict удаляет данные из кеша, чтобы гарантировать, что устаревшие данные не будут возвращены.

**Пример работы кеша:**\
Рассмотрим работу кеширования на примере метода getUserById

**Первый вызов метода getUserById(1):**\
Метод выполняется.\
Результат (User(id=1, name=”User1")) сохраняется в кеш с ключом

**Последующий вызов метода getUserById(1):**\
Spring проверяет наличие результата в кеше.\
Если результат есть, он возвращается из кеша, и метод не выполняется повторно.

**Обновление данных с помощью updateUser(User(1, “UpdatedUser1”)):**\
Метод выполняется.\
Кеш обновляется новым значением (User(id=1, name=”UpdatedUser1")) с ключом 1.

**Удаление данных из кеша с помощью deleteUser(1):**\
Метод выполняется.\
Запись с ключом 1 удаляется из кеша.

```kotlin
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-cache'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.jetbrains.kotlin:kotlin-reflect'
    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.6.1'
    runtimeOnly 'com.h2database:h2'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
```

```kotlin
import org.springframework.cache.annotation.EnableCaching
import org.springframework.context.annotation.Configuration


@Configuration
@EnableCaching
class CacheConfig
```

**@Cacheable:**\
Кеширует результат метода.

**@CachePut:**\
Обновляет кеш после выполнения метода.

**@CacheEvict:**\
Удаляет записи из кеша.

```kotlin
@Service
class UserService {

    @Cacheable("users", key = "#id")
    fun getUserById(id: Long): User {
        // Симуляция долгого запроса
        Thread.sleep(3000)
        return User(id, "User$id")
    }

    @CachePut(value = ["users"], key = "#user.id")
    fun updateUser(user: User): User {
        // Логика обновления пользователя
        return user
    }

    @CacheEvict(value = ["users"], key = "#id")
    fun deleteUser(id: Long) {
        // Логика удаления пользователя
    }

    @Caching(
        put = [CachePut(value = ["users"], key = "#user.id")],
        evict = [CacheEvict(value = ["users_list"], allEntries = true)]
    )
    fun saveUser(user: User): User {
        // Логика сохранения пользователя
        return user
    }
}
```

**Возможные конфигурации кеша:**

**Simple (ConcurrentMapCache):**\
Это тип кеша по умолчанию, который использует ConcurrentHashMap для хранения кеша в памяти.

**JCache (JSR-107):**\
Это стандарт Java для кеширования, который позволяет интегрироваться с различными провайдерами кеша, такими как Ehcache, Hazelcast, Infinispan и другими.

**Ehcache:**\
Популярный и мощный кеш-провайдер, поддерживающий расширенные функции управления кешем.

**Hazelcast:**\
Кластерный распределенный кеш, который также может быть использован в качестве сетевого игрового сервера.

**Infinispan:**\
Масштабируемый и распределенный кеш-провайдер, который может работать как в памяти, так и на диске.

**Caffeine:**\
Высокопроизводительный кеш для Java, который поддерживает конфигурируемую политику истечения срока действия и другие функции.

**Redis:**\
Распределенное хранилище данных, работающее в памяти, которое можно использовать для кеширования и обмена сообщениями.

**Guava:**\
Библиотека от Google, которая предоставляет базовый механизм кеширования.

**Simple Cache (ConcurrentMapCache)** application.yml:

```yaml
spring:
  cache:
    type: simple
```

**JCache (JSR-107)** application.yml:

```yaml
spring:
  cache:
    type: jcache
```

**Ehcache** application.yml:

```yaml
spring:
  cache:
    type: ehcache
```

**Ehcache** ehcache.xml:

```xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd">
    <cache name="users" maxEntriesLocalHeap="1000" timeToLiveSeconds="3600"/>
</ehcache>
```

**Hazelcast** application.yml:

```yaml
spring:
  cache:
    type: hazelcast
```

**Hazelcast** hazelcast.xml:

```xml
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.hazelcast.com/schema/config
           http://www.hazelcast.com/schema/config/hazelcast-config-3.10.xsd">
    <map name="users">
        <time-to-live-seconds>3600</time-to-live-seconds>
    </map>
</hazelcast>
```

**Infinispan** application.yml:

```yaml
spring:
  cache:
    type: infinispan
```

**Infinispan** infinispan.xml:

```xml
<infinispan>
    <cache-container>
        <local-cache name="users">
            <expiration lifespan="3600000"/>
        </local-cache>
    </cache-container>
</infinispan>
```

**Caffeine** application.yml:

```yaml
spring:
  cache:
    type: caffeine
```

```kotlin
import com.github.benmanes.caffeine.cache.Caffeine
import org.springframework.cache.CacheManager
import org.springframework.cache.caffeine.CaffeineCacheManager
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.concurrent.TimeUnit


@Configuration
class CacheConfig {

    @Bean
    fun cacheManager(): CacheManager {
        val cacheManager = CaffeineCacheManager("users")
        cacheManager.setCaffeine(
            Caffeine.newBuilder()
                .expireAfterWrite(60, TimeUnit.MINUTES)
                .maximumSize(100)
        )
        return cacheManager
    }

}
```

**Redis** application.yml:

```yaml
spring:
  cache:
    type: redis
  redis:
    host: localhost
    port: 6379
```

```kotlin
import org.springframework.cache.annotation.EnableCaching
import org.springframework.cache.redis.RedisCacheConfiguration
import org.springframework.cache.redis.RedisCacheManager
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.data.redis.serializer.RedisSerializationContext
import org.springframework.data.redis.serializer.StringRedisSerializer

@Configuration
@EnableCaching
class CacheConfig {

    @Bean
    fun redisCacheManager(redisConnectionFactory: RedisConnectionFactory): RedisCacheManager {
        val config = RedisCacheConfiguration.defaultCacheConfig()
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer()))

        return RedisCacheManager.builder(redisConnectionFactory)
            .cacheDefaults(config)
            .build()
    }

    @Bean
    fun redisTemplate(redisConnectionFactory: RedisConnectionFactory): RedisTemplate<String, Any> {
        val template = RedisTemplate<String, Any>()
        template.setConnectionFactory(redisConnectionFactory)
        template.keySerializer = StringRedisSerializer()
        template.valueSerializer = StringRedisSerializer()
        return template
    }
}
```

**Guava** application.yml:

```yaml
spring:
  cache:
    type: guava
```

```kotlin
import com.google.common.cache.CacheBuilder
import org.springframework.cache.CacheManager
import org.springframework.cache.guava.GuavaCacheManager
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.concurrent.TimeUnit

@Configuration
class CacheConfig {

    @Bean
    fun cacheManager(): CacheManager {
        val cacheManager = GuavaCacheManager("users")
        cacheManager.setCacheBuilder(
            CacheBuilder.newBuilder()
                .expireAfterWrite(60, TimeUnit.MINUTES)
                .maximumSize(100)
        )
        return cacheManager
    }
}
```

## Как использовать AWS (Amazon Web Services) в Spring {#Как-использовать-AWS-Amazon-Web-Services-в-Spring}

Интеграция Spring Boot с AWS (Amazon Web Services) может включать множество различных сервисов, таких как Amazon S3, Amazon RDS, Amazon SQS и другие. В этом примере мы рассмотрим, как интегрироваться с Amazon S3 для загрузки и скачивания файлов.

**Пример интеграции Spring Boot с Amazon S3:**

```kotlin
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("software.amazon.awssdk:s3")
    implementation("software.amazon.awssdk:auth")
}
```

```properties
aws.accessKeyId=YOUR_ACCESS_KEY_ID
aws.secretKey=YOUR_SECRET_KEY
aws.region=us-east-1
aws.s3.bucketName=your-bucket-name
```

```kotlin
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.s3.S3Client


@Configuration
class AwsS3Config {

    @Value("\${aws.accessKeyId}")
    lateinit var accessKeyId: String

    @Value("\${aws.secretKey}")
    lateinit var secretKey: String

    @Value("\${aws.region}")
    lateinit var region: String

    @Bean
    fun s3Client(): S3Client {
        val credentials = AwsBasicCredentials.create(accessKeyId, secretKey)
        return S3Client.builder()
            .region(Region.of(region))
            .credentialsProvider(StaticCredentialsProvider.create(credentials))
            .build()
    }

}
```

```kotlin
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service
import org.springframework.web.multipart.MultipartFile
import software.amazon.awssdk.services.s3.S3Client
import software.amazon.awssdk.services.s3.model.PutObjectRequest
import software.amazon.awssdk.services.s3.model.GetObjectRequest
import java.nio.file.Paths


@Service
class S3Service(private val s3Client: S3Client) {

    @Value("\${aws.s3.bucketName}")
    lateinit var bucketName: String

    fun uploadFile(file: MultipartFile): String {
        val fileName = file.originalFilename ?: throw IllegalArgumentException("File name is missing")
        val putObjectRequest = PutObjectRequest.builder()
            .bucket(bucketName)
            .key(fileName)
            .build()
        s3Client.putObject(putObjectRequest, Paths.get(fileName))
        return "File uploaded successfully: $fileName"
    }

    fun downloadFile(fileName: String): ByteArray {
        val getObjectRequest = GetObjectRequest.builder()
            .bucket(bucketName)
            .key(fileName)
            .build()
        return s3Client.getObject(getObjectRequest).readAllBytes()
    }

}
```

```kotlin
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartFile


@RestController
@RequestMapping("/s3")
class S3Controller(private val s3Service: S3Service) {

    @PostMapping("/upload")
    fun uploadFile(@RequestParam("file") file: MultipartFile): String {
        return s3Service.uploadFile(file)
    }

    @GetMapping("/download/{fileName}")
    fun downloadFile(@PathVariable fileName: String): ResponseEntity<ByteArray> {
        val fileBytes = s3Service.downloadFile(fileName)
        return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"$fileName\"")
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .body(fileBytes)
    }

}
```

## Как настроить GitHub Actions Continuous Integration {#Как-настроить-GitHub-Actions-Continuous-Integration}

Непрерывная интеграция (Continuous Integration, CI) — это практика разработки программного обеспечения, при которой разработчики часто интегрируют изменения в основной репозиторий кода. Каждая интеграция проверяется автоматизированной сборкой и тестированием, что позволяет обнаружить и исправить ошибки на ранних этапах.

**Пример настройки CI с использованием GitHub Actions для проекта на Spring Boot:**

**Создание репозитория на GitHub:**\
Создайте новый репозиторий на GitHub.\
Клонируйте репозиторий на локальную машину и добавьте исходный код вашего Spring Boot проекта.

**Добавление файла конфигурации GitHub Actions:**\
В корне вашего проекта создайте директорию .github/workflows и внутри нее создайте файл ci.yml.

.github/workflows/ci.yml

```yaml
name: CI
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout repository
      uses: actions/checkout@v2
    - name: Set up JDK 11
      uses: actions/setup-java@v2
      with:
        distribution: 'adopt'
        java-version: '11'
    - name: Cache Gradle packages
      uses: actions/cache@v2
      with:
        path: ~/.gradle/caches
        key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
        restore-keys: |
          ${{ runner.os }}-gradle-
    - name: Grant execute permission for gradlew
      run: chmod +x gradlew
    - name: Build with Gradle
      run: ./gradlew build
    - name: Run tests
      run: ./gradlew test
```

**name: CI:**\
Имя workflow.

**on:**\
Определяет триггеры для запуска workflow. В данном случае это push и pull_request на ветку main.

**jobs:**\
Определяет набор заданий, которые будут выполнены.

**build:**\
Имя задания, которое будет выполняться.

**runs-on: ubuntu-latest:**\
Указывает, что задание будет выполняться на последней версии Ubuntu.

**steps:**\
Определяет шаги для выполнения задания:

**Checkout repository:**\
Клонирование репозитория.

**Set up JDK 11:**\
Установка JDK 11.

**Cache Gradle packages:**\
Кэширование зависимостей Gradle для ускорения последующих сборок.

**Grant execute permission for gradlew:**\
Предоставление прав на выполнение файла gradlew.

**Build with Gradle:**\
Сборка проекта с использованием Gradle.

**Run tests:**\
Запуск тестов.

[Copyright: Roman Kryvolapov](https://t.me/RomanKryvolapov)
