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

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

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Best Practices и расширенные сценарии работы с Flyway

1. Организация миграций

1.1. Разделение миграций по окружениям

Проблема:
Разные БД для dev/test/prod могут требовать специфичных скриптов (например, тестовые данные только для dev).

Решение:
Структура папок:

db/
migration/
dev/
V1__Dev_only_data.sql
prod/
V1__Prod_indexes.sql
common/
V1__Base_schema.sql


Настройка application.yml для dev:
spring:
flyway:
locations: classpath:db/migration/common, classpath:db/migration/dev


1.2. Работа с существующей БД (baseline)

Проблема:
Flyway ожидает пустую БД или таблицу flyway_schema_history. Если БД уже используется, требуется инициализация.

Решение:
Включить baseline-on-migrate:

spring:
flyway:
baseline-on-migrate: true
baseline-version: 1.0 # Версия, с которой начнется контроль


Вручную через CLI:

flyway baseline -baselineVersion="1.0"


Важно:
После baseline миграции с версией ≤ baselineVersion игнорируются.


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

2.1. Интеграция с Testcontainers + JUnit

Цель:
Проверить, что миграции применяются без ошибок в изолированной БД.

Пример теста (Java + JUnit 5):
import org.flywaydb.core.Flyway;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@Testcontainers
public class FlywayMigrationTest {

@Container
private static final PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15");

@Test
void testMigrations() {
Flyway flyway = Flyway.configure()
.dataSource(
postgres.getJdbcUrl(),
postgres.getUsername(),
postgres.getPassword()
)
.load();
flyway.migrate(); // Упадет, если есть ошибки
}
}


Что проверяем:
Применение всех миграций без исключений.
Соответствие схемы ожиданиям (можно добавить проверку через DataSource).



3. Работа в команде

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

Правила:
Уникальность версий:
Используйте дату + порядковый номер: V20240315-1__Add_users_table.sql.

Описательные имена:
Плохо: V1__Changes.sql.
Хорошо: V20240315-2__Alter_users_add_email.sql.


3.2. Запрет изменения примененных скриптов

Проблема:
Изменение уже выполненного скрипта приводит к ошибке:

Validate failed: Migration checksum mismatch for V1__Create_table.sql


Решение:
Никогда не изменять содержимое V*-файлов после их применения.

Для исправлений:
Создать новую миграцию с корректировкой:
-- V20240315-3__Fix_users_table.sql
ALTER TABLE users ALTER COLUMN email SET NOT NULL;

Если критично, использовать flyway repair (пересчитывает контрольные суммы).

Исключение:
R__-миграции (повторяемые) можно изменять — они переприменяются при изменениях.


4. Расширенные сценарии

4.1. Миграции для нескольких схем БД

Настройка:
spring:
flyway:
schemas: public, audit
default-schema: public
В SQL-файлах укажите схему явно:

sql
-- V1__Create_audit_table.sql
CREATE TABLE audit.logs (
id SERIAL PRIMARY KEY,
message TEXT
);


4.2. Шаблонизация SQL

Проблема:
Разные SQL-синтаксисы для PostgreSQL/Oracle.

Решение (через Maven/Gradle):
Используйте placeholders в pom.xml:
<configuration>
<placeholders>
<table.prefix>${env}</table.prefix>
</placeholders>
</configuration>


В SQL:
CREATE TABLE ${table.prefix}_users (...);


4.3. Откаты (безопасная альтернатива)


Тактика:
Для деструктивных изменений (DROP, DELETE) создавать "обратные" миграции:
-- V5__Drop_users.sql
DROP TABLE users;

-- V6__Restore_users.sql (если нужно откатить)
CREATE TABLE users (...); -- Повторяем структуру из V1
INSERT INTO users (...) SELECT ... FROM backup_users;
Использовать резервные копии перед рискованными операциями.


#Java #middle #Flyway
Что выведет код?

import java.util.function.Supplier;

public class Task200625 {
public static void main(String[] args) {
Supplier<Integer> supplier = () -> {
System.out.print("A");
return 1;
};

System.out.print("B");
supplier.get();
System.out.print("C");
}
}


