Java: fill the gaps
12.1K subscribers
6 photos
191 links
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк

🔥Тот самый курс по многопочке🔥
https://fillthegaps.ru/mt

Комплименты, вопросы, предложения: @utki_letyat
Download Telegram
Апдейт по фичам java 23

⚡️ Новость 1 ⚡️

Derived records не войдут в java 23. Причина прозаична и близка каждому из нас — её просто не успели доделать:)
Фича поедет в следующий релиз, и пощупать её можно будет зимой.

⚡️ Новость 2 ⚡️

Еще один кандидат в java 23, который не смог — это String templates. Но тут история другая.

Напомню, в чём идея темплейтов. Есть две стратегии работы со строками:

🔸 Конкатенация — собираем строку по частям:
String str = "Hello, " + name + "!";


Этот подход использует StringBuilder, метод concat и тд.

🔸 Интерполяция — замена переменных внутри шаблона:
String name = "Jake";
String str = "Hello, ${name}!";


В чистом виде в java такого нет. Отдаленно похожи Formatter и MessageFormat, но там вместо переменных какие-то %s и %d, а переменные стоят отдельно:
String.format("%d plus %d equals %d", x, y, x + y);


А так чистокровная интерполяция выглядит в Kotlin:
"$x plus $y equals ${x + y}"


Новые String templates реализуют своеобразный вариант java интерполяции. В начале строки добавляется STR, переменные обрамляются в \{}
int x = 10, y = 20;
String str = STR."\{x} + \{y} = \{x + y}";
// "10 + 20 = 30"


Зачем они добавили префикс STR? Почему нельзя сделать как в котлине?

По 2 причинам:

1️⃣ Для обратной совместимости

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

2️⃣ Для других обработчиков

По задумке авторов другие процессоры могут делать больше, чем просто подстановку переменных. Например, собрать и провалидировать SQL запрос:
String sql = DB."select from users where id=/{id}";


Что пошло не так?

Разработчики собрали фидбэк и решили, что процессоры (STR, DB, etc) — это лишнее усложнение и очень странный API. Я с ними согласна, выглядит как работа со статическими полями, и вряд ли кому-то нужно что-то большее, чем работа со строками.

Что в итоге с интерполяцией?

Brian Goetz (архитектор java) написал:
The remaining question that everyone is probably asking is: “so how do we do interpolation.” The answer there is “ordinary library methods”.

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

Напомню, что идея String templates появилась в 2021 году, прошлой осенью вышла preview версия. И только сейчас разработчики поняли, что фича так себе. Косяки бывают на всех проектах, это нормально:)
IDEA: шорткаты для сверхзвуковой навигации по коду

🚀 Посмотреть список методов: Ctrl + F12

Быстро найти нужный метод или узнать, что вообще умеет класс

🚀 Найти класс или файл в проекте: Shift-Shift

Откроется строка поиска, можно ввести начало имени или аббревиатуру класса

🚀 Перейти к определению: Ctrl + B

Для переменных — переходит к месту, где она была объявлена, для методов — к их реализации

🚀 Вернуться в предыдущий класс:
Ctrl + Alt + ⬅️
Ctrl + Alt + ➡️


IDEA хранит небольшую историю перемещений, по которой можно перемещаться стрелками. Так очень удобно править несколько связанных файлов

🚀 Найти строку по номеру: Ctrl + G

Когда коллега пишет: "проверь условие в строке 850", можно не проматывать огромный класс, а быстро перейти на нужную строку

Очень удобные шорткаты, обязательно попробуйте🔥
Полезное в PostgreSQL, часть 1

Алгоритмы с литкода — это База. Но только для собеседований, на практике вы редко их встретите.

С SQL ситуация обратная. На собесах спросят максимум джойн и HAVING, а на практике всё гораздо интереснее.

Сегодня расскажу простые и полезные приёмы PostgreSQL, с которыми ваши скрипты станут симпатичнее💅

1️⃣ JOIN + USING

