А вы знали, что термин Cursor (Курсор) пришел к нам из древней Греции?
Cursor в переводе с латыни — бегун. Этим словом называли прозрачную каретку на логарифмической линейке — бегунок, ползунок. На бегунке была нанесена тончайшая линия для выбора нужной позиции на линейке. Позднее термин «курсор» перекочевал в компьютеры.
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 — блокировка для предотвращения конфликтов.
Начинавшийся как попытка освоить телеграм-каналы и найти единомышленников в программировании на Java - сегодня наш канал, небольшое, но уникальное сообщество, в котором объединено стремление охватить все аспекты Java, с встречами и интересным общением!🤓
Вот немного статистики по каналу: 🔵Сегодня нас в канале: 669 человек 🔵Постов в канале: 2406 или 6.6 постов в день 🔵Постов содержащих обучающую информацию: ~ 440 🔵Постов с уникальными задачами: 257 🔵Постов с IT-мемами: 415 🔵Постов с IT-фактами: 89 🔵Постов с IT-цитатами и биографиями: 88 🔵Проведено встреч и опубликовано видео:42 🔵Подписчиков на YouTube:220 🔵Потрачено на рекламу/заработано: 0 рублей
Много это или мало - судить Вам))
Как бы то ни было, канал для меня сейчас - это прекрасная возможность не стагнировать, повторять пройденное и изучать новое, знакомиться и общаться с интересными людьми. И в любом случае, я продолжу его развивать, наполнять информацией и стараться дать то, чего Вы все здесь ищете. Спасибо всем, кто поддерживает канал, помогает с лайвкодингом, приходит на встречи.
Ответ: Перегрузка (overloading): методы в одном классе с одинаковым именем, но разными параметрами (по количеству или типу). Происходит на этапе компиляции.
Переопределение (overriding): подкласс переопределяет метод родительского класса с той же сигнатурой. Происходит на этапе выполнения.
А вы знали, что термин "бит" был придуман как сокращение?
В 1948 году Клод Шеннон впервые использовал слово «bit» для обозначения наименьшей единицы количества информации в статье «Математическая теория связи». Происхождение этого слова он приписывал Джону Тьюки, использовавшему сокращение «bit» вместо слов «binary digit» в заметке лаборатории Белла от 9 января 1947 года.
"JavaScript — это самый недооцененный язык программирования."
Дуглас Крокфорд, постоянный участник развития языка JavaScript, создатель текстового формата обмена данными JSON (JavaScript Object Notation), автор "JavaScript: The Good Parts", сказал это в 2008 году на конференции JSConf.
Глубокое изучение типа float в Java: сравнение с double и целочисленными типами
Тип float — один из двух примитивных типов с плавающей точкой в Java. Он используется для хранения чисел с десятичной частью и обеспечивает определённый баланс между точностью и потреблением памяти. Несмотря на свою "простоту", float имеет множество нюансов, особенно в сравнении с double и целочисленными типами (int, long и т. д.), и может вести себя неожиданно, если не понимать его природу.
Что такое float в Java
float — это 32-битный (4 байта) тип данных, реализующий стандарт IEEE 754 для представления чисел с плавающей точкой.
Это означает, что число хранится в следующем формате: 1 бит — знак числа 8 бит — экспонента 23 бита — мантисса (дробная часть)
Таким образом, float может хранить числа приблизительно в диапазоне от ±1.4 × 10^-45 до ±3.4 × 10^38 с точностью около 6–7 значащих десятичных цифр.
Чтобы обозначить литерал как float, нужно явно указать f или F:
float pi = 3.1415927f; Без этого литерал будет воспринят как double по умолчанию, что приведет к ошибке компиляции при попытке неявного присваивания.
Сравнение с double
double — это 64-битный тип, также реализующий IEEE 754, но имеющий: 1 бит для знака 11 бит для экспоненты 52 бита для мантиссы Он способен хранить числа от ±4.9 × 10^-324 до ±1.7 × 10^308, с точностью около 15–16 значащих цифр.
То есть: float — быстрее, но менее точен, занимает меньше памяти double — точнее, но требует больше памяти и может быть чуть медленнее в вычислениях на некоторых архитектурах
В реальной практике предпочтение обычно отдают double, особенно в финансовых, статистических или инженерных вычислениях, где важна точность. float чаще применяется в графике (например, координаты вершин), машинном обучении, играх и устройствах с ограниченными ресурсами (встраиваемые системы, Android до определённых API-уровней).
Сравнение с целочисленными типами
Целочисленные типы (byte, short, int, long) хранят точные значения и не допускают погрешностей. Они идеальны для подсчётов, индексов, флагов, битовых масок и всего, что не связано с дробями. В отличие от них, float и double — не точные типы.
Это означает: Результаты вычислений могут быть неточными из-за ограниченной точности представления дробных чисел. Сравнение значений на равенство (==) — рискованно и почти всегдаплохая идея.
Простые на вид операции могут давать неожиданный результат:
float a = 0.1f + 0.2f; System.out.println(a == 0.3f); // false Это связано с тем, что не все десятичные дроби можно точно представить в двоичной системе.
Работа с памятью и производительность
Обе переменные — float и double — примитивные типы и, следовательно, при размещении в стеке (например, внутри метода) не требуют участия сборщика мусора. Они быстро выделяются и удаляются вместе с фреймом стека. Однако, если переменные — поля объекта, то они хранятся в куче, и их "жизненный цикл" зависит от объекта.
С точки зрения производительности: На современных процессорах разница между float и double минимальна. Некоторые GPU и встраиваемые процессоры всё ещё используют float как основной тип с плавающей точкой. На JVM оба типа оптимизируются, но float может быть чуть быстрее при большом объеме операций и памяти.
Погрешность и потеря точности Каждое присваивание или операция с float может сопровождаться потерей точности. Например:
float a = 1_000_000; float b = a + 0.0001f; System.out.println(a == b); // true — потерялась дробная часть
Нормализованные и денормализованные числа
float поддерживает очень маленькие значения, но при этом точность сильно страдает. Денормализованные значения позволяют представлять числа ближе к нулю, но с меньшей точностью.
NaN, Infinity и -Infinity
float поддерживает специальные значения: Float.NaN — результат недопустимых операций (например, 0.0f / 0.0f) Float.POSITIVE_INFINITY и Float.NEGATIVE_INFINITY — результат переполнения или деления на 0 Эти значения не вызывают исключений, и с ними можно работать, но это требует осторожности.
Сравнение на равенство Из-за округлений не следует использовать == для сравнения двух float.
Вместо этого используют допустимую погрешность:
float a = 0.1f + 0.2f; float b = 0.3f; if (Math.abs(a - b) < 1e-6) { System.out.println("Равны с учетом погрешности"); }
Приведение типов
При смешанных операциях с float и целочисленными типами Java автоматически приводит меньший тип к float.
Например:
int x = 3; float y = 2.5f; float result = x + y; // x преобразован в float Это не вызывает проблем, но может повлиять на точность, если целое число очень большое.
Двоичное представление и неожиданное округление
Некоторые десятичные дроби (например, 0.1, 0.2) не могут быть точно представлены в двоичной системе. Это приводит к накапливающимся погрешностям, особенно при работе с циклами или большими массивами данных.
Когда использовать float, а когда — double
Используй float, если: Работаешь в среде с ограниченной памятью или производительностью (например, Android, микроконтроллеры) Требуется снизить объем данных (например, передача координат в 3D-движке) Максимальная точность не критична
Используй double, если: Точность важна (финансовые расчеты, физические симуляции) Объёмы данных позволяют использовать больше памяти Не хочешь постоянно контролировать потерю точности
Совсем недавно стал свидетелем неочевидной проблемы, когда вроде бы полностью протестированный стабильный сервис, по непонятным причинам падает на проде с ошибкой OutOfMemory. Причинами и способами решения, сегодня я решил поделиться с Вами.
Вводные данные: 🔵Основная сущность (к примеру User) и связанная с ней через one-to-many, вторичная сущность (пусть будет Car). 🔵Сущности описаны по стандарту Spring JPA в коде. 🔵По запросу бизнеса, при получении списка основных сущностей должна применяться пагинация, фильтрация и сортировка (для корректного отображения на web-странице). 🔵Само собой при получении основных сущностей, нужно подгружать все связанные, а так же иметь возможность фильтрации и сортировки по содержащихся в них данных.
Предложенное решение (как показала практика - неверное): 🔵Для решения подобного запроса использовать стандартные средства Spring, такие как Pageable и для формирования сложного SQL запроса средствами Java - Specification
Примерный код:
🧍 User
@Entity public class User { //стандартные поля @OneToMany(mappedBy = "owner", fetch = FetchType.LAZY) private List<Car> cars = new ArrayList<>(); }
🚗 Car
@Entity public class Car { //стандартные поля @ManyToOne @JoinColumn(name = "owner_id") private User owner; }
Пагинация + fetch join через Specification (без фильтров)
Проблема: 🔵Хотя предложенное выше решение и выглядит очевидным при озвученном кейсе требований и решении проблемы N+1, оно создает проблему о которой Вам не расскажут на хайповых курсах и видео уроках.
🔵Суть проблемы в том, что когда вы используете JOIN FETCH (например, через Specification), Hibernate подгружает связанные сущности в один SQL-запрос (чтобы избежать N+1).
Однако в сочетании с пагинацией (Pageable) Hibernate теряет корректность подсчёта количества строк и может загрузить всю таблицу в память, чтобы затем вручную "отрезать" нужную страницу на уровне Java, тем самым использовавВСЮ выделенную JVM память для хранения.
О последствиях такого непредсказуемого поведения можете посудить сами. 😱
Hibernate должен выполнить: SELECT COUNT(*) ... — чтобы узнать общее количество строк. SELECT ... LIMIT 10 OFFSET 0 — чтобы получить только первую страницу.
❗️Но fetch join меняет семантику запроса
Когда вы пишете JOIN FETCH, например:
root.fetch("cars", JoinType.LEFT);
Или:
criteriaQuery.distinct(true);
Hibernate генерирует SQL примерно такого вида:
SELECT u.*, r.* FROM users u LEFT JOIN roles r ON r.user_id = u.id
При этом: Если у одного пользователя 3 cars, то он появится 3 раза в результате SQL-запроса. Hibernate потом вручную собирает дубликаты в одну сущность User, у которой будет List<Car> с 3 элементами. LIMIT/OFFSET применяются к строкам SQL, а не к "собранным" сущностям — и это вызывает проблемы.
⚠️ Проблема: LIMIT работает до агрегации Hibernate не может корректно объединить дубликаты после применения LIMIT, потому что: При использовании fetch join, результат SQL-разворачивается в несколько строк (по связям). Но LIMIT обрезает эти строки до того, как Hibernate агрегирует их в объекты Java. Поэтому Hibernate игнорирует LIMIT в SQL, чтобы корректно собрать сущности → он загружает все строки в память, затем отрезает нужную страницу на уровне Java.
🤯Что я получил в результате использования неверного решения
🔜 Упавший сервис на проде 😨 (так как на тестовых средах, объем загружамых данных был в разы меньше). 🔜 Бесценный опыт, которым сейчас делюсь с Вами ☺️
А завтра я расскажу как решить данный кейс и как не попасть в подобную ловушку неочевидного поведения Hibernate ...
А вы знали, что термин "алгоритм" назван в честь персидского математика?
Слово "algorithm" происходит от имени Аль-Хорезми, персидского учёного IX века, чьи труды по математике легли в основу современных вычислений. Его имя латинизировали как "Algoritmi".