#Tasks
Варианты ответа:
Anonymous Quiz
11%
"ABC"
39%
"BAC"
33%
"A"
17%
Exception
Продолжаем выбирать темы для разбора и голосовать за рассмотрение предложенных! 🤓

Голосуем за тему к рассмотрению в эти выходные!

Выбираем новую тему!
(можете предложить что-то из того, что предлагали на прошлой и позапрошлых неделях и что проиграло в голосовании!)

Не стесняемся! ✌️
Please open Telegram to view this post
VIEW IN TELEGRAM
Что такое Thread.sleep() и чем он отличается от Thread.yield()? 🤓

Ответ:

Thread.sleep(long millis) приостанавливает выполнение текущего потока на указанное время (в миллисекундах), не освобождая монитор. Может выбросить InterruptedException.


Thread.yield() дает возможность другим потокам с таким же приоритетом выполниться, но не гарантирует переключение.

Пример:
Thread.sleep(1000); // Пауза на 1 секунду
Thread.yield(); // Уступить процессор другим потокам

#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Ключевые методы Optional

Optional предоставляет множество методов для работы с данными:

isPresent(): Возвращает true, если значение присутствует.
isEmpty() (добавлен в Java 11): Возвращает true, если значение отсутствует.
ifPresent(Consumer<? super T> consumer): Выполняет действие, если значение присутствует.
orElse(T other): Возвращает значение, если оно присутствует, или other, если отсутствует.
orElseGet(Supplier<? extends T> supplier): Возвращает значение или результат выполнения Supplier, если значение отсутствует.
orElseThrow(Supplier<? extends X> exceptionSupplier): Возвращает значение или выбрасывает исключение, если значение отсутствует.
map(Function<? super T, ? extends U> mapper): Преобразует значение, если оно присутствует, возвращая новый Optional.
flatMap(Function<? super T, Optional<U>> mapper): Преобразует значение, возвращая Optional, результат которого не оборачивается в дополнительный Optional.
filter(Predicate<? super T> predicate): Возвращает Optional, содержащий значение, если оно удовлетворяет предикату, или пустой Optional.
or(Supplier<? extends Optional<? extends T>> supplier) (добавлен в Java 9): Возвращает текущий Optional, если значение присутствует, или результат Supplier.


Пример использования методов
Optional<String> name = Optional.ofNullable(getName()); // Может вернуть null
String result = name
.map(String::toUpperCase)
.filter(s -> s.length() > 3)
.orElse("DEFAULT");
System.out.println(result); // Выведет преобразованное значение или "DEFAULT"

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



Преимущества Optional
Безопасность от NPE: Optional явно указывает на возможность отсутствия значения, заставляя разработчика обрабатывать этот случай.
Читаемость кода: Код с Optional более выразителен, так как сигнализирует о потенциальном отсутствии значения.
Избежание явного null: Уменьшает использование проверок if (value != null), делая код чище.
Интеграция с функциональным программированием: Методы map, flatMap и filter позволяют использовать Optional в цепочках обработки данных, интегрируясь с Stream API.



Практическое использование Optional

Интеграция со Stream API
Optional часто используется в связке со Stream API для обработки данных, которые могут отсутствовать. Например:
Optional<User> user = findUserById(123);
Stream<User> userStream = user.stream(); // Преобразует Optional в Stream
userStream.forEach(System.out::println);

Метод stream() (добавлен в Java 9) позволяет преобразовать Optional в Stream, содержащий либо одно значение, либо пустой.


Обработка вложенных объектов
Optional особенно полезен при работе с цепочками объектов, где каждое звено может быть null. Без Optional код выглядел бы так:
String city = null;
if (user != null && user.getAddress() != null && user.getAddress().getCity() != null) {
city = user.getAddress().getCity();
}


С использованием Optional код становится более компактным:
String city = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.orElse("Unknown");


Метод flatMap полезен, если методы возвращают Optional:
Optional<String> city = Optional.ofNullable(user)
.flatMap(u -> Optional.ofNullable(u.getAddress()))
.flatMap(a -> Optional.ofNullable(a.getCity()));


Использование в API
Optional часто применяется в публичных API для возврата значений, которые могут отсутствовать. Например, метод Map.getOrDefault может быть заменен на Optional:
Map<String, String> map = new HashMap<>();
Optional<String> value = Optional.ofNullable(map.get("key"));