Если при джойне таблиц имена столбцов совпадают, вместо ON используйте USING:
SELECT * FROM table1 
INNER JOIN table2 USING (user_id);


2️⃣ INSERT + SELECT

Чтобы вставить в одну таблицу значения из другой, нет смысла вытаскивать данные отдельно. INSERT и SELECT прекрасно комбинируются:
INSERT INTO users (id, name)
SELECT user_id, fullname FROM customers;


3️⃣ RETURNING

Чтобы после вставки вернуть новые строки, отдельный SELECT не нужен. Добавьте RETURNING для нужных столбцов:
INSERT INTO users VALUE (…)
RETURNING id;


Обычно возвращают id, но можно вернуть несколько столбцов и даже *.

Для DELETE RETURNING тоже работает и возвращает удалённые строки.

4️⃣ Тестовые данные

Для генерации простейших тестовых данных через SQL вам пригодятся:

🔸 generate_series(a, b) — последовательность целых чисел от a до b. Той же функцией генерятся даты в заданном диапазоне, синтаксис смотрите в документации

🔸 random() — случайное число от 0 до 1

🔸 md5(а) — хэш числа а. Помогает превратить результат random() в строку

Дальше комбинируем. Например, так:
INSERT INTO t
SELECT id, // 1, 2,…, 100
'name' || id, // name1,…, name100
random(),
md5(random()::text)
FROM generate_series(1,100) id;


Одним запросом получаем 100 строк в базе!

Приёмы выше хоть и простые, но пригодятся на большинстве проектов. Пользуйтесь и ставьте огонёчек, если нужна вторая часть🔥
Полезное в PostgreSQL, часть 2

Прошлый пост собрал столько огоньков, что аж на душе потеплело, спасибо❤️

Продолжим наш ликбез по SQL. Сегодня расскажу про 3 похожие конструкции для вложенных запросов: CTE, View и Materialized View.

⭐️ Сommon Table Expression (СТЕ) выглядит так:
WITH cte_name AS (
  SELECT …
)
SELECT … FROM cte_name;

cte_name используется как источник данных

⭐️ VIEW и MATERIALIZED VIEW выглядят похоже:
CREATE (MATERIALIZED) VIEW view_name AS
   SELECT … ;

Полученное вью также используется как источник данных:
SELECT … FROM view_name;


В чём же разница?

1️⃣ Что именно хранится

VIEW — это просто сокращение запроса, результат выполнения не сохраняется. При каждом FROM view_name запрос выполняется заново.

MATERIALIZED VIEW сохраняет результат на момент выполнения. По сути создаётся временная таблица с копией данных.

Важный момент: если исходные данные поменяются, они не повлияют на данные в MATERIALIZED VIEW. Для актуализации данных надо отдельно вызвать команду REFRESH.

СТЕ как MATERIALIZED VIEW сохраняет результат выполнения и считается один раз.

2️⃣ Видимость

CTE не существует сам по себе, за ним обязательно должен следовать запрос, который его использует. Можно сказать, что область видимости и время жизни CTE — один запрос.

VIEW и MATERIALIZED VIEW доступны на уровне схемы, можно пользоваться много раз из разных мест. Удалять вью надо явно командой DROP.

Это основные отличия. Есть ещё несколько, но не будем углубляться:)

Примерные кейсы использования:

🔧 CTE — сделать сложные запросы более читаемыми
🔨 View — синоним для популярного запроса на выборку
🪛 Materialized view — снимок данных, которые долго считать с нуля, но к которым много обращений

Теперь закрепим знания из этого и предыдущего поста небольшой задачкой✍️

Есть 2 таблицы:

🔸 from_table со столбцами id, firstname, lastname.
🔸 to_table со столбцами id, name

Задача: перенести все строки из from_table в to_table, соединив firstname и lastname в одно поле name. После выполнения запроса from_table должен стать пустым.

Попробуйте выполнить задачу ОДНИМ запросом.

Онлайн Postgres: pgplayground

