Типы 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
Что выведет код?
#Tasks
public class Task020625 {
public static void main(String[] args) {
String s1 = new String("Java");
String s2 = "Java";
String s3 = s1.intern();
System.out.println((s1 == s2) + " " + (s2 == s3));
}
}
#Tasks
В чем разница между Thread и Runnable? 🤓
Ответ:
Thread — это класс, представляющий поток.
Runnable — интерфейс с методом run(), описывающий задачу для потока.
Runnable предпочтительнее, так как позволяет наследовать другой класс и использовать пулы потоков.
#собеседование
Ответ:
Runnable — интерфейс с методом run(), описывающий задачу для потока.
Runnable предпочтительнее, так как позволяет наследовать другой класс и использовать пулы потоков.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
Глубокое изучение типа данных boolean в Java
Тип boolean — самый компактный и в то же время наименее числовой из всех примитивных типов в Java. Он представляет логические значения true или false и лежит в основе всех условных конструкций языка. Несмотря на простоту, поведение boolean в Java имеет ряд важных нюансов, связанных с его представлением в памяти, взаимодействием с другими типами, ограничениями и поведением на уровне JVM.
Что представляет собой boolean
Тип boolean может принимать только два значения: true и false. Он не является числовым типом и не участвует в арифметике. Java строго типизирована, поэтому невозможно, как в C или Python, неявно преобразовать boolean в число или наоборот. Это ограничение сделано для повышения читаемости и безопасности кода.
Например, такой код в Java недопустим:
Хранение в памяти
На уровне языка boolean воспринимается как логическое значение, но в памяти JVM его представление зависит от контекста:
При хранении в массиве или в виде поля класса, boolean может занимать целый байт или даже больше, в зависимости от выравнивания и архитектуры. В спецификации Java не указано точное количество битов, которое должен занимать boolean, только то, что он представляет true/false.
В локальных переменных boolean может быть оптимизирован компилятором и размещен в стеке наряду с другими переменными. Однако фактический объем занимаемой памяти определяется JVM и может отличаться от теоретически минимального.
На уровне байткода логические значения часто реализуются как int со значением 0 или 1, но это скрыто от разработчика и не влияет на язык напрямую.
Инициализация и значение по умолчанию
Локальные переменные типа boolean не инициализируются автоматически — попытка использовать их без явного присваивания вызовет ошибку компиляции.
Поля классов и массивов по умолчанию получают значение false.
Операции с boolean
Java предоставляет классический набор логических операций:
!a — логическое отрицание (NOT)
a && b — логическое И (AND, с коротким замыканием)
a || b — логическое ИЛИ (OR, с коротким замыканием)
a ^ b — исключающее ИЛИ (XOR)
Кроме того, возможны сравнения (==, !=) между значениями типа boolean, но не упорядоченные сравнения (<, >, <=, >=) — они не имеют смысла и запрещены.
В отличие от числовых типов, boolean не участвует в операциях сложения, вычитания или побитовых вычислений. Java запрещает такие действия на уровне компилятора.
Сравнение с другими примитивами
boolean — единственный логический тип в Java. Нет аналогов bit, flag, bool8, bool32 и т. д.
Он не является числом. В то время как char, byte, short, int, long, float и double можно свободно преобразовывать друг в друга, boolean изолирован от них.
Это делает boolean более строго типизированным и безопасным, но менее гибким, если требуется, например, сериализация логики в числовом формате.
#Java #для_новичков #beginner #boolean
Тип boolean — самый компактный и в то же время наименее числовой из всех примитивных типов в Java. Он представляет логические значения true или false и лежит в основе всех условных конструкций языка. Несмотря на простоту, поведение boolean в Java имеет ряд важных нюансов, связанных с его представлением в памяти, взаимодействием с другими типами, ограничениями и поведением на уровне JVM.
Что представляет собой boolean
Тип boolean может принимать только два значения: true и false. Он не является числовым типом и не участвует в арифметике. Java строго типизирована, поэтому невозможно, как в C или Python, неявно преобразовать boolean в число или наоборот. Это ограничение сделано для повышения читаемости и безопасности кода.
Например, такой код в Java недопустим:
int x = true; // Ошибка компиляции
if (1) { ... } // Тоже ошибка
Хранение в памяти
На уровне языка boolean воспринимается как логическое значение, но в памяти JVM его представление зависит от контекста:
При хранении в массиве или в виде поля класса, boolean может занимать целый байт или даже больше, в зависимости от выравнивания и архитектуры. В спецификации Java не указано точное количество битов, которое должен занимать boolean, только то, что он представляет true/false.
В локальных переменных boolean может быть оптимизирован компилятором и размещен в стеке наряду с другими переменными. Однако фактический объем занимаемой памяти определяется JVM и может отличаться от теоретически минимального.
На уровне байткода логические значения часто реализуются как int со значением 0 или 1, но это скрыто от разработчика и не влияет на язык напрямую.
Инициализация и значение по умолчанию
Локальные переменные типа boolean не инициализируются автоматически — попытка использовать их без явного присваивания вызовет ошибку компиляции.
Поля классов и массивов по умолчанию получают значение false.
Операции с boolean
Java предоставляет классический набор логических операций:
!a — логическое отрицание (NOT)
a && b — логическое И (AND, с коротким замыканием)
a || b — логическое ИЛИ (OR, с коротким замыканием)
a ^ b — исключающее ИЛИ (XOR)
Кроме того, возможны сравнения (==, !=) между значениями типа boolean, но не упорядоченные сравнения (<, >, <=, >=) — они не имеют смысла и запрещены.
В отличие от числовых типов, boolean не участвует в операциях сложения, вычитания или побитовых вычислений. Java запрещает такие действия на уровне компилятора.
Сравнение с другими примитивами
boolean — единственный логический тип в Java. Нет аналогов bit, flag, bool8, bool32 и т. д.
Он не является числом. В то время как char, byte, short, int, long, float и double можно свободно преобразовывать друг в друга, boolean изолирован от них.
Это делает boolean более строго типизированным и безопасным, но менее гибким, если требуется, например, сериализация логики в числовом формате.
#Java #для_новичков #beginner #boolean
Типизация и ограничения
Нельзя привести boolean к int или обратно. Даже явно: (int) true — ошибка.
Нельзя использовать boolean в качестве индекса массива или как аргумент методов, ожидающих число.
Нет Boolean.parseInt(), как у числовых типов — только Boolean.parseBoolean() с String.
Упаковка и объектный аналог
Для работы с boolean как объектом существует класс-обертка Boolean. Он используется при работе с коллекциями (List<Boolean>) или API, которые требуют объектов.
Как и другие обертки, Boolean поддерживает автоупаковку и автораспаковку:
Подводные камни и особенности
Нет арифметики. Переход с языков вроде C, где true — это 1, а false — это 0, может вызвать ошибки. Java не допускает смешивания логики и чисел.
Отсутствие побитовых операций. Нельзя использовать & или | на boolean как побитовые операторы. Да, есть &, | и ^, но они действуют как логические, без короткого замыкания — в отличие от && и ||.
Нет поддержки масок и битов. Если требуется управлять отдельными битами, используют int или byte, а не boolean[].
Выражения с boolean всегда проверяются как логические. Даже if (a & b) — это проверка boolean, а не побитовая операция над числами.
Интерфейсы и структуры не позволяют оптимизировать boolean до одного бита. Например, boolean[] всегда тратит 1 байт (или больше) на элемент, а не один бит, как хотелось бы при экономии памяти. Для работы с компактными флагами используют BitSet или битовые маски на базе int.
#Java #для_новичков #beginner #boolean
Нельзя привести boolean к int или обратно. Даже явно: (int) true — ошибка.
Нельзя использовать boolean в качестве индекса массива или как аргумент методов, ожидающих число.
Нет Boolean.parseInt(), как у числовых типов — только Boolean.parseBoolean() с String.
Упаковка и объектный аналог
Для работы с boolean как объектом существует класс-обертка Boolean. Он используется при работе с коллекциями (List<Boolean>) или API, которые требуют объектов.
Как и другие обертки, Boolean поддерживает автоупаковку и автораспаковку:
Boolean obj = true; // упаковка
boolean val = obj; // распаковка
Также есть кэширование значений: Boolean.TRUE и Boolean.FALSE — это единственные экземпляры класса Boolean, которые обычно и используются.
Подводные камни и особенности
Нет арифметики. Переход с языков вроде C, где true — это 1, а false — это 0, может вызвать ошибки. Java не допускает смешивания логики и чисел.
Отсутствие побитовых операций. Нельзя использовать & или | на boolean как побитовые операторы. Да, есть &, | и ^, но они действуют как логические, без короткого замыкания — в отличие от && и ||.
Нет поддержки масок и битов. Если требуется управлять отдельными битами, используют int или byte, а не boolean[].
Выражения с boolean всегда проверяются как логические. Даже if (a & b) — это проверка boolean, а не побитовая операция над числами.
Интерфейсы и структуры не позволяют оптимизировать boolean до одного бита. Например, boolean[] всегда тратит 1 байт (или больше) на элемент, а не один бит, как хотелось бы при экономии памяти. Для работы с компактными флагами используют BitSet или битовые маски на базе int.
#Java #для_новичков #beginner #boolean
Продолжаем выбирать темы для разбора и голосовать за рассмотрение предложенных! 🤓
Голосуем за тему к рассмотрению в эти выходные!
Выбираем новую тему!
(можете предложить что-то из того, что предлагали на прошлой и позапрошлых неделях и что проиграло в голосовании!)
Не стесняемся!✌️
Голосуем за тему к рассмотрению в эти выходные!
Выбираем новую тему!
(можете предложить что-то из того, что предлагали на прошлой и позапрошлых неделях и что проиграло в голосовании!)
Не стесняемся!
Please open Telegram to view this post
VIEW IN TELEGRAM
Что выведет код?
#Tasks
public class Task030625 {
public static void main(String[] args) {
boolean a = false;
boolean b = true;
boolean c = a != b == !b;
System.out.println(c);
}
}
#Tasks
Что такое ключевое слово synchronized? 🤓
Ответ:
synchronized обеспечивает потокобезопасность, позволяя только одному потоку выполнять блок кода или метод. Используется для предотвращения состояния гонки (race condition) при доступе к общим ресурсам.
#собеседование
Ответ:
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
Откаты (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