Выбираем темы для рассмотрения в следующие выходные! 🤨
Anonymous Poll
32%
Optional
24%
AOP
29%
JSON
15%
IO/NIO
Please open Telegram to view this post
VIEW IN TELEGRAM
Типы миграций и версионирование в 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
Что выведет код?
#Tasks
import java.util.stream.IntStream;
public class Task160625 {
public static void main(String[] args) {
IntStream.rangeClosed(1, 5)
.filter(i -> i % 2 == 0)
.peek(System.out::print)
.forEachOrdered(i -> {});
}
}
#Tasks
Чем отличается ConcurrentHashMap от HashMap? 🤓
Ответ:
ConcurrentHashMap — потокобезопасная версия HashMap, предназначенная для многопоточных приложений.
Она использует сегментированную блокировку (lock striping), чтобы минимизировать конфликты. HashMap не потокобезопасна, и одновременный доступ из нескольких потоков может привести к ошибкам.
Пример:
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1); // Потокобезопасно
ConcurrentHashMap не блокирует всю структуру, а только отдельные сегменты.
#собеседование
Ответ:
Она использует сегментированную блокировку (lock striping), чтобы минимизировать конфликты. HashMap не потокобезопасна, и одновременный доступ из нескольких потоков может привести к ошибкам.
Пример:
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1); // Потокобезопасно
ConcurrentHashMap не блокирует всю структуру, а только отдельные сегменты.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
Ссылочные типы в Java — интерфейсы (interfaces)
Интерфейсы в Java — это ключевой механизм для реализации абстракции, полиморфизма и разделения ответственности. Они относятся к ссылочным типам и позволяют описывать поведение, не определяя конкретную реализацию. Интерфейсы широко применяются при проектировании архитектуры, обеспечивают гибкость, модульность и поддержку паттернов проектирования.
Понимание интерфейсов необходимо для эффективного взаимодействия с фреймворками, библиотеками и построения легко расширяемого кода.
Создание интерфейсов
Интерфейсы объявляются с помощью ключевого слова interface. Они содержат объявления методов без реализации (до Java 8) или с ограниченной реализацией (начиная с Java 8):
Класс, реализующий интерфейс, должен предоставить реализацию всех его методов:
Начиная с Java 8, интерфейсы могут содержать:
default-методы с реализацией
static-методы
private-методы (с Java 9)
Ключевые особенности:
Интерфейс не может содержать поля с реализацией — только public static final константы.
Все методы по умолчанию public abstract, если не указано иное.
Интерфейс — это тип, и его можно использовать как ссылку.
Использование интерфейсов
Интерфейсы позволяют описывать поведение без привязки к конкретной реализации:
Это даёт возможность:
использовать полиморфизм,
отделять абстракции от реализаций,
писать гибкий и расширяемый код.
Интерфейсы можно использовать как:
параметры методов
типы переменных
результаты возвращаемых значений
обобщенные типы
Также интерфейсы — неотъемлемая часть функционального программирования в Java 8+:
Существование и удаление
Поскольку интерфейсы — это ссылочные типы, они работают как любые другие объекты. Переменная типа интерфейса — это ссылка на объект, реализующий этот интерфейс.
Здесь animal — это ссылка на объект типа Dog, но доступ к нему осуществляется через интерфейс Animal. Удаление объекта происходит по общим правилам: когда все ссылки исчезают, он становится доступным для сборки мусора.
Полиморфизм через интерфейсы
Интерфейсы — один из основных инструментов реализации полиморфизма. Код, написанный против интерфейсов, легко расширяем:
#Java #для_новичков #beginner #reference_types #Interface
Интерфейсы в Java — это ключевой механизм для реализации абстракции, полиморфизма и разделения ответственности. Они относятся к ссылочным типам и позволяют описывать поведение, не определяя конкретную реализацию. Интерфейсы широко применяются при проектировании архитектуры, обеспечивают гибкость, модульность и поддержку паттернов проектирования.
Понимание интерфейсов необходимо для эффективного взаимодействия с фреймворками, библиотеками и построения легко расширяемого кода.
Создание интерфейсов
Интерфейсы объявляются с помощью ключевого слова interface. Они содержат объявления методов без реализации (до Java 8) или с ограниченной реализацией (начиная с Java 8):
public interface Animal {
void makeSound();
}
Класс, реализующий интерфейс, должен предоставить реализацию всех его методов:
public class Dog implements Animal {
public void makeSound() {
System.out.println("Woof");
}
}
Начиная с Java 8, интерфейсы могут содержать:
default-методы с реализацией
static-методы
private-методы (с Java 9)
public interface Logger {
default void log(String msg) {
System.out.println("Log: " + msg);
}
}
Ключевые особенности:
Интерфейс не может содержать поля с реализацией — только public static final константы.
Все методы по умолчанию public abstract, если не указано иное.
Интерфейс — это тип, и его можно использовать как ссылку.
Использование интерфейсов
Интерфейсы позволяют описывать поведение без привязки к конкретной реализации:
public void processAnimal(Animal animal) {
animal.makeSound(); // работает с любым классом, реализующим Animal
}
Это даёт возможность:
использовать полиморфизм,
отделять абстракции от реализаций,
писать гибкий и расширяемый код.
Интерфейсы можно использовать как:
параметры методов
типы переменных
результаты возвращаемых значений
обобщенные типы
Также интерфейсы — неотъемлемая часть функционального программирования в Java 8+:
@FunctionalInterface
public interface Converter<F, T> {
T convert(F from);
}
Существование и удаление
Поскольку интерфейсы — это ссылочные типы, они работают как любые другие объекты. Переменная типа интерфейса — это ссылка на объект, реализующий этот интерфейс.
Animal animal = new Dog();
Здесь animal — это ссылка на объект типа Dog, но доступ к нему осуществляется через интерфейс Animal. Удаление объекта происходит по общим правилам: когда все ссылки исчезают, он становится доступным для сборки мусора.
Полиморфизм через интерфейсы
Интерфейсы — один из основных инструментов реализации полиморфизма. Код, написанный против интерфейсов, легко расширяем:
List<String> list = new ArrayList<>();
Map<String, Integer> map = new HashMap<>();
Здесь List и Map — интерфейсы, а ArrayList и HashMap — конкретные реализации. Это позволяет заменить реализацию без изменения остального кода.
#Java #для_новичков #beginner #reference_types #Interface
Множественное наследование
Классы в Java не могут наследовать более одного класса, но могут реализовать несколько интерфейсов:
Трудности и подводные камни
1. Конфликт default-методов
Если класс реализует несколько интерфейсов, в которых есть default-методы с одинаковыми сигнатурами, возникает конфликт:
2. Отсутствие состояния
Интерфейсы не могут содержать нестатические поля, что ограничивает их в сравнении с абстрактными классами. Всё, что можно объявить — это public static final константы.
3. Непредсказуемое поведение equals/hashCode
Если объект используется через интерфейс, его поведение equals() и hashCode() может зависеть от реализации, что важно при использовании в коллекциях.
4. Ошибки при переопределении
Ошибки в сигнатурах при реализации интерфейса не всегда очевидны. Например, случайное переопределение метода с другой сигнатурой может вызвать баг.
5. Подмена реализации
Использование интерфейса как типа может скрывать специфичное поведение конкретной реализации:
Дополнительные нюансы
1. Функциональные интерфейсы
С Java 8 появились интерфейсы с единственным абстрактным методом — функциональные интерфейсы. Они могут быть использованы с лямбда-выражениями:
2. Интерфейсы и обобщения
Интерфейсы часто комбинируются с generics для повышения универсальности:
3. Marker-интерфейсы
Некоторые интерфейсы, такие как Serializable или Cloneable, не содержат методов. Они используются как маркеры для определения поведения JVM или библиотек.
4. Интерфейсы и наследование
Интерфейс может наследовать другие интерфейсы:
5. Интерфейсы и динамические прокси
Через интерфейсы создаются динамические прокси, например, в java.lang.reflect.Proxy, что активно используется в Spring, Hibernate и других фреймворках.
#Java #для_новичков #beginner #reference_types #Interface
Классы в Java не могут наследовать более одного класса, но могут реализовать несколько интерфейсов:
public interface Flyable {
void fly();
}
public interface Swimmable {
void swim();
}
public class Duck implements Flyable, Swimmable {
public void fly() { ... }
public void swim() { ... }
}
Это решает проблему ограничений одиночного наследования и позволяет композировать поведение.
Трудности и подводные камни
1. Конфликт default-методов
Если класс реализует несколько интерфейсов, в которых есть default-методы с одинаковыми сигнатурами, возникает конфликт:
interface A {
default void show() { System.out.println("A"); }
}
interface B {
default void show() { System.out.println("B"); }
}
class C implements A, B {
public void show() {
A.super.show(); // Явное разрешение конфликта
}
}
2. Отсутствие состояния
Интерфейсы не могут содержать нестатические поля, что ограничивает их в сравнении с абстрактными классами. Всё, что можно объявить — это public static final константы.
3. Непредсказуемое поведение equals/hashCode
Если объект используется через интерфейс, его поведение equals() и hashCode() может зависеть от реализации, что важно при использовании в коллекциях.
4. Ошибки при переопределении
Ошибки в сигнатурах при реализации интерфейса не всегда очевидны. Например, случайное переопределение метода с другой сигнатурой может вызвать баг.
5. Подмена реализации
Использование интерфейса как типа может скрывать специфичное поведение конкретной реализации:
List<String> list = new LinkedList<>();
list.get(0); // Поведение может отличаться от ArrayList
Дополнительные нюансы
1. Функциональные интерфейсы
С Java 8 появились интерфейсы с единственным абстрактным методом — функциональные интерфейсы. Они могут быть использованы с лямбда-выражениями:
Runnable r = () -> System.out.println("Run");
2. Интерфейсы и обобщения
Интерфейсы часто комбинируются с generics для повышения универсальности:
public interface Repository<T> {
void save(T entity);
}
3. Marker-интерфейсы
Некоторые интерфейсы, такие как Serializable или Cloneable, не содержат методов. Они используются как маркеры для определения поведения JVM или библиотек.
4. Интерфейсы и наследование
Интерфейс может наследовать другие интерфейсы:
interface A { void a(); }
interface B extends A { void b(); }
Класс, реализующий B, обязан реализовать методы a() и b().
5. Интерфейсы и динамические прокси
Через интерфейсы создаются динамические прокси, например, в java.lang.reflect.Proxy, что активно используется в Spring, Hibernate и других фреймворках.
#Java #для_новичков #beginner #reference_types #Interface
Что выведет код?
#Tasks
interface A170625 {
default void show() {
System.out.println("A");
}
}
interface B170625 extends A170625 {
default void show() {
System.out.println("B");
}
}
interface C170625 extends A170625 {}
class D170625 implements B170625, C170625 {
public static void main(String[] args) {
new D170625().show();
}
}
#Tasks
Продолжаем выбирать темы для разбора и голосовать за рассмотрение предложенных! 🤓
Голосуем за тему к рассмотрению в эти выходные!
Выбираем новую тему!
(можете предложить что-то из того, что предлагали на прошлой и позапрошлых неделях и что проиграло в голосовании!)
Не стесняемся!✌️
Голосуем за тему к рассмотрению в эти выходные!
Выбираем новую тему!
(можете предложить что-то из того, что предлагали на прошлой и позапрошлых неделях и что проиграло в голосовании!)
Не стесняемся!
Please open Telegram to view this post
VIEW IN TELEGRAM
Чем отличается Iterator от ListIterator? 🤓
Ответ:
Iterator позволяет перебирать элементы коллекции в одном направлении и удалять их (remove()).
ListIterator расширяет Iterator для списков (List), добавляя возможность двунаправленного перебора (previous(), hasPrevious()) и модификации элементов (set()).
Пример:
List<String> list = new ArrayList<>(Arrays.asList("A", "B"));
ListIterator<String> it = list.listIterator();
it.next (); // Переход к "A"
it.set("C"); // Замена "A" на "C"
#собеседование
Ответ:
ListIterator расширяет Iterator для списков (List), добавляя возможность двунаправленного перебора (previous(), hasPrevious()) и модификации элементов (set()).
Пример:
List<String> list = new ArrayList<>(Arrays.asList("A", "B"));
ListIterator<String> it = list.listIterator();
it.set("C"); // Замена "A" на "C"
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
Интеграция Flyway с инструментами
1. Maven: плагин flyway-maven-plugin
Настройка в pom.xml:
Ключевые параметры:
url — JDBC-строка подключения.
locations — пути к папкам с миграциями (classpath: или filesystem:).
baselineVersion — версия для инициализации существующей БД (если не пустая).
Команды:
2. Gradle: плагин org.flywaydb.flyway
Настройка в build.gradle:
Команды:
3. Интеграция с Spring Boot
Автоконфигурация через application.yml:
Принцип работы:
При старте приложения Spring Boot автоматически:
Проверяет наличие таблицы flyway_schema_history.
Применяет невыполненные миграции из указанных в locations.
Если baseline-on-migrate: true, Flyway инициализирует БД без ошибок, даже если она не пуста.
Важные свойства:
spring.flyway.clean-disabled — запрет на случайную очистку БД (по умолчанию true в prod).
spring.flyway.table — кастомное имя для таблицы истории (по умолчанию flyway_schema_history).
4. Callbacks (SQL-хуки)
SQL-скрипты, выполняемые до/после основных операций Flyway (миграции, очистки и т.д.).
Поддерживаемые события:
beforeMigrate, afterMigrate
beforeClean, afterClean
beforeInfo, afterInfo
Пример использования:
Создайте файл db/migration/beforeMigrate.sql:
Flyway выполнит этот скрипт перед применением миграций.
Правила:
Имена файлов должны соответствовать шаблону: <event>.sql (например, afterMigrate.sql).
Размещаются в папке locations (рядом с миграциями).
5. Мультимодульные проекты (Maven/Gradle)
Если миграции находятся в отдельном модуле:
Укажите путь к ресурсам через filesystem::
6. Пропуск миграций в тестах
В application-test.yml:
7. Кастомные места хранения миграций
S3-хранилище (через Flyway Teams):
Классовый путь + файловая система:
#Java #middle #Flyway
1. Maven: плагин flyway-maven-plugin
Настройка в pom.xml:
<build>
<plugins>
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>9.22.0</version>
<configuration>
<url>jdbc:postgresql://localhost:5432/mydb</url>
<user>postgres</user>
<password>password</password>
<locations>
<location>classpath:db/migration</location>
</locations>
</configuration>
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
Ключевые параметры:
url — JDBC-строка подключения.
locations — пути к папкам с миграциями (classpath: или filesystem:).
baselineVersion — версия для инициализации существующей БД (если не пустая).
Команды:
mvn flyway:migrate # Применить миграции
mvn flyway:info # Показать статус
mvn flyway:clean # Очистить БД (удалить все объекты)
2. Gradle: плагин org.flywaydb.flyway
Настройка в build.gradle:
plugins {
id "org.flywaydb.flyway" version "9.22.0"
}
flyway {
url = 'jdbc:postgresql://localhost:5432/mydb'
user = 'postgres'
password = 'password'
locations = ['classpath:db/migration']
}
dependencies {
flywayRuntime 'org.postgresql:postgresql:42.6.0'
}
Команды:
gradle flywayMigrate # Применить миграции
gradle flywayInfo # Показать статус
3. Интеграция с Spring Boot
Автоконфигурация через application.yml:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: postgres
password: password
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: true # Игнорировать существующую БД
validate-on-migrate: false # Отключить валидацию (для dev)
Принцип работы:
При старте приложения Spring Boot автоматически:
Проверяет наличие таблицы flyway_schema_history.
Применяет невыполненные миграции из указанных в locations.
Если baseline-on-migrate: true, Flyway инициализирует БД без ошибок, даже если она не пуста.
Важные свойства:
spring.flyway.clean-disabled — запрет на случайную очистку БД (по умолчанию true в prod).
spring.flyway.table — кастомное имя для таблицы истории (по умолчанию flyway_schema_history).
4. Callbacks (SQL-хуки)
SQL-скрипты, выполняемые до/после основных операций Flyway (миграции, очистки и т.д.).
Поддерживаемые события:
beforeMigrate, afterMigrate
beforeClean, afterClean
beforeInfo, afterInfo
Пример использования:
Создайте файл db/migration/beforeMigrate.sql:
-- beforeMigrate.sql
CREATE TABLE IF NOT EXISTS _flyway_audit (
operation VARCHAR(20),
timestamp TIMESTAMP
);
INSERT INTO _flyway_audit VALUES ('migration_started', NOW());
Flyway выполнит этот скрипт перед применением миграций.
Правила:
Имена файлов должны соответствовать шаблону: <event>.sql (например, afterMigrate.sql).
Размещаются в папке locations (рядом с миграциями).
5. Мультимодульные проекты (Maven/Gradle)
Если миграции находятся в отдельном модуле:
Укажите путь к ресурсам через filesystem::
<!-- Maven -->
<location>filesystem:${project.basedir}/../migrations/src/main/resources/db/migration</location>
// Gradle
locations = ['filesystem:../migrations/src/main/resources/db/migration']
6. Пропуск миграций в тестах
В application-test.yml:
spring:
flyway:
enabled: false
7. Кастомные места хранения миграций
S3-хранилище (через Flyway Teams):
spring:
flyway:
locations: s3://my-bucket/db/migration
Классовый путь + файловая система:
locations: classpath:db/migration, filesystem:/opt/migrations
#Java #middle #Flyway
Что выведет код?
#Tasks
import java.util.Optional;
public class Task180625 {
public static void main(String[] args) {
Optional<String> opt = Optional.ofNullable(null)
.flatMap(s -> Optional.of("value"))
.or(() -> Optional.of("default"));
System.out.println(opt.get());
}
}
#Tasks