Java for Beginner
677 subscribers
562 photos
156 videos
12 files
862 links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Откаты (Rollback) и теги в Liquibase

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

1. Типы откатов

1.1. Автоматический откат (на основе Liquibase)
Liquibase может автоматически генерировать команды отката для некоторых операций, если они указаны в changeSet.

Пример (YAML):
- changeSet:
id: 1
author: dev
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: INT
constraints:
primaryKey: true
- rollback:
dropTable:
tableName: users


Как это работает?
При выполнении liquibase update применяется createTable.
При откате (rollback) выполнится dropTable.

Какие операции поддерживают автоматический откат?
createTable → dropTable
addColumn → dropColumn
createIndex → dropIndex


Ограничения:
Не все операции имеют автоматический откат (например, сложные ALTER TABLE).
Для них требуется ручной rollback.


1.2. Ручной откат (пользовательские SQL-скрипты)
Если Liquibase не может сгенерировать откат автоматически, его можно задать вручную.

Пример (YAML):
- changeSet:
id: 2
author: dev
changes:
- sql:
sql: "ALTER TABLE users ADD COLUMN last_login TIMESTAMP"
- rollback:
sql:
sql: "ALTER TABLE users DROP COLUMN last_login"


Когда использовать?
Для сложных SQL-запросов.
Если Liquibase не поддерживает автоматический откат для операции.


2. Теги (Tags)

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

2.1. Создание тега (tag)
Пометка текущего состояния БД.

Команда:
liquibase tag v1.0
или через changelog:

yaml
- changeSet:
id: 3
author: dev
changes:
- tagDatabase:
tag: v1.0


Где хранится информация о тегах?
В таблице DATABASECHANGELOG (поле TAG).

2.2. Откат к тегу (rollback <tag>)
Возврат базы данных к состоянию на момент создания тега.

Команда:
liquibase rollback v1.0


Как это работает?
Liquibase находит тег v1.0 в DATABASECHANGELOG.
Определяет, какие changeSet’ы были выполнены после этого тега.
Применяет откаты в обратном порядке.


Пример (YAML):
- changeSet:
id: 4
author: dev
changes:
- tagDatabase:
tag: v1.1


3. Практические сценарии отката

3.1. Отмена последнего changeSet’а
liquibase rollbackCount 1


Что произойдёт?
Liquibase откатит один последний применённый changeSet.


3.2. Восстановление до определённой версии

Вариант 1: По тегу
liquibase rollback v1.0


Вариант 2: По дате
liquibase rollbackToDate 2024-03-15


Вариант 3: По количеству changeSet’ов
liquibase rollbackCount 3  # Откатит 3 последних изменения


#Java #middle #Liquibase
4. Важные нюансы

Откат не всегда возможен
Если changeSet не содержит rollback, Liquibase не сможет его отменить автоматически.

(Пример: изменение данных (INSERT/UPDATE) требует ручного rollback.)

Теги vs. Версии
Теги — это произвольные метки (например, v1.0, prod-release).
Версии — это последовательные номера changeSet’ов.


Безопасность откатов
Всегда тестируйте rollback в dev-среде, прежде чем применять в production.
Используйте preConditions, чтобы избежать ошибок.


5. Пример полного сценария

1. Изначальный changelog:

databaseChangeLog:
- changeSet:
id: 1
author: dev
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: INT
constraints:
primaryKey: true
- rollback:
dropTable:
tableName: users

- changeSet:
id: 2
author: dev
changes:
- addColumn:
tableName: users
column:
name: username
type: VARCHAR(50)
- rollback:
dropColumn:
tableName: users
columnName: username

- changeSet:
id: 3
author: dev
changes:
- tagDatabase:
tag: v1.0


2. Применяем изменения:
liquibase update


3. Откатываемся до тега v1.0:
liquibase rollback v1.0


Результат:
Будет отменён changeSet id=2 (удалён столбец username).
Таблица users останется (так как changeSet id=1 был до тега).


#Java #middle #Liquibase
Интеграция Liquibase с другими инструментами

Maven / Gradle

Maven

Добавьте зависимость в pom.xml:
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>4.25.0</version>
</dependency>