Исходный код для экспериментов:
CREATE TABLE from_table(id int, firstname text, lastname text);
INSERT INTO from_table VALUES(1, 'F1', 'L1');
CREATE TABLE to_table(id int, name text);


РЕШЕНИЕ

Вспоминаем, что DELETE возвращает удалённые строки. Формируем из них СТЕ и передаём в INSERT:
WITH deleted_rows AS (
   DELETE FROM from_table
   RETURNING id, firstname || ' ' || lastname
)
INSERT INTO to_table
   SELECT * FROM deleted_rows;
REST API

— неоднозначная тема, по которой часто много вопросов и споров. В этом посте кратко расскажу, что такое REST API и как он связан с REST. И самое интересное — покажу принципы REST API на реальных примерах.

Начнём сначала.

REST — это набор архитектурных принципов, которые описал Roy Fielding в диссертации 2000 года. Один из тезисов гласит, что взаимодействие между системами должно крутиться вокруг понятия ресурса.

REST API, в свою очередь, это набор рекомендаций для API: как составить URL-ы, что они принимают и возвращают. От REST здесь берётся только понятие ресурса. Остальное — частные интерпретации, в оригинальном документе ничего этого нет.

Под REST API обычно понимают следующий набор правил:

1️⃣ В основе пути — существительное во множественном числе

https://www.youtube.com/watch?v=123
https://rutube.ru/video/123

Множественное число встречается чаще, но на мой взгляд единственное тоже ок. Главное, чтобы в рамках проекта был единый стиль, и не смешивалось единственное и множественное

2️⃣ Иерархия ресурсов отражается в URL

Например, в магазине одежды есть раздел с футболками. Как это отразить в API:
https://www.lamoda.ru/c/2478/clothes-futbolki/
https://www.sportmaster.ru/catalog/zhenskaya_odezhda/futbolki/

3️⃣ Желаемые действия с ресурсом определяются через HTTP методы

Искусственный пример:
▫️ GET /users — вернуть список всех пользователей
▫️ GET /users/5 — получить пользователя с id=5
▫️ POST /users — добавить пользователя
▫️ PUT /users/5 — обновить пользователя с id=5 целиком
▫️ PATCH /users/5 — обновить часть полей у пользователя с id=5

Найти идеальный реальный пример у меня не получилось, но очень близок оказался HeadHunter API по работе с резюме. Там всё по канону, но для редактирования они используют PUT.

4️⃣ Вызов методов GET, DELETE, PUT, PATCH должен быть идемпотентным

Чтобы без проблем вызвать метод повторно, если что-то пошло не так

5️⃣ Методы PUT, POST and PATCH возвращают новый/обновлённый объект

6️⃣ Дополнительные параметры для метода GET указываются через ?, а тело метода остаётся пустым

GET /resumes?text=java&age_from=18 – поиск совершеннолетних кандидатов, в резюме которых встречается "java"

7️⃣ Информация для POST, PUT, PATCH запросов передаётся в теле метода

8️⃣ В качестве ответа возвращается соответствующий HTTP код
▫️ 1хх: информационные сообщения, чаще всего служебные. Для бизнес-логики не используются
▫️ 2xx: всё супер
▫️ 3xx: redirect
▫️ 4xx: ошибка на стороне клиента
▫️ 5xx: ошибка на стороне сервера

Какие плюсы у REST API?

Плюс на самом деле только один — в таком API проще разобраться. У Stepik REST API нет документации, но и так понятно, что GET /api/courses возвращает список курсов.

Как видно по примерам выше, некоторые компании придерживаются подобных правил, некоторые — нет. Rutube более REST API, чем Youtube, но для успеха продукта этого явно недостаточно🤭
Костыль в JDK и сортировка списков

Вести абстрактные разговоры о разработке легко и приятно. Можно два часа рассуждать, что такое хорошее API, но гораздо полезнее обсудить конкретные примеры. Сегодня разберём метод сортировки.

Если показать вопрос перед постом питонисту, он однозначно выберет list.sort(). Хотя бы потому что в питоне есть такой метод.

