Преобразование между JsonElement и Java-объектами
Использование fromJson и toJsonTree
Из JSON в POJO (fromJson)
Из POJO в JsonElement (toJsonTree)
Как конвертировать JsonElement обратно в POJO
Полезные советы
Проверка isJsonNull() перед чтением значений
Обработка разных типов полей
Использование JsonParser для разбора строки
Создание сложных JSON структур
Эти методы позволяют гибко работать с JSON, когда точная структура неизвестна или требуется динамическая модификация данных.
#Java #middle #Gson #JsonElement #JsonObject #JsonArray
Использование fromJson и toJsonTree
Из JSON в POJO (fromJson)
Gson gson = new Gson();
String json = "{\"name\":\"Bob\",\"age\":40}";
// Прямое преобразование строки в объект
Person person = gson.fromJson(json, Person.class);
System.out.println(person.getName()); // Bob
// Если есть JsonElement, можно использовать его
JsonElement jsonElement = JsonParser.parseString(json);
Person personFromElement = gson.fromJson(jsonElement, Person.class);
Из POJO в JsonElement (toJsonTree)
Person person = new Person("Bob", 40);
JsonElement personElement = gson.toJsonTree(person);
// Теперь можно модифицировать JSON
if (personElement.isJsonObject()) {
JsonObject personObject = personElement.getAsJsonObject();
personObject.addProperty("email", "bob@example.com");
}
String modifiedJson = gson.toJson(personElement);
System.out.println(modifiedJson);
Как конвертировать JsonElement обратно в POJO
JsonObject userJson = new JsonObject();
userJson.addProperty("name", "Eve");
userJson.addProperty("age", 28);
// Преобразование JsonObject в POJO
Gson gson = new Gson();
Person person = gson.fromJson(userJson, Person.class);
System.out.println(person.getName()); // Eve
Полезные советы
Проверка isJsonNull() перед чтением значений
JsonElement element = jsonObject.get("optionalField");
if (element != null && !element.isJsonNull()) {
String value = element.getAsString();
}
Обработка разных типов полей
JsonElement dynamicField = jsonObject.get("dynamicField");
if (dynamicField.isJsonPrimitive()) {
JsonPrimitive primitive = dynamicField.getAsJsonPrimitive();
if (primitive.isString()) {
// Обработка строки
} else if (primitive.isNumber()) {
// Обработка числа
}
} else if (dynamicField.isJsonArray()) {
// Обработка массива
}
Использование JsonParser для разбора строки
JsonElement parsed = JsonParser.parseString(jsonString);
Создание сложных JSON структур
JsonObject response = new JsonObject();
JsonArray items = new JsonArray();
JsonObject item1 = new JsonObject();
item1.addProperty("id", 1);
item1.addProperty("name", "Item 1");
items.add(item1);
response.add("items", items);
response.addProperty("count", 1);
String jsonOutput = gson.toJson(response);
Эти методы позволяют гибко работать с JSON, когда точная структура неизвестна или требуется динамическая модификация данных.
#Java #middle #Gson #JsonElement #JsonObject #JsonArray
Введение в Liquibase
1. Что такое Liquibase и зачем он нужен
Liquibase — это инструмент для управления изменениями в структуре базы данных (миграциями).
Он позволяет:
Контролировать эволюцию схемы БД через версионированные скрипты.
Автоматизировать применение изменений (создание таблиц, изменение столбцов и т. д.).
Обеспечивать консистентность между разными окружениями (dev, test, prod).
Поддерживать откат изменений (rollback) в случае ошибок.
Проблемы, которые решает Liquibase:
Ручное выполнение SQL-скриптов на разных серверах.
Отсутствие истории изменений БД.
Несовместимость версий схемы БД между разработчиками.
2. Поддерживаемые СУБД
Liquibase работает с большинством популярных баз данных:
Реляционные: PostgreSQL, MySQL, Oracle, SQL Server, H2, SQLite.
NoSQL: MongoDB (с ограниченной поддержкой).
Облачные: Amazon RDS, Google Cloud SQL.
3. Принципы работы
Changelog (журнал изменений)
Это главный файл, который ссылается на все изменения (changeSet’ы). Хранится в формате XML, YAML, JSON или SQL.
ChangeSet (набор изменений)
Минимальная единица изменения в Liquibase.
Каждый changeSet:
Имеет уникальный идентификатор (id + author).
Описывает одно или несколько изменений (например, создание таблицы).
Может содержать атрибуты (runOnChange, failOnError).
Контроль версий
Liquibase ведет таблицы в БД:
DATABASECHANGELOG — журнал примененных changeSet’ов.
DATABASECHANGELOGLOCK — блокировка для предотвращения конфликтов.
4. Форматы changelog-файлов
XML — строгая структура, но многословный.
YAML — лаконичный, но чувствителен к отступам.
SQL — прост для DBA, но менее гибкий.
5. Интеграция Liquibase в Java-проект (Maven/Gradle)
Maven
Добавить в pom.xml:
Конфигурация в application.properties (Spring Boot):
Gradle
Добавить в build.gradle:
#Java #middle #Liquibase
1. Что такое Liquibase и зачем он нужен
Liquibase — это инструмент для управления изменениями в структуре базы данных (миграциями).
Он позволяет:
Контролировать эволюцию схемы БД через версионированные скрипты.
Автоматизировать применение изменений (создание таблиц, изменение столбцов и т. д.).
Обеспечивать консистентность между разными окружениями (dev, test, prod).
Поддерживать откат изменений (rollback) в случае ошибок.
Проблемы, которые решает Liquibase:
Ручное выполнение SQL-скриптов на разных серверах.
Отсутствие истории изменений БД.
Несовместимость версий схемы БД между разработчиками.
2. Поддерживаемые СУБД
Liquibase работает с большинством популярных баз данных:
Реляционные: PostgreSQL, MySQL, Oracle, SQL Server, H2, SQLite.
NoSQL: MongoDB (с ограниченной поддержкой).
Облачные: Amazon RDS, Google Cloud SQL.
3. Принципы работы
Changelog (журнал изменений)
Это главный файл, который ссылается на все изменения (changeSet’ы). Хранится в формате XML, YAML, JSON или SQL.
ChangeSet (набор изменений)
Минимальная единица изменения в Liquibase.
Каждый changeSet:
Имеет уникальный идентификатор (id + author).
Описывает одно или несколько изменений (например, создание таблицы).
Может содержать атрибуты (runOnChange, failOnError).
Контроль версий
Liquibase ведет таблицы в БД:
DATABASECHANGELOG — журнал примененных changeSet’ов.
DATABASECHANGELOGLOCK — блокировка для предотвращения конфликтов.
4. Форматы changelog-файлов
XML — строгая структура, но многословный.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.6.xsd">
<changeSet id="1" author="alex">
<createTable tableName="users">
<column name="id" type="INT" autoIncrement="true">
<constraints primaryKey="true"/>
</column>
<column name="username" type="VARCHAR(50)"/>
</createTable>
</changeSet>
</databaseChangeLog>
YAML — лаконичный, но чувствителен к отступам.
databaseChangeLog:
- changeSet:
id: 1
author: alex
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: INT
autoIncrement: true
constraints:
primaryKey: true
- column:
name: username
type: VARCHAR(50)
SQL — прост для DBA, но менее гибкий.
--liquibase formatted sql
--changeset alex:1
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50)
);
5. Интеграция Liquibase в Java-проект (Maven/Gradle)
Maven
Добавить в pom.xml:
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>4.20.0</version>
</dependency>
Конфигурация в application.properties (Spring Boot):
spring.liquibase.change-log=classpath:db/changelog/db.changelog-master.yaml
spring.liquibase.url=jdbc:postgresql://localhost:5432/mydb
spring.liquibase.user=user
spring.liquibase.password=pass
Gradle
Добавить в build.gradle:
implementation 'org.liquibase:liquibase-core:4.20.0'
#Java #middle #Liquibase
Структура и основные команды Liquibase
1. Файл конфигурации (liquibase.properties)
Файл liquibase.properties содержит настройки для подключения к БД и управления Liquibase.
Основные параметры:
Разбор параметров:
url – JDBC-URL базы данных (зависит от СУБД).
username и password – учетные данные для подключения.
driver – класс JDBC-драйвера (например, org.postgresql.Driver для PostgreSQL).
changeLogFile – путь к главному файлу changelog.
Где размещается?
В корне проекта (рядом с pom.xml/build.gradle).
Или указывается явно при запуске:
2. Основные команды CLI
Liquibase предоставляет консольные команды для управления миграциями.
update – применение изменений
Применяет все невыполненные changeSet’ы из changelog.
Что происходит?
Liquibase проверяет таблицу DATABASECHANGELOG.
Находит changeSet’ы, которых нет в этой таблице.
Применяет их в порядке указания в changelog.
rollback – откат изменений
Возвращает БД к предыдущему состоянию.
status – проверка состояния БД
Показывает, какие changeSet’ы не применены.
Вывод:
validate – проверка корректности changelog
Проверяет синтаксис changelog без применения изменений.
Если есть ошибки, выведет сообщение, например:
3. Жизненный цикл изменений
Как Liquibase применяет changeSet’ы?
Парсинг changelog:
Liquibase читает главный файл (например, db.changelog-master.xml).
Загружает все вложенные changeSet’ы.
Проверка DATABASECHANGELOG:
Сравнивает список changeSet’ов с теми, что уже выполнены (хранятся в таблице DATABASECHANGELOG).
Применение изменений:
Невыполненные changeSet’ы применяются в порядке их объявления.
После успешного выполнения информация о changeSet’е записывается в DATABASECHANGELOG.
Контроль версий и порядок выполнения
Уникальность changeSet’а определяется по:
id (например, "1").
author (например, "alex").
Путь к файлу changelog.
Порядок выполнения:
Liquibase выполнит 001-create-users.xml раньше, чем 002-add-email.xml.
Атрибуты, влияющие на выполнение:
runOnChange="true" – повторно выполнит changeSet, если его содержимое изменилось.
failOnError="false" – пропустит ошибку (например, если таблица уже существует).
#Java #middle #Liquibase
1. Файл конфигурации (liquibase.properties)
Файл liquibase.properties содержит настройки для подключения к БД и управления Liquibase.
Основные параметры:
# Подключение к БД
url=jdbc:postgresql://localhost:5432/mydb
username=user
password=pass
driver=org.postgresql.Driver
# Настройки changelog
changeLogFile=db/changelog/db.changelog-master.xml
# Дополнительные параметры
liquibase.hub.mode=off # Отключение Liquibase Hub (если не используется)
Разбор параметров:
url – JDBC-URL базы данных (зависит от СУБД).
username и password – учетные данные для подключения.
driver – класс JDBC-драйвера (например, org.postgresql.Driver для PostgreSQL).
changeLogFile – путь к главному файлу changelog.
Где размещается?
В корне проекта (рядом с pom.xml/build.gradle).
Или указывается явно при запуске:
liquibase --defaults-file=config/liquibase.properties update
2. Основные команды CLI
Liquibase предоставляет консольные команды для управления миграциями.
update – применение изменений
Применяет все невыполненные changeSet’ы из changelog.
liquibase update
Что происходит?
Liquibase проверяет таблицу DATABASECHANGELOG.
Находит changeSet’ы, которых нет в этой таблице.
Применяет их в порядке указания в changelog.
rollback – откат изменений
Возвращает БД к предыдущему состоянию.
# Откат до тега v1.0
liquibase rollback v1.0
# Откат последнего changeSet’а
liquibase rollbackCount 1
# Откат до определенной даты
liquibase rollbackToDate 2024-01-01
status – проверка состояния БД
Показывает, какие changeSet’ы не применены.
liquibase status
Вывод:
2 changesets have not been applied to mydb@jdbc:postgresql://localhost:5432/mydb
validate – проверка корректности changelog
Проверяет синтаксис changelog без применения изменений.
liquibase validate
Если есть ошибки, выведет сообщение, например:
ERROR: ChangeSet db/changelog/changes/001-create-table.xml::1::alex failed. Reason: Table 'users' already exists
3. Жизненный цикл изменений
Как Liquibase применяет changeSet’ы?
Парсинг changelog:
Liquibase читает главный файл (например, db.changelog-master.xml).
Загружает все вложенные changeSet’ы.
Проверка DATABASECHANGELOG:
Сравнивает список changeSet’ов с теми, что уже выполнены (хранятся в таблице DATABASECHANGELOG).
Применение изменений:
Невыполненные changeSet’ы применяются в порядке их объявления.
После успешного выполнения информация о changeSet’е записывается в DATABASECHANGELOG.
Контроль версий и порядок выполнения
Уникальность changeSet’а определяется по:
id (например, "1").
author (например, "alex").
Путь к файлу changelog.
Порядок выполнения:
<databaseChangeLog>
<include file="db/changelog/changes/001-create-users.xml"/>
<include file="db/changelog/changes/002-add-email.xml"/>
</databaseChangeLog>
Liquibase выполнит 001-create-users.xml раньше, чем 002-add-email.xml.
Атрибуты, влияющие на выполнение:
runOnChange="true" – повторно выполнит changeSet, если его содержимое изменилось.
failOnError="false" – пропустит ошибку (например, если таблица уже существует).
#Java #middle #Liquibase
Типы changesets и стратегии развертывания в Liquibase
1. Основные изменения (changesets)
1.1. Создание таблиц (createTable)
Позволяет создавать новые таблицы с указанием столбцов и ограничений.
Пример (YAML):
Разбор:
tableName – имя таблицы.
columns – список столбцов.
constraints – ограничения (primaryKey, nullable, unique).
1.2. Добавление столбцов (addColumn)
Добавляет новый столбец в существующую таблицу.
Пример (YAML):
Параметры:
tableName – имя таблицы.
defaultValue – значение по умолчанию (опционально).
1.3. Изменение структуры (alterTable, modifyDataType)
Используется для изменения типа данных столбца или переименования таблицы.
Пример (YAML):
2. Управление зависимостями
2.1. Атрибуты runAlways и runOnChange
runAlways="true" – changeSet выполняется каждый раз, даже если уже был применен.
2.2. Условия (preConditions)
Проверяют условия перед выполнением changeSet’а.
Пример (YAML):
Доступные проверки:
tableExists / tableNotExists
columnExists
sqlCheck (произвольный SQL-запрос).
3. Стратегии развертывания
3.1. Последовательное применение
ChangeSet’ы выполняются строго по порядку, указанному в databaseChangeLog.
Пример:
3.2. Параллельное выполнение
Liquibase не поддерживает параллельное выполнение changeSet’ов по умолчанию.
Однако можно:
Разделять changelog на независимые модули.
Использовать contexts для условного выполнения.
Пример:
4. Поддержка разных форматов
Liquibase позволяет использовать:
YAML – лаконичный и удобный для разработчиков.
XML – строгий и структурированный.
JSON – альтернатива YAML.
SQL – для прямого написания SQL-запросов.
Пример SQL-формата:
#Java #middle #Liquibase
1. Основные изменения (changesets)
1.1. Создание таблиц (createTable)
Позволяет создавать новые таблицы с указанием столбцов и ограничений.
Пример (YAML):
databaseChangeLog:
- changeSet:
id: 1
author: dev
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: BIGINT
autoIncrement: true
constraints:
primaryKey: true
nullable: false
- column:
name: username
type: VARCHAR(50)
constraints:
unique: true
Разбор:
tableName – имя таблицы.
columns – список столбцов.
constraints – ограничения (primaryKey, nullable, unique).
1.2. Добавление столбцов (addColumn)
Добавляет новый столбец в существующую таблицу.
Пример (YAML):
- changeSet:
id: 2
author: dev
changes:
- addColumn:
tableName: users
columns:
- column:
name: email
type: VARCHAR(100)
constraints:
nullable: false
Параметры:
tableName – имя таблицы.
defaultValue – значение по умолчанию (опционально).
1.3. Изменение структуры (alterTable, modifyDataType)
Используется для изменения типа данных столбца или переименования таблицы.
Пример (YAML):
- changeSet:
id: 3
author: dev
changes:
- alterTable:
tableName: users
columns:
- column:
name: username
type: VARCHAR(100) # Увеличение длины
- renameColumn:
tableName: users
oldColumnName: email
newColumnName: email_address
2. Управление зависимостями
2.1. Атрибуты runAlways и runOnChange
runAlways="true" – changeSet выполняется каждый раз, даже если уже был применен.
- changeSet:
id: 4
author: dev
runAlways: true
changes:
- sql:
sql: "INSERT INTO logs (message) VALUES ('Migration executed')"
runOnChange="true" – выполняется только если содержимое changeSet’а изменилось.
2.2. Условия (preConditions)
Проверяют условия перед выполнением changeSet’а.
Пример (YAML):
- changeSet:
id: 5
author: dev
preConditions:
- tableExists:
tableName: users
changes:
- addColumn:
tableName: users
columns:
- column:
name: status
type: VARCHAR(20)
Доступные проверки:
tableExists / tableNotExists
columnExists
sqlCheck (произвольный SQL-запрос).
3. Стратегии развертывания
3.1. Последовательное применение
ChangeSet’ы выполняются строго по порядку, указанному в databaseChangeLog.
Пример:
databaseChangeLog:
- include:
file: db/changelog/v1.0/001-create-tables.yaml
- include:
file: db/changelog/v1.0/002-add-constraints.yaml
3.2. Параллельное выполнение
Liquibase не поддерживает параллельное выполнение changeSet’ов по умолчанию.
Однако можно:
Разделять changelog на независимые модули.
Использовать contexts для условного выполнения.
Пример:
- changeSet:
id: 6
author: dev
context: "!prod" # Не выполнять в production
changes:
- insert:
tableName: test_data
columns:
- column:
name: value
value: "Test"
4. Поддержка разных форматов
Liquibase позволяет использовать:
YAML – лаконичный и удобный для разработчиков.
XML – строгий и структурированный.
JSON – альтернатива YAML.
SQL – для прямого написания SQL-запросов.
Пример SQL-формата:
--liquibase formatted sql
--changeset dev:7
CREATE INDEX idx_user_email ON users(email);
--changeset dev:8 context:prod
ALTER TABLE users ADD COLUMN last_login TIMESTAMP;
#Java #middle #Liquibase
Откаты (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