Конфигурация через pom.xml:
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<version>4.25.0</version>
<configuration>
<changeLogFile>src/main/resources/db/changelog/db.changelog-master.yaml</changeLogFile>
<url>jdbc:postgresql://localhost:5432/mydb</url>
<username>user</username>
<password>pass</password>
</configuration>
</plugin>


Запуск миграций:
mvn liquibase:update


Gradle

Добавьте в build.gradle:
plugins {
id 'org.liquibase.gradle' version '2.2.0'
}

dependencies {
liquibaseRuntime 'org.liquibase:liquibase-core:4.25.0'
liquibaseRuntime 'org.postgresql:postgresql:42.6.0'
}

liquibase {
changeLogFile 'src/main/resources/db/changelog/db.changelog-master.yaml'
url 'jdbc:postgresql://localhost:5432/mydb'
username 'user'
password 'pass'
}


Запуск:
gradle liquibaseUpdate


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

Spring Boot автоматически настраивает Liquibase, если он обнаружен в classpath.

Настройки в application.yml:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: user
password: pass
liquibase:
change-log: classpath:db/changelog/db.changelog-master.yaml
enabled: true
contexts: dev # Фильтрация по контексту


Поведение:
Миграции запускаются при старте приложения.
Можно отключить через spring.liquibase.enabled=false.


Расширенные функции Liquibase

Динамические свойства (property)

Позволяют выносить повторяющиеся значения в переменные.

Пример (YAML):
databaseChangeLog:
- property:
name: default.schema
value: public
- changeSet:
id: 1
author: dev
changes:
- createTable:
tableName: ${default.schema}.users
columns:
- column:
name: id
type: INT


В
liquibase.properties:
default.schema=public


Через командную строку:
liquibase update -Ddefault.schema=public


Контексты (contexts) и фильтрация changeSet’ов

Контексты позволяют выбирать, какие changeSet’ы применять в определённых окружениях.

Пример (YAML):
- changeSet:
id: 2
author: dev
context: "dev,test" # Будет выполнен только в dev и test
changes:
- insert:
tableName: users
columns:
- column:
name: username
value: "test_user"

- changeSet:
id: 3
author: dev
context: "prod" # Только в production
changes:
- createIndex:
tableName: users
indexName: idx_user_email
columns:
- column:
name: email


Как передать контекст?

Через командную строку:
liquibase update --contexts="dev"


В Spring Boot:
spring:
liquibase:
contexts: dev


#Java #middle #Liquibase
Best Practices

Организация changelog-файлов

Рекомендуемая структура:
src/main/resources/db/changelog/
├── db.changelog-master.yaml # Главный файл
├── changes/
│ ├── 001-create-tables.yaml
│ ├── 002-add-indexes.yaml
│ └── 003-insert-data.yaml
└── rollback/
├── 001-rollback.yaml # Ручные откаты


Главный файл (db.changelog-master.yaml):
databaseChangeLog:
- includeAll:
path: db/changelog/changes/
relativeToChangelogFile: true


Почему так?

Удобно управлять версиями.
Легко находить изменения.


Тестирование миграций

Стратегии:
Локальная проверка:
liquibase validate  # Проверка синтаксиса
liquibase update # Применение в test-DB


Интеграционные тесты (JUnit + Testcontainers):
@Testcontainers
@SpringBootTest
class LiquibaseMigrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");

@Test
void testMigrations() {
// Spring Boot автоматически применит миграции
assertTrue(true); # Если не упало — значит, успешно
}
}


Работа в команде (избежание конфликтов)


Правила:

Именование changeSet’ов:
Используйте id: <дата>-<номер> (например, id: 20240315-1).
Указывайте автора (author: github_username).


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

Использование preConditions:
- changeSet:
id: 20240315-1
author: dev
preConditions:
- tableExists:
tableName: users
changes:
- addColumn:
tableName: users
column:
name: phone
type: VARCHAR(20)


Регулярные обновления:
Перед началом работы выполняйте liquibase update, чтобы получить актуальную схему.

#Java #middle #Liquibase
Настройка OAuth2 для Gmail

Для Gmail предпочтительно использовать OAuth2.

Пример конфигурации:
@Configuration
public class MailConfig {

@Bean
public JavaMailSender javaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost("smtp.gmail.com");
mailSender.setPort(587);
mailSender.setUsername("your.email@gmail.com");

Properties props = mailSender.getJavaMailProperties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.auth.mechanisms", "XOAUTH2");

// Настройка OAuth2 токена через Google API Client Library
// Подробности: https://developers.google.com/identity/protocols/oauth2
return mailSender;
}
}


