Откаты (Rollback) и теги в Liquibase
Liquibase предоставляет мощные инструменты для отката изменений в базе данных, что критически важно при возникновении ошибок или необходимости вернуться к предыдущей версии схемы.
1. Типы откатов
1.1. Автоматический откат (на основе Liquibase)
Liquibase может автоматически генерировать команды отката для некоторых операций, если они указаны в changeSet.
Пример (YAML):
Как это работает?
При выполнении liquibase update применяется createTable.
При откате (rollback) выполнится dropTable.
Какие операции поддерживают автоматический откат?
createTable → dropTable
addColumn → dropColumn
createIndex → dropIndex
Ограничения:
Не все операции имеют автоматический откат (например, сложные ALTER TABLE).
Для них требуется ручной rollback.
1.2. Ручной откат (пользовательские SQL-скрипты)
Если Liquibase не может сгенерировать откат автоматически, его можно задать вручную.
Пример (YAML):
Когда использовать?
Для сложных SQL-запросов.
Если Liquibase не поддерживает автоматический откат для операции.
2. Теги (Tags)
Теги позволяют помечать определённое состояние базы данных, чтобы позже можно было к нему вернуться.
2.1. Создание тега (tag)
Пометка текущего состояния БД.
Команда:
Где хранится информация о тегах?
В таблице DATABASECHANGELOG (поле TAG).
2.2. Откат к тегу (rollback <tag>)
Возврат базы данных к состоянию на момент создания тега.
Команда:
Как это работает?
Liquibase находит тег v1.0 в DATABASECHANGELOG.
Определяет, какие changeSet’ы были выполнены после этого тега.
Применяет откаты в обратном порядке.
Пример (YAML):
3. Практические сценарии отката
3.1. Отмена последнего changeSet’а
Что произойдёт?
Liquibase откатит один последний применённый changeSet.
3.2. Восстановление до определённой версии
Вариант 1: По тегу
Вариант 2: По дате
Вариант 3: По количеству changeSet’ов
#Java #middle #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:
2. Применяем изменения:
3. Откатываемся до тега v1.0:
Результат:
Будет отменён changeSet id=2 (удалён столбец username).
Таблица users останется (так как changeSet id=1 был до тега).
#Java #middle #Liquibase
Откат не всегда возможен
Если 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:
Конфигурация через pom.xml:
Запуск миграций:
Gradle
Добавьте в build.gradle:
Запуск:
Spring Boot (автоконфигурация)
Spring Boot автоматически настраивает Liquibase, если он обнаружен в classpath.
Настройки в application.yml:
Поведение:
Миграции запускаются при старте приложения.
Можно отключить через spring.liquibase.enabled=false.
Расширенные функции Liquibase
Динамические свойства (property)
Позволяют выносить повторяющиеся значения в переменные.
Пример (YAML):
В liquibase.properties:
Через командную строку:
Контексты (contexts) и фильтрация changeSet’ов
Контексты позволяют выбирать, какие changeSet’ы применять в определённых окружениях.
Пример (YAML):
Как передать контекст?
Через командную строку:
В Spring Boot:
#Java #middle #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-файлов
Рекомендуемая структура:
Главный файл (db.changelog-master.yaml):
Почему так?
Удобно управлять версиями.
Легко находить изменения.
Тестирование миграций
Стратегии:
Локальная проверка:
Интеграционные тесты (JUnit + Testcontainers):
Работа в команде (избежание конфликтов)
Правила:
Именование changeSet’ов:
Используйте id: <дата>-<номер> (например, id: 20240315-1).
Указывайте автора (author: github_username).
Порядок изменений:
Не зависеть от порядка выполнения (например, не предполагать, что таблица A уже существует при создании B).
Использование preConditions:
Регулярные обновления:
Перед началом работы выполняйте liquibase update, чтобы получить актуальную схему.
#Java #middle #Liquibase
Организация 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.
Пример конфигурации:
Примечание: Зарегистрируйте приложение в Google Cloud Console и используйте библиотеку google-auth-library-oauth2-http для получения accessToken.
Создание сервиса отправки почты
Создадим сервис EmailService для отправки писем.
Конфигурация асинхронности:
#Java #middle #on_request #JavaMailSender
Для 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
Примеры кода
Простой текст:
HTML-письмо:
Вложение:
Обработка ошибок и логирование
JavaMailSender может выбрасывать исключения, которые нужно обрабатывать.
Основные исключения:
Пример отлова:
Повторные попытки
Используйте @Retryable из Spring Retry:
Зависимость:
Мониторинг и метрики
Для продакшен-приложений полезно отслеживать метрики отправки писем с помощью Spring Actuator:
Зависимость:
Безопасность и защита данных
Никогда не храните пароли в открытом виде в git-репозиториях.
Рекомендации:
Используйте переменные окружения или секреты (например, Kubernetes Secrets).
Применяйте Spring Cloud Vault или HashiCorp Vault.
Используйте шифрование с Jasypt.
Настройте OAuth2 для Gmail.
Пример с переменными окружения:
#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:
Зависимость:
Unit-тестирование:
Интеграция с очередями
Для отложенной отправки используйте RabbitMQ(ну или другой брокер сообщений):
Зависимость:
Распространенные ошибки и подводные камни
Неверная конфигурация SMTP (порт, шифрование).
Проблемы с двухфакторной авторизацией (особенно Gmail).
Ограничения на массовую рассылку (например, Gmail: ~500 писем/день для бесплатных аккаунтов).
Проблемы с кодировкой (например, кириллица в теме письма).
Отсутствие MIME-типа у вложений.
Блокировка сервером (например, Mail.ru требует DKIM и SPF).
Решение для кодировки:
#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-скрипта:
Повторяемые миграции (префикс 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 представляет собой специализированный инструмент управления миграциями баз данных, реализующий принцип "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-URL базы данных (формат зависит от СУБД).
flyway.locations — пути к миграциям:
classpath: — поиск в ресурсах проекта (например, src/main/resources/db/migration).
filesystem: — абсолютный путь на диске.
flyway.baselineOnMigrate — инициализация существующей БД (без таблицы flyway_schema_history).
1.2. Параметры командной строки
Настройки можно переопределять через аргументы:
2. Основные CLI-команды
2.1. flyway migrate
Применяет все невыполненные миграции.
Процесс:
Проверяет таблицу flyway_schema_history.
Находит скрипты, которых нет в истории.
Выполняет их в порядке версий (например, V1__... перед V2__...).
Пример:
2.2. flyway info
Показывает текущее состояние БД:
Какие миграции применены.
Какие ожидают выполнения.
Статус (Success, Pending, Failed).
Вывод:
2.3. flyway validate
Проверяет:
Целостность скриптов (не изменены ли уже примененные).
Корректность именования.
Ошибка:
2.4. flyway clean
Удаляет все объекты БД (таблицы, процедуры), включая flyway_schema_history.
Опасность: Необратимая операция. Используется только в dev-среде.
3. Форматы миграций
3.1. SQL-миграции
Стандартный подход. Скрипты с расширением .sql в директории db/migration.
Пример (V1__Create_users.sql):
3.2. Java-миграции
Для сложной логики (например, условные операции).
Шаги:
Создайте класс в проекте:
Укажите путь к Java-классам в flyway.locations:
3.3. Callbacks (SQL-хуки)
Скрипты, выполняемые до/после событий Flyway.
Пример (beforeMigrate.sql):
Поддерживаемые события:
beforeMigrate, afterMigrate.
beforeClean, afterClean.
Директория:
4. Важные замечания
Именование файлов:
Регистрозависимо (V1__test.sql ≠ v1__test.sql).
Разделитель — двойное подчеркивание (__).
Порядок выполнения:
Сначала V1__..., затем V2__....
R__... выполняются после всех версионных.
Безопасность:
Никогда не изменяйте уже примененные скрипты.
Для отката создавайте новые миграции (например, V3__Drop_table.sql).
#Java #middle #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 + версия + описание:
Пример:
Повторяемые миграции (Repeatable)
Префикс R + описание:
Пример:
2. Java-миграции
Позволяют описывать миграции на Java для сложной логики, недоступной в SQL.
2.1. Реализация интерфейсов
JdbcMigration (устаревший):
JavaMigration (рекомендуемый):
2.2. Правила
Классы должны находиться в classpath (обычно src/main/java/db/migration).
Имя класса соответствует имени файла SQL-миграции (например, V2__InsertTestData.java).
3. Правила версионирования
3.1. Семантическое версионирование
Рекомендуемый формат версии:
Пример:
Преимущества:
Понятная история изменений.
Совместимость с Semantic Versioning.
3.2. Откат изменений
Flyway не поддерживает автоматический откат. Альтернативы:
Новая миграция для отмены изменений:
Callbacks (SQL-хуки):
Скрипты beforeMigrate/afterMigrate для резервного копирования.
Важно: Не изменяйте уже примененные миграции — это нарушит целостность flyway_schema_history.
4. Примеры
4.1. SQL-миграции
4.2. Java-миграция
#Java #middle #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