Класс Integer реализует интерфейс Comparable, сортировка чисел — базовая функциональность любого языка программирования. Так что метод sort() максимально логичен.

Однако в интерфейсе List нет такого метода, только
void sort(Comparator<?super Е> c) {…}

Для элементарной операции сортировки чисел приходится писать
list.sort(Comparator.naturalOrder())

Код с Comparator.naturalOrder() похож на какой-то костыль. Под капотом не происходит ничего особенного, реализация компаратора очень простая:
(с1, с2) -> c1.compareTo(c2)

Так зачем писать так сложно? Почему в интерфейсе List нет метода sort()?

Сейчас расскажу:)

Java создавался как язык для больших и долгоживущих приложений, и его основные ценности — стабильность и обратная совместимость.

C начала 2000-х в JDK есть метод Collections.sort(List). Статический метод, который меняет внутреннее состояние аргумента. Сейчас это порицается, но в те времена было норм.

В больших компаниях классы JDK часто расширяли удобными методами, в том числе сортировкой в функциональном стиле:
CustomList sorted = list.sort();

Спустя много лет стало понятно, что экземплярные методы сортировки — это классно, и надо добавить такой метод в JDK. Чтобы текущие реализации списков не сломались, это должен быть дефолтный метод в интерфейсе List.

Но есть проблема. Допустим, на проекте есть такой класс:
public class CustomList implements List {
public CustomList sort() {…}
}


Допустим, в java 8 в интерфейс List добавили бы метод
default void sort() {…}

Старый метод не может переопределить дефолтный. тк возвращаемые значения не совместимы. Поэтому проекты, которые определили свой функциональный sort в начале 2000-х, перестанут компилироваться. Пользователи будут недовольны😡

Многие проекты полагаются на свой sort, поэтому разработчики JDK не стали добавлять его в интерфейс. Метод sort(Comparator) использовался редко, поэтому теперь он с нами.

У Stream API нет проблем с совместимостью, так что для стримов есть прекрасный метод sorted(). Для коллекций метод sorted() есть в Kotlin💖
(обратите внимание на суффикс -ed, всё по правилам функционального подхода)

Ответ на вопрос перед постом: отсортировать список можно так:
list.sort(Comparator.naturalOrder());
list = list.stream().sorted().toList();


Если вам понравился list.sort(), значит у вас хороший вкус на API. К сожалению, у java свои загоны, поэтому этого метода в JDK нет.
В классе Order есть поле sum (сумма заказа). Мы хотим отсортировать заказы от большей суммы к меньшей. Что написать внутри метода orders.sort(…)?
Anonymous Poll
33%
(o1, o2) -> (int) (o1.getSum() - o2.getSum())
67%
(o1, o2) -> (int) (o2.getSum() - o1.getSum())
Как написать компаратор

Продолжим тему прошлого поста про сортировки и функциональный подход. Итак, компаратор задаёт правило сравнения элементов между собой. Делает он это с помощью метода compare:
public int compare(T o1, T o2) {…}

Если метод вернул
▫️ число больше нуля — первый элемент больше второго
▫️ 0 — элементы равны
▫️ число меньше нуля — первый меньше второго

Простейшая и популярная реализация — вычесть одно значение из другого:
(o1, o2) -> (int) (o1.getSum() - o2.getSum())

Что с этим не так?

Я всегда сомневаюсь, что из чего вычитать. Если вы отвечали на опрос дольше одной секунды, значит мы в одном лагере:) Компаратор — совсем не то место, где мозг должен спотыкаться.

В Java 8 в интерфейсе Comparator появился удобный метод:
orders.sort(comparing(Order::getSum))

Что классно:
Не надо вспоминать, что из чего вычитать
Легко сделать сравнение в обратном порядке:
comparing(Order::getSum).reversed()
Можно учесть null:
nullsFirst(comparing(Order::getSum))
nullLast(…)

Удобно сортировать по нескольким полям:
comparing(Order::getSum).thenComparing(Order::getId)