Примечание: Зарегистрируйте приложение в Google Cloud Console и используйте библиотеку google-auth-library-oauth2-http для получения accessToken.

Создание сервиса отправки почты

Создадим сервис EmailService для отправки писем.

@Service
public class EmailService {

@Autowired
private JavaMailSender mailSender;

@Autowired
private TemplateEngine templateEngine;

private static final Logger logger = LoggerFactory.getLogger(EmailService.class);

public void sendSimpleEmail(String to, String subject, String text) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(subject);
message.setText(text);
mailSender.send(message);
}

public void sendHtmlEmail(String to, String subject, String htmlBody) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setTo(to);
helper.setSubject(MimeUtility.encodeText(subject, "UTF-8", null));
helper.setText(htmlBody, true);
mailSender.send(message);
}

public void sendEmailWithAttachment(String to, String subject, String text, File attachment) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setTo(to);
helper.setSubject(MimeUtility.encodeText(subject, "UTF-8", null));
helper.setText(text);
FileSystemResource file = new FileSystemResource(attachment);
helper.addAttachment(attachment.getName(), file);
mailSender.send(message);
}

@Async
public CompletableFuture<Void> sendSimpleEmailAsync(String to, String subject, String text IMPORTANT: text) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(MimeUtility.encodeText(subject, "UTF-8", null));
message.setText(text);
mailSender.send(message);
return CompletableFuture.completedFuture(null);
}

public void sendTemplatedEmail(String to, String subject, String username) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
Context context = new Context();
context.setVariable("username", username);
String htmlBody = templateEngine.process("email-template", context);
helper.setTo(to);
helper.setSubject(MimeUtility.encodeText(subject, "UTF-8", null));
helper.setText(htmlBody, true);
mailSender.send(message);
}
}


Конфигурация асинхронности:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.initialize();
return executor;
}
}


#Java #middle #on_request #JavaMailSender
Примеры кода

Простой текст:
SimpleMailMessage message = new SimpleMailMessage();
message.setTo("user@example.com");
message.setSubject("Тестовое письмо");
message.setText("Привет из Spring Boot!");
mailSender.send(message);


HTML-письмо:
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setTo("user@example.com");
helper.setSubject(MimeUtility.encodeText("HTML письмо", "UTF-8", null));
helper.setText("<h1>Привет!</h1><p>Это HTML письмо.</p>", true);
mailSender.send(message);


Вложение:

File file = new File("/path/to/file.pdf");
FileSystemResource resource = new FileSystemResource(file);
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setTo("user@example.com");
helper.setSubject(MimeUtility.encodeText("Письмо с вложением", "UTF-8", null));
helper.setText("Смотри вложение!");
helper.addAttachment("file.pdf", resource);
mailSender.send(message);


Обработка ошибок и логирование

JavaMailSender может выбрасывать исключения, которые нужно обрабатывать.

Основные исключения:
MailAuthenticationException
MailSendException
MessagingException


Пример отлова:
try {
mailSender.send(message);
} catch (MailException e) {
logger.error("Ошибка отправки письма", e);
throw e;
}


Повторные попытки
Используйте @Retryable из Spring Retry:
@Retryable(value = MailException.class, maxAttempts = 3)
public void sendSimpleEmail(String to, String subject, String text) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(subject);
message.setText(text);
mailSender.send(message);
}


Зависимость:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>


Мониторинг и метрики

Для продакшен-приложений полезно отслеживать метрики отправки писем с помощью Spring Actuator:
@Service
public class EmailService {

@Autowired
private JavaMailSender mailSender;

@Autowired
private MeterRegistry meterRegistry;

public void sendSimpleEmail(String to, String subject, String text) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(subject);
message.setText(text);
try {
mailSender.send(message);
meterRegistry.counter("email.sent.success").increment();
} catch (MailException e) {
meterRegistry.counter("email.sent.failure").increment();
throw e;
}
}
}


Зависимость:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>


Безопасность и защита данных

Никогда не храните пароли в открытом виде в git-репозиториях.

