Откаты (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
Продолжаем выбирать темы для разбора и голосовать за рассмотрение предложенных! 🤓
Голосуем за тему к рассмотрению в эти выходные!
Выбираем новую тему!
(можете предложить что-то из того, что предлагали на прошлой и позапрошлых неделях и что проиграло в голосовании!)
Не стесняемся!✌️
Голосуем за тему к рассмотрению в эти выходные!
Выбираем новую тему!
(можете предложить что-то из того, что предлагали на прошлой и позапрошлых неделях и что проиграло в голосовании!)
Не стесняемся!
Please open Telegram to view this post
VIEW IN TELEGRAM
Что выведет код?
#Tasks
public class Task040525 {
public static void main(String[] args) {
System.out.println('A' + 1.5F + "Java");
}
}
#Tasks
Пагинация, которую начинаешь ненавидеть 😵
Сегодня вновь хочу рассказать о неочевидном поведении Spring Data JPA, которое хоть и встречается нечасто, но может попортить нервы.
Давайте представим, что есть связанные сущности(код условный, никакого реального совпадения):
И есть бизнес-задача: Провести сортировку (SortBy в Pageable) по полям вложенных сущностей (т.е. по workstation.number и department.name).
🙅♂️ Как хочется сделать
Как минимум все по стандарту:
И выглядит вроде бы все пристойно.
Но! Как всегда в магии Spring'а есть НО✌️
Для связи @OneToOne (Workstation.number) сортировка работает, потому что это прямое простое соединение (join), и JPA может легко построить корректный SQL-запрос:
❗️ Но, для связи @OneToMany или @ManyToOne (department.name) сортировка не работает❗️
Потому что:
Это коллекционная ассоциация
При соединении таблиц может возникать несколько записей для одной основной сущности
JPA/Hibernate не может автоматически разрешить, как именно нужно применять сортировку в таком случае и не делает НИЧЕГО🙄
🆗 Как надо сделать
Явный JOIN + ORDER BY в JPQL
Явное указание, как соединять таблицы и ORDER BY до пагинации как минимум решат все проблемы🏝
Использование JOIN FETCH (если нужны данные сразу)
Тот же вариант что и выше, но если не жалко памяти🆘
Specification API
Вариант для любителей пожесче🧑💻
Как не сойти с ума при поиске подобных ошибок?🤪
Всегда, на стадии разработки и тестирования включайте SQL в логах (spring.jpa.show-sql=true) и будет Вам счастие.
Помните - если сортировка не применяется – валите всё на JOIN.
Понравился стиль подачи материала?
Отправь другу и ставь - 🔥
Если бывало такое -❤️
#Java #join #autor
Сегодня вновь хочу рассказать о неочевидном поведении Spring Data JPA, которое хоть и встречается нечасто, но может попортить нервы.
Давайте представим, что есть связанные сущности(код условный, никакого реального совпадения):
@Entity
@Data
public class Employee {
@Id
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "workstation_id")
private Workstation workstation;
@ManyToOne
@JoinColumn(name = "department_id")
private Department department;
}
@Entity
@Data
public class Workstation {
@Id
private Long id;
private String number; // Поле для сортировки
}
@Entity
@Data
public class Department {
@Id
private Long id;
private String name; // Поле для сортировки
}
И есть бизнес-задача: Провести сортировку (SortBy в Pageable) по полям вложенных сущностей (т.е. по workstation.number и department.name).
Как минимум все по стандарту:
@Query("SELECT e FROM Employee e WHERE e.name = :name")
Page<Employee> findByName(@Param("name") String name, Pageable pageable)
Естественно поля могут передаваться таким образом:
Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.fromString("workstation.number"));
или
Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.fromString("department.name"));
И выглядит вроде бы все пристойно.
Но! Как всегда в магии Spring'а есть НО
Для связи @OneToOne (Workstation.number) сортировка работает, потому что это прямое простое соединение (join), и JPA может легко построить корректный SQL-запрос:
SELECT e.* FROM employee e
JOIN workstation w ON e.workstation_id = w.id
WHERE e.name = 'John'
ORDER BY w.number ASC -- Сортировка применяется
Потому что:
Это коллекционная ассоциация
При соединении таблиц может возникать несколько записей для одной основной сущности
JPA/Hibernate не может автоматически разрешить, как именно нужно применять сортировку в таком случае и не делает НИЧЕГО
Явный JOIN + ORDER BY в JPQL
@Query("SELECT e FROM Employee e " +
"LEFT JOIN e.department d " + // Явное соединение
"WHERE e.name = :name " +
"ORDER BY d.name") // Сортировка в запросе(но не обязательно)
Page<Employee> findByNameWithDepartmentSort(
@Param("name") String name,
Pageable pageable);
Явное указание, как соединять таблицы и ORDER BY до пагинации как минимум решат все проблемы
Использование JOIN FETCH (если нужны данные сразу)
@Query("SELECT DISTINCT e FROM Employee e " +
"LEFT JOIN FETCH e.department d " + // Загружаем department сразу
"WHERE e.name = :name " +
"ORDER BY d.name")
Page<Employee> findByNameWithDepartmentFetch(
@Param("name") String name,
Pageable pageable);
Тот же вариант что и выше, но если не жалко памяти
Specification API
public interface EmployeeRepository extends
JpaRepository<Employee, Long>,
JpaSpecificationExecutor<Employee> { }
// В сервисе:
Specification<Employee> spec = (root, query, cb) -> {
Join<Employee, Department> department = root.join("department");
return cb.equal(root.get("name"), name);
};
Sort sort = Sort.by("department.name").ascending();
Pageable pageable = PageRequest.of(0, 10, sort);
Page<Employee> result = repository.findAll(spec, pageable);
Вариант для любителей пожесче
Как не сойти с ума при поиске подобных ошибок?
Всегда, на стадии разработки и тестирования включайте SQL в логах (spring.jpa.show-sql=true) и будет Вам счастие.
Помните - если сортировка не применяется – валите всё на JOIN.
Понравился стиль подачи материала?
Отправь другу и ставь - 🔥
Если бывало такое -
#Java #join #autor
Please open Telegram to view this post
VIEW IN TELEGRAM
Что такое ExecutorService? 🤓
Ответ:
ExecutorService - это интерфейс в Java, который представляет асинхронный механизм выполнения задач. Он является частью фреймворка java.util.concurrent и предоставляет мощные возможности для управления потоками и выполнения задач в многопоточной среде .
Основные возможности ExecutorService:
🔵 Управление пулом потоков
🔵 Асинхронное выполнение задач (Runnable и Callable)
🔵 Контроль жизненного цикла сервиса
🔵 Возможность получения результатов выполнения (Future)
🔵 Запуск группы задач и ожидание их завершения
#собеседование
Ответ:
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
Ссылочные типы данных в Java
Ссылочные типы данных (reference types) являются неотъемлемой частью объектно-ориентированной модели Java. В отличие от примитивов, они представляют объекты и хранят ссылки (адреса) на область памяти, где находится фактическое содержимое.
1. Концепция и философия
Java — строго объектно-ориентированный язык, и ссылки — основа его динамической природы.
Зачем нужны ссылочные типы:
Инкапсуляция данных и поведения. Объекты объединяют данные и методы, действующие над ними.
Гибкость. Ссылочные типы позволяют моделировать сложные структуры (например, графы, деревья, коллекции).
Полиморфизм и наследование. Через ссылки возможна работа с объектами по интерфейсам и абстрактным типам.
2. Представление и поведение ссылок
Ссылочная переменная не содержит сам объект, а только указатель на него.
Пример с мутабельным объектом:
3. Классы, интерфейсы и массивы
К ссылочным типам относятся:
Классы (например, String, Object, Scanner, ArrayList)
Интерфейсы (например, List, Runnable, Serializable)
Массивы (int[], String[], Object[][]) — в Java массивы — это объекты!
Специальный тип null — означает отсутствие объекта
4. Работа JVM с ссылками
При работе со ссылочными типами:
Объекты размещаются в куче (heap).
Ссылки могут храниться в стеке вызовов, в полях объектов, в массиве и т.д.
JVM использует сборку мусора (GC) для очистки неиспользуемых объектов.
5. Особенности ссылочных типов
a) Сравнение ссылок
b) NullPointerException
6. Автоматическая работа с памятью
Одно из главных преимуществ Java — автоматическое управление памятью. Не нужно вручную освобождать объекты, как в C/C++.
Но важно помнить:
Объекты "висят" в памяти, пока есть активные ссылки.
Циклические ссылки не мешают сборке мусора (в отличие от простых reference counting-систем).
7. Массивы как ссылочные типы
Массивы в Java — полноценные объекты.
8. Использование в обобщениях
Ссылочные типы активно применяются в дженериках (обобщениях).
Нельзя использовать примитивы в параметрах типа:
9. Влияние на производительность
Работа со ссылками может быть дороже, чем с примитивами:
Дополнительные обращения к памяти
Участие в GC
Наличие виртуального вызова методов (vtable)
Частые создания объектов (например, при упаковке/распаковке)
Оптимизации:
Избегать ненужных аллокаций
Использовать StringBuilder вместо + в циклах
Применять ObjectPool, если объекты часто переиспользуются
#Java #для_новичков #beginner #reference_types
Ссылочные типы данных (reference types) являются неотъемлемой частью объектно-ориентированной модели Java. В отличие от примитивов, они представляют объекты и хранят ссылки (адреса) на область памяти, где находится фактическое содержимое.
1. Концепция и философия
Java — строго объектно-ориентированный язык, и ссылки — основа его динамической природы.
Зачем нужны ссылочные типы:
Инкапсуляция данных и поведения. Объекты объединяют данные и методы, действующие над ними.
Гибкость. Ссылочные типы позволяют моделировать сложные структуры (например, графы, деревья, коллекции).
Полиморфизм и наследование. Через ссылки возможна работа с объектами по интерфейсам и абстрактным типам.
List<String> names = new ArrayList<>(); // ссылка на объект ArrayList
2. Представление и поведение ссылок
Ссылочная переменная не содержит сам объект, а только указатель на него.
String a = "Hello";
String b = a;
Обе переменные a и b ссылаются на один и тот же объект "Hello" в куче (heap). Изменение объекта (если он мутабелен) через одну ссылку отразится на другой.
Пример с мутабельным объектом:
List<Integer> list1 = new ArrayList<>();
List<Integer> list2 = list1;
list1.add(10);
System.out.println(list2); // [10]
3. Классы, интерфейсы и массивы
К ссылочным типам относятся:
Классы (например, String, Object, Scanner, ArrayList)
Интерфейсы (например, List, Runnable, Serializable)
Массивы (int[], String[], Object[][]) — в Java массивы — это объекты!
Специальный тип null — означает отсутствие объекта
4. Работа JVM с ссылками
При работе со ссылочными типами:
Объекты размещаются в куче (heap).
Ссылки могут храниться в стеке вызовов, в полях объектов, в массиве и т.д.
JVM использует сборку мусора (GC) для очистки неиспользуемых объектов.
class User {
String name;
}
User user1 = new User();
User user2 = user1;
user1 = null; // объект всё ещё доступен через user2
5. Особенности ссылочных типов
a) Сравнение ссылок
String a = new String("Java");
String b = new String("Java");
System.out.println(a == b); // false — разные объекты
System.out.println(a.equals(b)); // true — сравнение содержимого
== сравнивает ссылки, а equals() — содержимое (если метод переопределён корректно).
b) NullPointerException
String s = null;
System.out.println(s.length()); // исключение
Поэтому рекомендуется использовать Objects.requireNonNull, Optional, или делать явные проверки на null.
6. Автоматическая работа с памятью
Одно из главных преимуществ Java — автоматическое управление памятью. Не нужно вручную освобождать объекты, как в C/C++.
Но важно помнить:
Объекты "висят" в памяти, пока есть активные ссылки.
Циклические ссылки не мешают сборке мусора (в отличие от простых reference counting-систем).
7. Массивы как ссылочные типы
Массивы в Java — полноценные объекты.
int[] nums = {1, 2, 3};
System.out.println(nums.length); // 3
Массивы имеют поля и методы (length — поле, а не метод), и их тип — int[], String[] и т.д.
8. Использование в обобщениях
Ссылочные типы активно применяются в дженериках (обобщениях).
List<Integer> list = new ArrayList<>();
Нельзя использовать примитивы в параметрах типа:
List<int> wrongList; // ошибка
Поэтому применяются классы-обёртки: Integer, Double, Boolean и т.д.
9. Влияние на производительность
Работа со ссылками может быть дороже, чем с примитивами:
Дополнительные обращения к памяти
Участие в GC
Наличие виртуального вызова методов (vtable)
Частые создания объектов (например, при упаковке/распаковке)
Оптимизации:
Избегать ненужных аллокаций
Использовать StringBuilder вместо + в циклах
Применять ObjectPool, если объекты часто переиспользуются
#Java #для_новичков #beginner #reference_types
Вы еще читаете этот канал? 🤨
Anonymous Poll
55%
Да! Каждый день, все интересно, стараюсь не пропускать 🙂
26%
Да, периодически встречается что-то интересное 🤷♀️
17%
Редко, но заглядываю 🤫
0%
Канал больше не интересен, практически не захожу 👎
0%
Я сам(а) не понимаю зачем нахожусь в этом канале, пожалуй выйду 🤢
2%
Что это за канал??? Почему я это читаю 😨
Что выведет код?
#Tasks
public class Task050625 {
public static void main(String[] args) {
Object[] objects = new Object[3];
objects[0] = 10;
objects[1] = "Hello";
objects[2] = new int[]{1, 2, 3};
System.out.println(objects[0] instanceof Number);
System.out.println(objects[1] instanceof CharSequence);
System.out.println(objects[2] instanceof Object);
}
}
#Tasks
Варианты ответа:
Anonymous Quiz
16%
true true false
8%
false true true
56%
true true true
20%
Ошибка выполнения
Продолжаем выбирать темы для разбора и голосовать за рассмотрение предложенных! 🤓
Голосуем за тему к рассмотрению в эти выходные!
Выбираем новую тему!
(можете предложить что-то из того, что предлагали на прошлой и позапрошлых неделях и что проиграло в голосовании!)
Не стесняемся!✌️
Голосуем за тему к рассмотрению в эти выходные!
Выбираем новую тему!
(можете предложить что-то из того, что предлагали на прошлой и позапрошлых неделях и что проиграло в голосовании!)
Не стесняемся!
Please open Telegram to view this post
VIEW IN TELEGRAM
Что такое StringBuilder и чем он отличается от String? 🤓
Ответ:
String — неизменяемый (immutable) класс. При конкатенации создается новый объект.
StringBuilder — изменяемый, эффективен для многократных изменений строки, так как не создает новые объекты.
#собеседование
Ответ:
StringBuilder — изменяемый, эффективен для многократных изменений строки, так как не создает новые объекты.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
Интеграция 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