Самостоятельно обрабатывать null и писать сложные сортировки очень утомительно. Помню, как с удовольствием удаляла из проекта компараторы на 20 строк после перехода на Java 8😊

Важные нюансы:

1️⃣ comparing*

В интерфейсе Comparator также доступны методы comparingInt, comparingLong и comparingDouble. Используются для полей примитивного типа, чтобы избежать лишнего боксинга. Если в классе Order

Long id → используем comparing(Order::getId)
long id → comparingLong(Order::getId)

Не указывайте тип лишний раз. Для работы с объектами подойдёт обычный comparing

2️⃣ Нетривиальная работа с null*

В обычных методах легко понять, что происходит:
comparing(A).reversed().thenComparing(Б)
=
отсортировать по полю А в обратном порядке, дубликаты отсортировать по Б

Методы null* выбиваются из этой схемы.
nullsFirst(comparing(Order::getSum))
означает, что первыми будут null объекты, а существующие заказы отсортируются по сумме. Этот компаратор работает для такого кода:

orders.add(null); // эти элементы будут впереди
orders.add(new Order(…)); // эти отсортируются по полю sum

Если в списке нет null объектов, но в поле sum возможен null, придётся писать так:
…comparing(Order::getSum, nullsFirst(naturalOrder()));

Сравнение по нескольким nullable полям выглядит совсем плохо. К счастью, на практике такие задачи встречаются редко.

Ответ на вопрос перед постом:
(o1, o2) -> (int) (o2.getSum() - o1.getSum())

Но лучше использовать comparing(Order::getSum).reversed()
Какой паттерн на практике считается альтернативой паттерну Saga?
Anonymous Poll
35%
Message router
14%
Claim check
14%
Service activator
21%
Retry
17%
Circuit breaker
Сага

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

Какую проблему решаем?

В системе много компонентов: стайка микросервисов, несколько БД, кэши и месседж брокер. Каждый микросервис в норме работает с одной БД и имеет четкий круг обязанностей.

Для многих задач нужно пройтись через несколько сервисов и зафиксировать изменения в нескольких БД.

Пример — покупка в интернет-магазине. Создать заказ, проверить наличие, снять деньги и тд. Если на каком-то шаге случится ошибка, предыдущие изменения можно откатить, чтобы система осталась в согласованном состоянии.

Для таких случаев на сцену выходит паттерн сага в двух вариантах: хореография и оркестрация.

💃 Хореография — каждый сервис “танцует” свой набор движений: знает, что делать при ошибке и кого предупредить. Например:
▫️ В сервисе А происходит ошибка
▫️ Сервис А шлёт сервису Б событие “я не смог”
▫️ Сервис Б дёргает у сервиса В команду “отмена”
▫️ Система снова в согласованном состоянии🥳

Плюсы:
Легко сделать для простых бизнес-процессов

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

🥁 Оркестрация — когда за порядком следит отдельный сервис. Если кто-то не смог, оркестратор шлёт остальным участникам компенсирующие указания.

Плюсы:
Сервисы меньше знают друг о друге, и логика внутри проще. Сервис оплаты не придумывает, что делать при отмене заказа, а получает четкую команду “Верни деньги”
Информация о шагах находится в одном месте
Легче понять, что происходит и разобраться в ошибках
Проще менять шаги в процессе
Проще в управлении, если в системе несколько сложных процессов

Минусы:
Ещё один компонент. Дополнительный деплой, мониторинг, страничка в конфлюенс и тд

Как реализуется оркестрация?

Есть миллион библиотек и фреймворков, например, Apache Camel или Netflix Conductor. Упомянете их — получите плюсик.

Почему сага редко встречается?

Система не выполняет в итоге нужное действие. Чаще проблема решается через Retry или Fallback. Откат предыдущих шагов — очень крайняя мера.

Собственно, на собесе ждут, что кандидат расскажет все, что выше. Хорошо, если на этом моменте обсуждение закончится, и вы перейдете к следующему вопросу.