Рекомендации:
Используйте переменные окружения или секреты (например, Kubernetes Secrets).
Применяйте Spring Cloud Vault или HashiCorp Vault.
Используйте шифрование с Jasypt.
Настройте OAuth2 для Gmail.


Пример с переменными окружения:
spring.config.import=optional:configserver:http://config-server
spring.mail.password=${MAIL_PASSWORD}


#Java #middle #on_request #JavaMailSender
Тестирование отправки почты

Интеграционные тесты с Testcontainers:
@Testcontainers
@SpringBootTest
public class EmailServiceTest {

@Container
private static final GreenMailContainer greenMail = new GreenMailContainer();

@Autowired
private EmailService emailService;

@Test
void shouldSendEmail() {
emailService.sendSimpleEmail("test@example.com", "Test Subject", "Test Body");
assertEquals(1, greenMail.getReceivedMessages().length);
}
}


Зависимость:
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>greenmail</artifactId>
<version>2.2.0</version>
<scope>test</scope>
</dependency>


Unit-тестирование:
@MockBean
private JavaMailSender mailSender;

@Test
void shouldSendMail() {
SimpleMailMessage message = new SimpleMailMessage();
emailService.sendSimpleEmail("to", "subject", "body");
verify(mailSender, times(1)).send(any(SimpleMailMessage.class));
}


Интеграция с очередями

Для отложенной отправки используйте RabbitMQ(ну или другой брокер сообщений):

@Service
public class EmailService {

@Autowired
private JavaMailSender mailSender;

@Autowired
private RabbitTemplate rabbitTemplate;

public void sendEmailToQueue(String to, String subject, String text) {
EmailMessage email = new EmailMessage(to, subject, text);
rabbitTemplate.convertAndSend("emailQueue", email);
}

@RabbitListener(queues = "emailQueue")
public void processEmail(EmailMessage email) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(email.getTo());
message.setSubject(email.getSubject());
message.setText(email.getText());
mailSender.send(message);
}

@Data
static class EmailMessage {
private String to;
private String subject;
private String text;
}
}


Зависимость:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>


Распространенные ошибки и подводные камни

Неверная конфигурация SMTP (порт, шифрование).
Проблемы с двухфакторной авторизацией (особенно Gmail).
Ограничения на массовую рассылку (например, Gmail: ~500 писем/день для бесплатных аккаунтов).
Проблемы с кодировкой (например, кириллица в теме письма).
Отсутствие MIME-типа у вложений.
Блокировка сервером (например,
Mail.ru требует DKIM и SPF).

Решение для кодировки:
helper.setSubject(MimeUtility.encodeText("Тема на кириллице", "UTF-8", null));


#Java #middle #on_request #JavaMailSender
Введение в Flyway

Flyway представляет собой специализированный инструмент управления миграциями баз данных, реализующий принцип "Database-as-Code". Его фундаментальная задача заключается в обеспечении контролируемого и воспроизводимого процесса эволюции схемы базы данных через систему версионированных изменений.

Ключевые функциональные аспекты:
Декларативное управление схемой - все изменения БД описываются в виде отдельных скриптов
Детерминированность применения - каждая миграция имеет строгую версионную привязку
Атомарность изменений - миграции применяются как единое целое (транзакционно)
Историчность - полный аудит всех выполненных изменений


Сравнение с Liquibase

Принципиальные различия между Flyway и Liquibase проявляются в нескольких аспектах:

Архитектурный подход
Flyway придерживается парадигмы "SQL-первым", где основным носителем изменений являются нативные SQL-скрипты. Liquibase предлагает абстрактный DSL (XML/YAML/JSON) для описания изменений, который затем транслируется в SQL.

Механизм откатов
В Flyway стратегия откатов реализована через создание новых миграционных скриптов с обратными изменениями. Liquibase предоставляет встроенный механизм описания операций отката непосредственно в changeSet.

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

Интеграционные возможности
Оба инструмента имеют глубокую интеграцию с экосистемой Java, но Flyway чаще выбирают для Spring Boot-проектов благодаря более простой автоконфигурации.


Базовые концепции

Модель миграций

Flyway оперирует двумя фундаментальными типами миграций:


Версионные миграции (префикс V)
Линейно упорядоченные изменения схемы
Неизменяемые после применения