#Java #Training #Medium #Optional
Антипаттерны и ограничения

Использование Optional для полей класса: Optional не предназначен для хранения состояния в полях класса, так как он не сериализуем (Optional не реализует Serializable). Вместо этого используйте null для полей или коллекции.
// Антипаттерн
public class User {
private Optional<String> name; // Не рекомендуется
}


Чрезмерное использование isPresent():
Проверки isPresent() с последующим вызовом get() сводят на нет преимущества Optional:

// Антипаттерн
if (optional.isPresent()) {
return optional.get();
}


Вместо этого используйте orElse, orElseGet или map
:
return optional.orElse(defaultValue);


Передача Optional в методы: Передача Optional в качестве аргумента метода может усложнить API. Лучше передавать значение или null:
// Антипаттерн
void process(Optional<String> value) { ... }

// Лучше
void process(String value) { ... }


Производительность: Создание объектов Optional в циклах или в высокопроизводительных системах может привести к накладным расходам. В таких случаях рассмотрите использование null или оптимизируйте код.
Сериализация: Optional не реализует Serializable, что делает его непригодным для использования в сериализуемых классах. Если сериализация необходима, преобразуйте Optional в null или значение перед сохранением.
Потокобезопасность: Optional не является потокобезопасным, так как он не предназначен для конкурентного доступа. Если значение внутри Optional изменяется в многопоточной среде, используйте синхронизацию или потокобезопасные альтернативы.


Производительность и оптимизация
Создание объектов: Каждый вызов Optional.of или ofNullable создает новый объект (кроме empty(), который использует синглтон). В критически важных участках кода минимизируйте создание Optional.
Кэширование: Если Optional возвращается часто используемым методом, рассмотрите кэширование результата, чтобы избежать повторного создания объектов.
Ленивые вычисления: Используйте orElseGet вместо orElse, если альтернативное значение требует вычислений:

// Менее эффективно
String result = optional.orElse(computeExpensiveDefault());

// Более эффективно
String result = optional.orElseGet(() -> computeExpensiveDefault());



Рекомендации

- Используйте Optional только там, где это имеет смысл: Применяйте Optional для возвращаемых значений методов, где отсутствие значения — это ожидаемый сценарий. Не используйте его для всех случаев, чтобы избежать ненужной сложности.
- Интеграция с функциональными API: Максимально используйте методы map, flatMap и filter для обработки данных в функциональном стиле. Это улучшает читаемость и поддерживаемость кода.
- Проверка на обратную совместимость: Если вы разрабатываете публичное API, учитывайте, что клиенты на Java 7 и ниже не смогут использовать Optional. В таких случаях предоставляйте альтернативные методы, возвращающие null.
- Тестирование: Убедитесь, что тесты покрывают оба случая — присутствие и отсутствие значения в Optional. Это гарантирует корректную обработку всех сценариев.
- Рефакторинг старого кода: При рефакторинге кода, использующего null, заменяйте проверки на Optional, но только если это улучшает читаемость и безопасность. Не заменяйте null на Optional механически.



Пример реального сценария

Предположим, у нас есть сервис, который возвращает информацию о пользователе:
public class UserService {
public Optional<User> findUserById(long id) {
// Имитация поиска в базе данных
return id == 123 ? Optional.of(new User("Alice")) : Optional.empty();
}

public Optional<String> getUserCity(long id) {
return findUserById(id)
.flatMap(user -> Optional.ofNullable(user.getAddress()))
.flatMap(address -> Optional.ofNullable(address.getCity()));
}
}

Этот код демонстрирует, как Optional используется для безопасной обработки цепочки объектов, где каждое звено может быть отсутствующим.


#Java #Training #Medium #Optional
Есть предложение встретиться завтра в 16:00 по МСК на лайвкодинг!

Тема будет: MailSender - или как отправить email из Spring
Anonymous Poll
40%
Да, интересно, я приду!🥳
7%
Блиин, хочу но не могу 🤷‍♀️
47%
Посмотрю в записи 😋
7%
Не приду, фигню рассматриваете 👎