Но в обсуждении саги есть важный нюанс, который меняет все. Если его не понимать, напротив вашей кандидатуры появится жирный минус.

Расскажу в следующем посте, а сейчас жду ваших огоньков🔥
Распределенные транзакции

Кукусики, спасибо за огоньки к прошлому посту! Очень приятно, вижу, что цените качественный контент🥰

А теперь к делу. В посте про сагу мы сформулировали, что она нужна для распределенных транзакций. Если на собесе упоминается термин “распределенная транзакция”, грех не спросить, что же это такое.

Удивительно, но на этом шаге многие сыпятся🤷

Транзакция — очень многозначительное понятие в айтишке, но первое, что приходит на ум — транзакции в базе данных.

Соответственно, распределённая транзакция — набор изменений в нескольких БД, которые выполняются атомарно. В любой момент времени система находится в согласованном состоянии.

— И как это, хорошо или плохо? — спрашивают вас
Чувствуете подвох, но пока не понимаете где:
— Да нормально вроде…

Интервьюер понимает, что вы опасны для проекта и ставит жирный минус.

Что не так с распределенными транзакциями в БД?

Во-первых, медленно работают. Во-вторых, часто являются признаком косяков в архитектуре.

Каждый сервис выполняет задачи определенного типа. Сервис заказов работает с заказами, сервис логистики — с маршрутами и тд. Все нужные сервису данные обычно лежат в одной БД.

🚩 Распределённая транзакция в БД — это обычно знак, что мы неверно распределили функции по сервисам.

Второе значение слова транзакция — набор изменений, которые происходят атомарно. Либо все завершатся успешно либо ни одно.

Тут нет отсылок на БД, нет требования одновременности и согласованности в каждый момент времени. И это уже нормальный вариант для жизни. Если что-то пошло не так, делаем ретрай или потихоньку откатываем изменения.

Ещё раз подсвечу разницу.

🛢️ Распределённая транзакция в БД:
▫️ Данные в системе согласованы в каждый момент времени
▫️ Гарантия распространяется только на изменения в БД
▫️ Сложно и долго
▫️ Признак плохой архитектуры

🎩 Распределённая транзакция бизнес-процесса:
▫️ Eventual consistency. Однажды всё станет хорошо, но в процессе возможны несогласованные данные
▫️ Можно менять состояние не только в БД, но и в других сервисах, файлах, отправлять сообщения в месседж брокер
▫️ Не требует сложных блокировок

Термин один, а значения абсолютно разные! Обидно, когда кандидат их путает и предлагает масштабно блокировать систему, где это не требуется.
Посолить, поперчить по вкусу

Пароли пользователей нужно беречь. Для защиты используется целый комплекс мер: от безопасной передачи по сети до шифрования данных на уровне БД.

Сегодня поговорим о способе защиты, с которым может встретиться любой бэкендер — соли🧂. Обсудим, что это и зачем она нужна.

Начнём с основ. Почему нельзя хранить пароль как обычный текст?

🥷🏼 Любой человек с доступом к БД сделает select, запишет логин-пароль и никто об этом не узнает
🥷🏼 Злоумышленник найдёт бэкап БД и прочитает данные

Первый логичный шаг — хранить не сам пароль, а его хэш. Операция однонаправленная: проверить введённый пароль можно, а вот получить пароль из хэша нельзя.

Почему хэш — это недостаточно безопасно?

Злоумышленник может воспользоваться rainbow tables🌈. Это база, где хранятся миллиарды хэшей разных строк. Это не какой-то даркнет, есть много сайтов с таким функционалом. Например, этот.

И здесь на сцену выходит наш главный герой🦸‍♂️

Соль — это последовательность символов, которая добавляется к паролю перед расчётом хэша:
Пароль: qwerty
Соль: 34%$b
В БД записываем хэш от qwerty34%$b

Если злоумышленник узнал хэш пароля, вероятность, что этот хэш найдётся в rainbow table ощутимо снижается.

Соль генерируется случайным образом, у каждого пользователя она своя. Может хранится вместе с паролем или в отдельном поле в БД.