Пример именования: V2__Add_email_column.sql

Содержимое SQL-скрипта:

ALTER TABLE users ADD COLUMN email VARCHAR(255);


Повторяемые миграции (префикс R)
Перезаписываемые скрипты
Применяются при изменении контрольной суммы


Пример: R__Update_report_views.sql


Механизм контроля версий

Система использует служебную таблицу flyway_schema_history, содержащую:
Точную версию каждой примененной миграции
Контрольную сумму скрипта
Временные метки выполнения
Статус операции


Жизненный цикл изменений

Типичный workflow включает:
Инициализацию схемы (baseline)
Валидацию существующих миграций
Применение новых изменений (migrate)
При необходимости - очистку (clean) и повторное развертывание



Поддерживаемые системы управления базами данных

Flyway обеспечивает совместимость с большинством современных реляционных СУБД:
PostgreSQL (полная поддержка, включая расширения)
MySQL/MariaDB (включая специфичные движки хранения)
Oracle (с учетом особенностей PL/SQL)
SQL Server (включая Azure SQL)
Встраиваемые БД (H2, SQLite)
Для NoSQL-систем (MongoDB) требуется использование коммерческой версии Flyway. Важно отметить, что некоторые специфические функции СУБД могут требовать особого подхода при миграциях.



Типовые сценарии использования

Разработка - синхронизация схемы между средами
Тестирование - подготовка изолированных тестовых БД
Развертывание - автоматизация деплоя в production
Анализ - отслеживание истории изменений схемы


#Java #middle #Flyway
Настройка и базовые команды Flyway

1. Конфигурация Flyway

1.1. Файл flyway.conf
Основной файл конфигурации Flyway. Располагается в корне проекта или в директории conf.

Пример содержимого:
# Подключение к БД
flyway.url=jdbc:postgresql://localhost:5432/mydb
flyway.user=admin
flyway.password=secret

# Директории с миграциями
flyway.locations=classpath:db/migration,filesystem:/opt/migrations

# Дополнительные настройки
flyway.schemas=public
flyway.baselineOnMigrate=true


Ключевые параметры:
flyway.url — JDBC-URL базы данных (формат зависит от СУБД).

flyway.locations — пути к миграциям:
classpath: — поиск в ресурсах проекта (например, src/main/resources/db/migration).
filesystem: — абсолютный путь на диске.
flyway.baselineOnMigrate — инициализация существующей БД (без таблицы flyway_schema_history).


1.2. Параметры командной строки

Настройки можно переопределять через аргументы:
flyway -url=jdbc:postgresql://localhost:5432/mydb \
-user=admin \
-password=secret \
-locations=classpath:db/migration \
migrate
Важно: Параметры из командной строки имеют приоритет над flyway.conf.


2. Основные CLI-команды

2.1. flyway migrate

Применяет все невыполненные миграции.

Процесс:
Проверяет таблицу flyway_schema_history.
Находит скрипты, которых нет в истории.
Выполняет их в порядке версий (например, V1__... перед V2__...).


Пример:
flyway migrate


2.2. flyway info

Показывает текущее состояние БД:
Какие миграции применены.
Какие ожидают выполнения.
Статус (Success, Pending, Failed).


Вывод:
+-----------+---------+---------------------+------+---------------------+---------+
| Version | Description | Installed on | State | Type | Checksum |
+-----------+---------+---------------------+------+---------------------+---------+
| 1 | Create users | 2024-03-15 10:00 | Done | SQL | 1234abcd |
| 2 | Add email | Pending | | SQL | 5678efgh |
+-----------+---------+---------------------+------+---------------------+---------+


2.3. flyway validate

Проверяет:
Целостность скриптов (не изменены ли уже примененные).
Корректность именования.


Ошибка:
Validation failed: Detected applied migration not resolved locally: 1
Причина: Локальный скрипт V1__... был изменен после применения к БД.


2.4. flyway clean

Удаляет все объекты БД (таблицы, процедуры), включая flyway_schema_history.
Опасность: Необратимая операция. Используется только в dev-среде.

flyway clean


3. Форматы миграций

3.1. SQL-миграции

Стандартный подход. Скрипты с расширением .sql в директории db/migration.

Пример (V1__Create_users.sql):
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50) NOT NULL
);

