В этой статье:
➤ Что такое ACID (Atomicity, Consistency, Isolation, Durability)
➤ Что такое CRUD (Create, Read, Update, Delete)
➤ Что такое JPA (Java Persistence API) и Hibernate
➤ Что такое Spring Data JPA
➤ Что такое JDBC (Java Database Connectivity)
➤ Чем JPA (Java Persistence API) отличается от JDBC (Java Database Connectivity)
➤ Что такое JPQL и Criteria API
➤ Как подключаться к базам данных в Spring
➤ Какие есть уровни изоляции транзакций
➤ Какие есть параметры у аннотации @Transactional
➤ Как использовать Redis в Spring
➤ Как в Spring обрабатывать изменения в базе данных
➤ Какие есть репозитории в Spring
➤ Что такое Jenkins
➤ Что такое Nexus
➤ Что такое План запроса (Query Plan)
➤ Как использовать индексы в базе данных Spring
➤ Какие могут быть аномалии при работе с базой данных
➤ Какие есть виды блокировки в базе данных
➤ Что такое ACID (Atomicity, Consistency, Isolation, Durability)
➤ Что такое CRUD (Create, Read, Update, Delete)
➤ Что такое JPA (Java Persistence API) и Hibernate
➤ Что такое Spring Data JPA
➤ Что такое JDBC (Java Database Connectivity)
➤ Чем JPA (Java Persistence API) отличается от JDBC (Java Database Connectivity)
➤ Что такое JPQL и Criteria API
➤ Как подключаться к базам данных в Spring
➤ Какие есть уровни изоляции транзакций
➤ Какие есть параметры у аннотации @Transactional
➤ Как использовать Redis в Spring
➤ Как в Spring обрабатывать изменения в базе данных
➤ Какие есть репозитории в Spring
➤ Что такое Jenkins
➤ Что такое Nexus
➤ Что такое План запроса (Query Plan)
➤ Как использовать индексы в базе данных Spring
➤ Какие могут быть аномалии при работе с базой данных
➤ Какие есть виды блокировки в базе данных
➤ Какие есть способы управления транзакциями в Spring
➤ Что такое R2DBC (Reactive Relational Database Connectivity)
➤ Что такое Flyway, миграции
➤ Где в Spring приложении следует хранить приватные данные
➤ Как в Spring можно использовать кеширование
➤ Как использовать AWS / Amazon Web Services в Spring
➤ Как настроить GitHub Actions Continuous Integration
➤ Что такое 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 — это аббревиатура, обозначающая основные операции, которые можно выполнять с данными в базе данных. 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:
возвращает количество сущностей.
Пример реализации, используются стандартные методы и один кастомный:
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 )
import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository interface ProductRepository : JpaRepository<Product, Long> { fun findByName(name: String): List<Product> }
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
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:
// 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>
// 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 (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)
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:
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)
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:
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:
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 для выполнения специфических запросов.
dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation 'com.h2database:h2' // H2 для тестирования }
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 }
import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository<User, Long> { }
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; } } }
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
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-запроса, который находит пользователей по имени.
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.
Создаем интерфейс для определения кастомных методов.
interface UserRepositoryCustom { fun findByNameCriteria(name: String): List<User> }
Реализуем интерфейс, используя Criteria API.
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 } }
Расширяем основной репозиторий кастомным интерфейсом.
import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository interface UserRepository : JpaRepository<User, Long>, UserRepositoryCustom
➤ Как подключаться к базам данных в Spring
В Spring Boot можно легко настроить подключение к разным типам баз данных, используя файлы конфигурации application.properties. Ниже приведены примеры настройки подключения к различным популярным базам данных: H2, MySQL, PostgreSQL, Oracle, MariaDB.
Подключение к H2 (в памяти):
H2 — это встроенная база данных, которая часто используется для разработки и тестирования.
dependencies { implementation("com.h2database:h2") }
# 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:
dependencies { implementation("mysql:mysql-connector-java") }
# 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:
dependencies { implementation("org.postgresql:postgresql") }
# 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:
dependencies { implementation("com.oracle.database.jdbc:ojdbc8") }
# 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):
dependencies { implementation("org.springframework.boot:spring-boot-starter-data-mongodb") }
spring.data.mongodb.host=localhost spring.data.mongodb.port=27017 spring.data.mongodb.database=mydatabase
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 )
import org.springframework.data.mongodb.repository.MongoRepository import org.springframework.stereotype.Repository @Repository interface UserRepository : MongoRepository<User, String> { fun findByName(name: String): User }
Подключение к MariaDB:
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' }
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):
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' }
spring.couchbase.connection-string=127.0.0.1 spring.couchbase.username=your_username spring.couchbase.password=your_password spring.couchbase.bucket.name=your_bucket
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" } }
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> }
Настройка пула соединений:
Независимо от используемой базы данных, можно настроить пул соединений для улучшения производительности.
# 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 стандарте и большинстве реляционных СУБД определены четыре уровня изоляции транзакций:
Уровень изоляции: | Грязное чтение | Неповторяющееся чтение | Фантомное чтение --------------------------------------------------------------------------------- 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, есть и другие способы
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
propagation (тип: Propagation):
Определяет, как транзакция должна распространяться на методы, вызываемые внутри текущей транзакции.
Значения:
Propagation.REQUIRED (по умолчанию):
Использует текущую транзакцию или создает новую, если текущая отсутствует.
Propagation.REQUIRES_NEW:
Всегда создает новую транзакцию, приостанавливая текущую, если она существует.
Propagation.SUPPORTS:
Использует текущую транзакцию, если она существует, в противном случае выполняется без транзакции.
Propagation.NOT_SUPPORTED:
Выполняет метод вне транзакции, приостанавливая текущую транзакцию, если она существует.
Propagation.MANDATORY:
Требует существования текущей транзакции, выбрасывает исключение, если ее нет.
Propagation.NEVER:
Выполняется без транзакции, выбрасывает исключение, если текущая транзакция существует.
Propagation.NESTED:
Создает вложенную транзакцию, если существует текущая транзакция.
@Transactional(propagation = Propagation.REQUIRED) fun myTransactionalMethod() {
isolation (тип: Isolation):
Определяет уровень изоляции транзакции.
Значения:
Isolation.DEFAULT (по умолчанию):
Использует уровень изоляции базы данных.
Isolation.READ_UNCOMMITTED:
Самый низкий уровень изоляции, позволяет “грязное чтение”.
Isolation.READ_COMMITTED:
Гарантирует, что данные, считанные в транзакции, уже зафиксированы.
Isolation.REPEATABLE_READ:
Гарантирует, что данные, считанные в транзакции, не изменятся до ее завершения.
Isolation.SERIALIZABLE:
Самый высокий уровень изоляции, гарантирует полную изоляцию транзакций.
@Transactional(isolation = Isolation.REPEATABLE_READ) fun myTransactionalMethod() { // Код будет выполнен в рамках транзакции с уровнем изоляции REPEATABLE_READ }
timeout (тип: Int):
Определяет максимальное время (в секундах), которое транзакция может выполняться до принудительного отката.
Значение по умолчанию -1 означает отсутствие ограничения по времени.
Если транзакция не завершится за указанное время, будет выброшено исключение TransactionTimedOutException.
@Transactional(timeout = 30) fun myTransactionalMethod() { // Код должен быть выполнен в течение 30 секунд }
readOnly (тип: Boolean):
Указывает, что транзакция предназначена только для чтения. Значение по умолчанию false.
Установка в true позволяет оптимизировать производительность, так как транзакция не будет содержать изменений.
@Transactional(readOnly = true) fun myReadOnlyMethod() { // Код выполняется в режиме только для чтения }
rollbackFor (тип: Array<Class<out Throwable>>):
Указывает массив исключений, при возникновении которых должна произойти откат транзакции.
@Transactional(rollbackFor = [RuntimeException::class, IOException::class]) fun myMethod() { // Транзакция будет откатана, если будут выброшены RuntimeException или IOException }
rollbackForClassName (тип: Array<String>):
Указывает массив имен исключений (в виде строк), при возникновении которых должна произойти откат транзакции.
noRollbackFor (тип: Array<Class<out Throwable>>):
Указывает массив исключений, при возникновении которых транзакция не должна откатываться.
@Transactional(noRollbackFor = [CustomException::class]) fun myMethod() { // Транзакция не будет откатана, если будет выброшен CustomException }
noRollbackForClassName (тип: Array<String>):
Указывает массив имен исключений (в виде строк), при возникновении которых транзакция не должна откатываться.
transactionManager:
Указывает, какой транзакционный менеджер использовать, если их несколько
@Transactional(transactionManager = "customTransactionManager") fun myMethod() { // Метод будет использовать customTransactionManager }
value:
Синоним для transactionManager. Можно использовать для указания транзакционного менеджера.
@Transactional("customTransactionManager") fun myMethod() { // Метод будет использовать customTransactionManager }
Пример использования всех параметров:
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). Основное внимание следует уделить началу, коммиту, откату транзакций и потенциальным ошибкам.
logging: level: org: springframework: transaction: DEBUG hibernate: SQL: DEBUG transaction: DEBUG type: descriptor: sql: BasicBinder: TRACE
Пример конфигурации логирования для Logback (logback-spring.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 (Remote Dictionary Server) — это высокопроизводительный, in-memory key-value хранилище, которое поддерживает множество структур данных, таких как строки, списки, множества, хеши и сортированные множества. Redis часто используется для кэширования данных, управления сессиями, очередей сообщений, чатов, счетчиков и других сценариев, требующих высокой производительности и низкой задержки.
Интеграция Spring Boot с Redis может быть выполнена с использованием Spring Data Redis, который предоставляет удобный и мощный API для работы с Redis. Вот пошаговый пример, как настроить и использовать Redis в Spring Boot приложении.
dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-redis' }
spring.redis.host=localhost spring.redis.port=6379
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 } }
import java.io.Serializable data class Person( val id: String, val name: String, val age: Int ) : Serializable
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) } }
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 Events или, если используется NoSQL база данных, таких как MongoDB, использование его функциональности Change Streams.
Пример для реляционной базы данных (например, PostgreSQL) с использованием триггеров и слушателей:
Создание триггера в базе данных:
Создайте триггер в базе данных для отслеживания изменений в таблице.
Пример для PostgreSQL:
Создайте функцию триггера
CREATE OR REPLACE FUNCTION notify_table_update() RETURNS TRIGGER AS $$ BEGIN PERFORM pg_notify('table_update', NEW.id::text); RETURN NEW; END; $$ LANGUAGE plpgsql;
Создайте триггер, который вызывает эту функцию при обновлении строки в таблице:
CREATE TRIGGER table_update_trigger AFTER UPDATE ON your_table FOR EACH ROW EXECUTE FUNCTION notify_table_update();
Настройка слушателя в Spring Boot:
Используйте JDBC для прослушивания уведомлений от базы данных.
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
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 для отслеживания изменений.
spring.data.mongodb.uri=mongodb://localhost:27017/your_database
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
Repository ├── CrudRepository │ ├── PagingAndSortingRepository │ │ ├── JpaRepository │ │ └── MongoRepository │ └── ReactiveCrudRepository │ └── R2dbcRepository
CrudRepository:
предоставляет основные методы CRUD (создание, чтение, обновление, удаление) для работы с базой данных. Это базовый интерфейс для всех репозиториев в Spring Data.
import org.springframework.data.repository.CrudRepository interface PersonRepository : CrudRepository<Person, Long>
JpaRepository:
расширяет CrudRepository и PagingAndSortingRepository, предоставляя дополнительные методы для работы с JPA (Java Persistence API), такие как методы для пакетных операций и методы для работы с флашированием.
import org.springframework.data.jpa.repository.JpaRepository interface PersonRepository : JpaRepository<Person, Long>
PagingAndSortingRepository:
расширяет CrudRepository и предоставляет методы для выполнения операций с сортировкой и пагинацией (разбиение на страницы).
import org.springframework.data.repository.PagingAndSortingRepository interface PersonRepository : PagingAndSortingRepository<Person, Long>
MongoRepository:
используется для работы с MongoDB. Он расширяет PagingAndSortingRepository и предоставляет методы для работы с MongoDB.
import org.springframework.data.mongodb.repository.MongoRepository interface PersonRepository : MongoRepository<Person, String>
ReactiveCrudRepository:
используется для реактивного программирования с поддержкой Project Reactor. Он предоставляет асинхронные методы для CRUD операций.
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).
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 — это популярный инструмент для непрерывной интеграции (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, скачав дистрибутив с официального сайта или используя пакетный менеджер вашей операционной системы.
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 — это репозиторий менеджер, используемый для хранения, управления и распространения артефактов, таких как библиотеки, пакеты и другие компоненты, используемые в процессе разработки программного обеспечения. Он поддерживает множество форматов, включая Maven, npm, NuGet, Docker, и многие другие.
Основные возможности Nexus:
Хранение артефактов:
Nexus предоставляет централизованное место для хранения всех артефактов, используемых в процессе разработки, таких как библиотеки, плагины и другие зависимости.
Управление зависимостями:
Nexus помогает управлять зависимостями проектов, обеспечивая доступ к необходимым артефактам и их версиям.
Кэширование удаленных репозиториев:
Nexus может кэшировать артефакты из удаленных репозиториев, что помогает уменьшить задержки и повысить производительность.
Поддержка множества форматов:
Nexus поддерживает различные форматы репозиториев, такие как Maven, npm, NuGet, Docker и другие.
Управление доступом:
Nexus предоставляет гибкую систему управления доступом, позволяя контролировать, кто может загружать и скачивать артефакты.
Интеграция с CI/CD:
Nexus легко интегрируется с инструментами для непрерывной интеграции и развертывания (CI/CD), такими как Jenkins, для автоматизации процессов сборки и развертывания.
Вы можете установить Nexus с использованием Docker:
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) — это набор инструкций, созданных системой управления базами данных (СУБД), которые описывают, как СУБД выполнит SQL-запрос. План запроса содержит информацию о порядке выполнения операций, используемых индексов, методах доступа к данным и других аспектах, которые влияют на производительность запроса.
Основные компоненты плана запроса:
Последовательность операций:
Операции могут включать сканирование таблицы, использование индексов, соединения таблиц, сортировку и агрегирование данных.
Используемые индексы:
План запроса указывает, какие индексы используются для ускорения доступа к данным.
Методы доступа к данным:
Методы могут включать полное сканирование таблицы, индексированное сканирование, поиск по первичному ключу и т. д.
Порядок соединения таблиц:
План запроса описывает порядок, в котором таблицы соединяются, и методы соединения (например, вложенные циклы, хеш-соединение, слияние).
В PostgreSQL можно использовать команду EXPLAIN, чтобы получить план запроса:
EXPLAIN SELECT * FROM users WHERE id = 1;
Пример плана запроса:
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.
Избегание полного сканирования таблицы:
Использование индексированных сканирований вместо полного сканирования таблицы для улучшения производительности.
Оптимизация соединений таблиц:
Выбор подходящего метода соединения таблиц (например, хеш-соединение, слияние) в зависимости от объема данных.\
Рефакторинг запросов:
Переписывание сложных запросов, чтобы уменьшить количество операций и улучшить производительность.
Пример использования индекса
Создание индекса:
CREATE INDEX idx_users_id ON users (id);
Получение плана запроса после создания индекса:
EXPLAIN SELECT * FROM users WHERE id = 1;
Пример плана запроса после создания индекса:
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 Data JPA
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' }
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 для создания индексов на уровне сущности.
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-запроса для проверки индекса:
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:
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.
-- Транзакция A берет разделяемую блокировку BEGIN TRANSACTION; SELECT * FROM MyTable WITH (HOLDLOCK, ROWLOCK);
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.
-- Транзакция B берет исключительную блокировку BEGIN TRANSACTION; UPDATE MyTable WITH (ROWLOCK) SET Column = 'Value';
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:
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 Framework управление транзакциями может быть реализовано несколькими способами. Основные способы включают декларативное и программное управление транзакциями.
Декларативное управление транзакциями:
@Transactional:
Используется для указания методов или классов, которые должны выполняться в рамках транзакции.
Можно настроить параметры, такие как уровень изоляции, распространение, тайм-аут и т.д.
import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @Service class MyService { @Transactional fun executeTransactionalMethod() { // Здесь код будет выполнен в рамках транзакции } }
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 для управления транзакциями в коде.
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.
@Bean fun transactionManager(dataSource: DataSource): PlatformTransactionManager { return DataSourceTransactionManager(dataSource) }
JPA-транзакции:
Используется JpaTransactionManager.
Подходит для управления транзакциями на уровне JPA.
@Bean fun transactionManager(entityManagerFactory: EntityManagerFactory): PlatformTransactionManager { return JpaTransactionManager(entityManagerFactory) }
Hibernate-транзакции:
Используется HibernateTransactionManager.
Подходит для управления транзакциями на уровне Hibernate.
@Bean fun transactionManager(sessionFactory: SessionFactory): PlatformTransactionManager { return HibernateTransactionManager(sessionFactory) }
JTA-транзакции:
Используется JtaTransactionManager.
Подходит для управления распределенными транзакциями в корпоративных приложениях.
@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.
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:
Предоставляет низкоуровневый доступ для управления синхронизацией транзакций.
import org.springframework.transaction.support.TransactionSynchronizationManager class MyService { fun someMethod() { if (TransactionSynchronizationManager.isActualTransactionActive()) { // Текущая транзакция активна } } }
➤ Что такое R2DBC (Reactive Relational Database Connectivity)
Для работы с базой данных в реактивном стиле в WebFlux вы можете использовать проект Spring Data R2DBC (Reactive Relational Database Connectivity). Этот проект позволяет работать с реляционными базами данных в реактивном стиле.
dependencies { implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc' implementation 'io.r2dbc:r2dbc-postgresql' }
spring: r2dbc: url: r2dbc:postgresql://localhost:5432/mydatabase username: myuser password: mypassword
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 )
import org.springframework.data.repository.reactive.ReactiveCrudRepository import reactor.core.publisher.Mono interface UserRepository : ReactiveCrudRepository<User, Long> { fun findByEmail(email: String): Mono<User> }
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) }
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 поддерживает множество баз данных, таких как PostgreSQL, MySQL, Oracle, SQL Server и многие другие.
Интеграция с различными инструментами:
Flyway легко интегрируется с популярными сборщиками проектов и фреймворками, такими как Maven, Gradle, Spring Boot и др.
Автоматическое обнаружение и выполнение миграций:
Flyway автоматически обнаруживает новые миграции и применяет их к базе данных.
Поддержка различных типов миграций:
Flyway поддерживает SQL и Java миграции, что позволяет гибко управлять изменениями в базе данных.
dependencies { implementation 'org.flywaydb:flyway-core' }
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
где
logging: level: org.springframework.data.r2dbc: DEBUG
устанавливает уровень логирования для пакета org.springframework.data.r2dbc на уровень DEBUG. Это значит, что будут выводиться детализированные сообщения о выполнении операций с базой данных через R2DBC.
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:
Пароль для подключения к базе данных.
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 для работы с базой данных.
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:
CREATE TABLE users ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(100) NOT NULL UNIQUE );
Flyway автоматически выполнит миграции при запуске приложения Spring Boot. Если в базе данных будут обнаружены новые миграции, они будут применены.
➤ Где в Spring приложении следует хранить приватные данные
В Spring приложениях приватные ключи и другие конфиденциальные данные должны храниться безопасно, чтобы предотвратить несанкционированный доступ. Вот несколько рекомендаций по их хранению:
Spring Boot Configuration Properties:
Используйте файл application.properties или application.yml, но не храните в них сами ключи, а лишь указывайте пути к ним.
Environment Variables:
Окружающие переменные (environment variables) являются безопасным способом хранения конфиденциальной информации.
@Value("\${my.secret.key}") private lateinit var mySecretKey: String
External Configuration Server:
Используйте Spring Cloud Config Server, который позволяет централизованно управлять конфигурацией и безопасно хранить ключи. Данные могут быть зашифрованы и защищены.
Vault:
HashiCorp Vault — это мощное решение для хранения секретов. Spring Cloud Vault позволяет интегрировать Vault с вашим приложением.
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:
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:
Если ваше приложение разворачивается в облаке, используйте облачные сервисы для хранения секретов.
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, для шифрования данных.
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 можно использовать кеширование
Конфигурация кеша:
Включение кеширования в конфигурационном классе с помощью аннотации @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 удаляется из кеша.
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' }
import org.springframework.cache.annotation.EnableCaching import org.springframework.context.annotation.Configuration @Configuration @EnableCaching class CacheConfig
@Cacheable:
Кеширует результат метода.
@CachePut:
Обновляет кеш после выполнения метода.
@CacheEvict:
Удаляет записи из кеша.
@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:
spring: cache: type: simple
JCache (JSR-107) application.yml:
spring: cache: type: jcache
Ehcache application.yml:
spring: cache: type: ehcache
Ehcache ehcache.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:
spring: cache: type: hazelcast
Hazelcast hazelcast.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:
spring: cache: type: infinispan
Infinispan infinispan.xml:
<infinispan> <cache-container> <local-cache name="users"> <expiration lifespan="3600000"/> </local-cache> </cache-container> </infinispan>
Caffeine application.yml:
spring: cache: type: caffeine
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:
spring: cache: type: redis redis: host: localhost port: 6379
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:
spring: cache: type: guava
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
Интеграция Spring Boot с AWS (Amazon Web Services) может включать множество различных сервисов, таких как Amazon S3, Amazon RDS, Amazon SQS и другие. В этом примере мы рассмотрим, как интегрироваться с Amazon S3 для загрузки и скачивания файлов.
Пример интеграции Spring Boot с Amazon S3:
dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("software.amazon.awssdk:s3") implementation("software.amazon.awssdk:auth") }
aws.accessKeyId=YOUR_ACCESS_KEY_ID aws.secretKey=YOUR_SECRET_KEY aws.region=us-east-1 aws.s3.bucketName=your-bucket-name
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() } }
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() } }
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
Непрерывная интеграция (Continuous Integration, CI) — это практика разработки программного обеспечения, при которой разработчики часто интегрируют изменения в основной репозиторий кода. Каждая интеграция проверяется автоматизированной сборкой и тестированием, что позволяет обнаружить и исправить ошибки на ранних этапах.
Пример настройки CI с использованием GitHub Actions для проекта на Spring Boot:
Создание репозитория на GitHub:
Создайте новый репозиторий на GitHub.
Клонируйте репозиторий на локальную машину и добавьте исходный код вашего Spring Boot проекта.
Добавление файла конфигурации GitHub Actions:
В корне вашего проекта создайте директорию .github/workflows и внутри нее создайте файл ci.yml.
.github/workflows/ci.yml
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:
Запуск тестов.