🌶 Pepper

При доступе к БД можно прочитать и засоленный пароль, и саму соль. Восстановить исходный пароль сложно, но если очень хочется, то можно. Чтобы усилить защиту, используется вариация под названием pepper (или secret salt). В этом случае последовательность символов, которая добавляется к паролю, хранится не в БД, а в другом месте.

Spring Security

К счастью, Spring предлагает готовые энкодеры, которые упрощают манипуляции с солью:

🔸 BCryptPasswordEncoder
генерирует соль и хранит её в итоговой строке. Разработчику ничего делать не надо, всё работает само:)

🔸 Pbkdf2PasswordEncoder
тоже сам разбирается с солью, плюс можно передать pepper в конструкторе объекта.

Ответ на вопрос перед постом: соль помогает в случае, когда злоумышленник прочитал из базы поле password. Она снижает вероятность, что по хэшу можно восстановить исходный пароль.
Как быстро отдавать данные по частям

Пагинация с первого взгляда кажется простой задачей. На практике это та ещё кроличья нора🌚

В этом посте расскажу 2 подхода к пагинации со стороны SQL, их плюсы, минусы и основные кейсы. За кадром оставим нюансы спринга и хибернейта.

Итак, пагинация бывает двух видов: offset и keyset.

🌸 Offset - для получения части данных используется (сюрприз) оператор offset. Первую страницу получаем так:
SELECT * FROM t ORDER BY …
LIMIT 100

Для второй и последующих добавляем offset:
SELECT * FROM t ORDER BY …
LIMIT 100 OFFSET 100

Offset показывает, сколько строк пропустить.

В чем подвох: кажется, что при offset 100 постгрес сразу прыгнет на 101 строку, но это не так. Он честно пройдет 100 строк, и потом отдаст следующие 100.

В случае большого оффсета такая операция будет работать долго даже при наличии индекса
Просто реализовать. Минимум действий со стороны клиента и поддержка в фреймворках.

🌸 Keyset пагинация. Она же cursor, она же seek.

Здесь мы используем where в паре с индексом и сразу прыгаем на нужную позицию. Первую страницу получаем так:
SELECT * FROM t ORDER BY name
LIMIT 10

Если записи отсортированы по имени, запоминаем имя последнего элемента. Пусть это будет х. Чтобы получить вторую страницу, добавляем where:
SELECT * FROM t ORDER BY name
WHERE name > x
LIMIT 10

Работает быстрее, чем вариант с оффсетом. Чем больше оффсет, тем значительнее разница
Сложнее в реализации, надо запоминать поле в последней пачке
Нельзя перейти к произвольной странице, только последовательная выдача данных

Когда что использовать?

Во многих докладах/статьях часто встречается мысль, что оффсет это ужас, и надо всегда использовать кейсет. На практике все не так категорично:

▫️ Если на сайте infinite scrolling или пользователь вряд ли будет много смотреть, вполне ок использовать простую оффсет пагинацию.

▫️ Если данные выкачиваются большими пачками для обработки, аналитики, миграции и тд, берём keyset пагинацию.

▫️ Если надо выгрузить много данных и прыгать по ним туда-сюда или у вас другой сложный случай, придется экспериментировать с индексами и запросами. Этот доклад хорошо показывает, что пагинация - далеко не тривиальная задача.

А поставить огонек качественному контенту очень просто, поэтому это нужно обязательно сделать🔥
Вышла Java 23🎉

Это не LTS версия, почти все фичи в превью стадии. Этап важный, но не самый интересный.

Поэтому подниму другую тему.

Я уже несколько лет делюсь знаниями в этом канале. У меня хорошо получается видеть главное и подсвечивать неочевидные моменты. Хочу продолжать в том же духе.

Помогите мне с контент-планом:)

В чем вам помочь разобраться? Какие знания хочется углубить?

Заполните, пожалуйста, эту форму. Всего 3 вопроса!
Please open Telegram to view this post
VIEW IN TELEGRAM
GraalVM, часть 1