INSERT INTO users (id, username) VALUES (1, 'admin');


3.2. Java-миграции

Для сложной логики (например, условные операции).


Шаги:
Создайте класс в проекте:
public class V2__InsertTestData implements JdbcMigration {
public void migrate(Connection connection) throws Exception {
try (Statement stmt = connection.createStatement()) {
stmt.execute("INSERT INTO users (username) VALUES ('test')");
}
}
}


Укажите путь к Java-классам в flyway.locations:
flyway.locations=classpath:db/migration,classpath:com/example/migrations


3.3. Callbacks (SQL-хуки)

Скрипты, выполняемые до/после событий Flyway.

Пример (beforeMigrate.sql):
-- Выполняется перед началом миграций
CREATE SCHEMA IF NOT EXISTS audit;


Поддерживаемые события:

beforeMigrate, afterMigrate.
beforeClean, afterClean.


Директория:
db/
migration/
V1__...sql
callback/
beforeMigrate.sql


4. Важные замечания

Именование файлов:
Регистрозависимо (V1__test.sql ≠ v1__test.sql).
Разделитель — двойное подчеркивание (__).


Порядок выполнения:
Сначала V1__..., затем V2__....
R__... выполняются после всех версионных.


Безопасность:

Никогда не изменяйте уже примененные скрипты.
Для отката создавайте новые миграции (например, V3__Drop_table.sql).


#Java #middle #Flyway
Типы миграций и версионирование в Flyway

1. Структура SQL-миграций

Flyway классифицирует SQL-миграции по префиксам в именах файлов, определяя их поведение при выполнении.

1.1. Именование файлов

Версионные миграции (Versioned)

Префикс V + версия + описание:
V<Версия>__<Описание>.sql


Пример:
-- V1__Create_users_table.sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL
);


Повторяемые миграции (Repeatable)

Префикс R + описание:
R__<Описание>.sql


Пример:
-- R__Update_user_view.sql
CREATE OR REPLACE VIEW user_view AS
SELECT id, username FROM users;


2. Java-миграции

Позволяют описывать миграции на Java для сложной логики, недоступной в SQL.

2.1. Реализация интерфейсов


JdbcMigration (устаревший):
public class V2__InsertTestData implements JdbcMigration {
@Override
public void migrate(Connection connection) throws Exception {
try (Statement stmt = connection.createStatement()) {
stmt.execute("INSERT INTO users (username) VALUES ('admin')");
}
}
}


JavaMigration (рекомендуемый):
public class V3__AddAdminUser implements JavaMigration {
@Override
public void migrate(Context context) throws Exception {
try (Statement stmt = context.getConnection().createStatement()) {
stmt.execute("INSERT INTO users (username) VALUES ('superuser')");
}
}
}


2.2. Правила

Классы должны находиться в classpath (обычно src/main/java/db/migration).
Имя класса соответствует имени файла SQL-миграции (например, V2__InsertTestData.java).

3. Правила версионирования

3.1. Семантическое версионирование

Рекомендуемый формат версии:
V<Мажор>.<Минор>.<Патч>__<Описание>.sql


Пример:
-- V1.2.3__Fix_email_constraint.sql
ALTER TABLE users ALTER COLUMN email SET NOT NULL;


Преимущества:
Понятная история изменений.
Совместимость с Semantic Versioning.


3.2. Откат изменений

Flyway не поддерживает автоматический откат. Альтернативы:

Новая миграция для отмены изменений:
-- V3__Drop_users_table.sql
DROP TABLE users;


Callbacks (SQL-хуки):
Скрипты beforeMigrate/afterMigrate для резервного копирования.

Важно: Не изменяйте уже примененные миграции — это нарушит целостность flyway_schema_history.

4. Примеры

4.1. SQL-миграции
-- V1__Initial_schema.sql
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100)
);

-- R__Update_view.sql
CREATE OR REPLACE VIEW active_users AS
SELECT * FROM users WHERE deleted_at IS NULL;


4.2. Java-миграция

public class V4__EncryptPasswords implements JavaMigration {
@Override
public void migrate(Context context) throws Exception {
try (PreparedStatement ps = context.getConnection()
.prepareStatement("UPDATE users SET password = encrypt(password)")) {
ps.executeUpdate();
}
}
}


#Java #middle #Flyway