Последние годы на конференциях встречаются доклады про GraalVM. У них мало просмотров, тк есть мнение, что Грааль — что-то экспериментальное и далёкое от коммерческой разработки.

Но нет!

GraalVM — проект, который объединяет в себе целую смесь идей и технологий. В этом посте расскажу о самом близком к практике и понятном компоненте — JIT-компиляторе Graal и зачем он нужен.

Начнем с основ:
✍️ Программист пишет java код
✍️ Компилятор превращает его в байт-код
✍️ JIT-компилятор внутри JVM переводит байт-код в системные вызовы для конкретной ОС

JVM использует два типа компиляторов: С1 и С2:
🐍 С1 (client) — для десктопа, браузера и приложений на слабом железе. Быстрый старт, низкое потребления памяти, но мало оптимизаций
🐊 С2 (server) — для больших и мощных серверов. Алгоритмы сложнее, памяти нужно больше, зато на выходе более оптимизированный нативный код

Разделение было актуально 20 лет назад. Сейчас железо мощнее и дешевле, поэтому с java 8 работает смешанный тип (tiered compilation) с участием обоих компиляторов.

Технологии позволяют сделать компиляцию быстрее, но вносить серьезные изменения в С1 и С2 проблематично, так как код сложный и запутанный.

Тот случай, когда проще написать заново:)

В Java 9 работа с JIT-компилятором скрылась за отдельным интерфейсом JVMCI — JVM Compiler Interface. Теперь можно собрать JVM с другим компилятором. Например, с новым и свежим JIT компилятором Graal.

В OpenJDK включается флажками
-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler

Работает пока только для Linux, иногда показывает небольшой прирост скорости. Так что имеет смысл попробовать!

В GraalVM есть ещё одно интересное направление, и о нем расскажу в следующем посте🔥
GraalVM, часть 2: native image

Продолжаем вникать в GraalVM. Сегодня поговорим о самой интересной его части — native image билдер.

Он компилирует java код и все нужные классы сразу в бинарный файл. Нет промежуточного этапа в виде байткода, а значит для запуска не нужна JVM.

Джава код запускается без JVM🤯

Бинарник собирается под конкретную платформу, поэтому код больше не write once, run anywhere! Одна из киллер фич java отправляется на антресоль.

Что получаем от такого богохульства:
Быстрый старт, тк все классы уже загружены
😐 Немного усложняется CI и тестирование
😡 Нужно адаптировать код

Например, статические блоки по классике выполняются при загрузке класса. Если класс не загружается, блок не выполнится.

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

😡 Если приложение использует библиотеки, они тоже должны предоставлять нейтив вариант
😡 Долгая компиляция

Сама с native images не сталкивалась, но видела доклад, как их используют в одном из проектов Сбера. Так что вещь рабочая, хоть и специфичная.

Spring native image🍃

Интересный проект по адаптации Spring под нейтив имедж. Обычные спринг приложения стартуют несколько минут. Ожидается, что native вариант запустится за несколько секунд!

Сложность в том, что в спринге большая часть магии происходит на старте. Активно используется рефлекшн и создаются прокси-классы. Чтобы адаптировать Spring под native, надо переписать огромную часть кода.

Плюс не все библиотеки легко адаптируются под нэйтив. Например, сейчас нет поддержки Mockito. А куда мы без него?

Поэтому сохраняется интрига — непонятно, что в итоге получится, и стоит ли оно того. Но следить за этим очень интересно🥰

Подведем итог

GraalVM — проект, объединяющий несколько направлений:
🌸 JIT компилятор. Уже сейчас можно запустить на OpenJDK и OracleJDK и чуточку ускорить приложение
🌸 Виртуальная машина GraalVM
🌸 Сборщик native image. Интересен и сам подход, и проекты, которые его используют, например, Spring native image

Есть в GraalVM и другие штуки, например, мультиязычный рантайм. Но они вряд ли пригодятся большинству из нас, поэтому не будем о них😊