12073
Присоединяйтесь к нашему каналу и погрузитесь в мир для Java-разработчика Связь: @devmangx РКН: https://clck.ru/3H4WUg
Уменьшаем время выборки из БД с 2–3 секунд до ~100 мс:
SELECT * FROM transactions
WHERE user_id = 40
ORDER BY created_at DESC
LIMIT 20 OFFSET 10000;
SELECT * FROM transactions
WHERE user_id = 40
AND created_at < '2024-05-01 10:00:00'
ORDER BY created_at DESC
LIMIT 20;
WHERE (created_at, id) < ('2024-05-01 10:00:00', 98765)
ORDER BY created_at DESC, id DESC
Это расширение для Chrome прям находка для тех, кто активно сидит на GitHub
Называется SimRepo и показывает похожие репозитории для любого открытого проекта.
Просто устанавливаешь и забываешь — расширение автоматически срабатывает каждый раз, когда открываешь репозиторий.
Полезно, если хочешь найти что-то лучше, посмотреть альтернативы или просто открыть для себя новые проекты во время поиска ☺️
👉 Java Portal
Быстрый Kafka consumer на виртуальных потоках.
Каждое новое сообщение, которое получает listener, обрабатывается в отдельном виртуальном потоке.
Естественно, при таком подходе теряется порядок сообщений внутри одного partition.
Выбирай, что для тебя важнее.
👉 Java Portal
Java-команды часто ищут прорывы не там.
Spring + JVM получает больше профита от Kotlin, чем от попыток перезабрести фреймворк.
Kotlin прокачивает базу.
А именно база решает, движется ли команда быстро или нет
1. Код становится проще.
Меньше шаблонного мусора.
Более безопасные дефолты.
Четче выраженные намерения.
Команда делает те же фичи быстрее и с меньшим количеством багов.
2. Работа с конкурентностью становится чище.
Коррутины стабильнее и удобнее, чем вечная возня с потоками.
Та же JVM. Тот же Spring.
Но асинхронный код наконец становится предсказуемым
3. Миграция с низкими рисками.
Никаких переписанных с нуля сервисов.
Никаких параллельных стеков.
Никакого большого взрыва.
Kotlin можно внедрять точечно - новые модули, новые сервисы, тесты, внутренние тулзы.
Эффект накапливается довольно быстро.
Так выглядит распространенный сценарий в успешных командах:
Один инженер пробует -> остальные замечают разницу -> adoption летит вверх.
4. Бизнес тоже ощущает разницу.
Меньше дефектов.
Быстрее онбординг.
Выше скорость разработки.
Лучше асинхронное поведение.
И все это без замены стека.
Kotlin не переписывает ваш проект.
Он исправляет мелочи, которые тормозят бекенд-команду годами.
Если ты уже на Spring + JVM, это самый выгодный апгрейд, который можно выкатить хоть завтра 🍌
👉 Java Portal
Java-подсказка: начиная с Java 11, если нужно повторить строку n раз, можно использовать метод repeat(n) у String.
👉 Java Portal
Всегда есть шанс, что один и тот же запрос прилетит в ваш API или сервер несколько раз.
Даже если вы отключаете кнопку после первого клика, вероятность уменьшается, но не исчезает.
Это решается идемпотентностью.
Идемпотентность — это когда одна и та же операция, выполненная несколько раз, даёт строго один и тот же результат.
Не похожий. Не почти такой же. А точно такой же.
Например, в платежных системах это обязательное свойство.
Это разница между стабильной системой и системой, которая может уронить бизнес.
Представим эндпоинт /payment/charge.
Если пользователь дважды жмёт кнопку оплатить, приложение не должно интерпретировать это как две транзакции.
По сути решение простое:
клиент отправляет idempotency-key, а сервер гарантирует, что операция будет выполнена только один раз.
Но правильная реализация важна.
Недостаточно просто игнорировать дубликаты. Нужно сохранять результат первой операции и возвращать его при повторных запросах, даже если второй запрос пришёл раньше, чем завершилась первая обработка.
Это значит, что потребуется хранить состояния, промежуточные результаты и думать об операции как о той, которую можно повторить без нарушения согласованности.
Без идемпотентности API ведет себя непредсказуемо.
С идемпотентностью можно пережить сетевые сбои, таймауты, повторные подключения и слишком быстрых юзеров, не ломая систему.
👉 Java Portal
Spring Boot: Spring Data JPA имеет встроенную поддержку пагинации через Pageable.
Лучше использовать пагинацию в репозиториях вместо того, чтобы вытягивать все данные разом.
Вместо такого репозитория:
public interface BookRepository extends JpaRepository<Book, Long> {
List<Book> findAll();
}public interface BookRepository extends JpaRepository<Book, Long> {
Page<Book> findAll(Pageable pageable);
}public Page<Book> getBooks(int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
return bookRepository.findAll(pageable);
}
Очень в тему перечитать этот классический материал от Майка Бостока. Отлично ложится как продолжение поста.
https://bost.ocks.org/mike/algorithms/
👉 Java Portal
Путаешь Dockerfile и Docker Compose?
Тогда вот короткое пояснение.
Они дополняют друг друга, но выполняют разные задачи в контейнерной инфраструктуре.
Dockerfile используется для создания и сборки Docker-образов.
Docker Compose используется для запуска контейнеров как части многоконтейнерной среды или с заданными параметрами выполнения.
Примечание. Начиная с версии 1.28.6, Docker Compose по умолчанию ищет compose.yaml или compose.yml, но сохраняет обратную совместимость с docker-compose.yaml/yml. Если в проекте есть оба варианта, приоритет у compose.yaml.
Прикрепил простую визуализацию, чтобы было легче разобраться.
👉 Java Portal
В Java такое видел почти каждый:
var user = new User("Jordy", "Colombia", 28, true, "admin");
Код компилируется, но никто не помнит, что означает каждый параметр (если только IDE не подсказывает).
Порядок важен, значения путаются, а любое изменение превращается в клоунаду.
Классический конструктор начинает убивать читаемость.
От этого и спасает Builder:
var user = User.builder()
.name("Jordy")
.country("Colombia")
.age(28)
.active(true)
.role("admin")
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("url"))
.GET()
.build();
Почему Redis однопоточный (и почему из-за этого он быстрый)
У тебя сервер на 32 ядра, а Redis грузит только одно.
На вид неэффективно. Но при этом Redis остается одной из самых быстрых in-memory баз.
Почему так?
Основной bottleneck обычно не CPU
Для типичных операций Redis вроде GET, SET, INCR CPU почти не тратится.
В реальных условиях упираешься в другое:
- пропускная способность сети (ответы клиентам)
- пропускная способность памяти (RAM <--> CPU cache)
- системные вызовы, TCP, epoll и другая возня ядра
В таких условиях добавление потоков не дает прироста, потому что ограничение в другом.
Почему мультипоточность сделала бы Redis медленнее?
Если бы несколько потоков трогали одну область данных, понадобились бы:
Локи
Чтобы безопасно читать или писать ключи. Ожидание блокировок добавляет задержки.
Context Switching
Переключение между 32 потоками сжигает ресурсы и ломает кэш-локальность.
Cache Coherence
Когда один поток обновляет данные, которые кэшировал другой, аппаратная часть должна инвалидировать кэши по ядрам.
Только что выкатили демо, показывающее, почему Spring Data AOT реально важен для производительности.
Перенос парсинга запросов из рантайма в стадию компиляции дает:
- запуск приложения быстрее на 50–70%
- ошибки в запросах ловятся до деплоя
- SQL генерируется заранее на этапе сборки
- всё готово для GraalVM Native
Репозиторий:
https://github.com/danvega/spring-data-aot
👉 Java Portal
25 золотых правил системного дизайна
1. Система с упором на чтение
> Используй кэш (Redis/Memcached) для частых запросов вроде профилей пользователей
2. Низкая задержка
> Используй кэш и CDN (Cloudflare), чтобы раздавать статику ближе к пользователю
3. Система с упором на запись
> Используй Message Queue (Kafka) для буферизации большого объема записей (логи, аналитика)
4. ACID-требования
> Используй SQL (PostgreSQL) для строгих транзакций вроде банковских операций
5. Неструктурированные данные
> Используй NoSQL (MongoDB) для гибких схем, например каталогов товаров
6. Сложные медиа-ресурсы
> Используй Blob Storage (AWS S3) для видео, изображений и больших файлов
7. Сложные предварительные расчёты
> Используй Message Queue + Cache для асинхронной генерации контента (например ленты новостей)
8. Поиск при больших объемах данных
> Используй Elasticsearch для полнотекстового поиска и автокомплита
9. Масштабирование SQL
> Используй шардирование, чтобы разделить большие таблицы на несколько инстансов
10. Высокая доступность
> Используй Load Balancer (NGINX) чтобы распределять трафик и избегать перегрузки
11. Глобальная доставка данных
> Используй CDN для стабильного стриминга и раздачи контента по всему миру
12. Графовые данные
> Используй Graph DB (Neo4j) для соцсетей, рекомендаций и связей между сущностями
13. Масштабирование компонентов
> Используй горизонтальное масштабирование, а не просто апгрейд железа
14. Быстрые запросы к базе
> Используй индексы на ключевых колонках вроде email или user_id
15. Пакетные задачи
> Используй Batch Processing для отчётов, расчётов или периодических задач
16. Защита от злоупотреблений
> Используй Rate Limiter, чтобы предотвращать DDoS и спам запросов к API
17. Доступ к микросервисам
> Используй API Gateway для авторизации, маршрутизации и SSL-терминации
18. Единая точка отказа
> Добавляй Redundancy (Active-Passive), чтобы сервис продолжал работать при сбоях
19. Отказоустойчивость данных
> Используй репликацию (Master-Slave), чтобы данные не терялись при падении узлов
20. Реальное время
> Используй WebSockets для чатов, лайв-обновлений, лайв-результатов
21. Обнаружение сбоев
> Используй Heartbeat-пинг, чтобы проверять статус сервисов каждые несколько секунд
22. Целостность данных
> Используй Checksums (MD5/SHA) чтобы проверить, что загруженные файлы не повреждены
23. Децентрализованное состояние
> Используй Gossip Protocol, чтобы ноды обменивались статусами без центрального сервера
24. Эффективное кеширование
> Используй Consistent Hashing, чтобы добавлять или убирать кэш-ноды без полного пересчёта ключей
25. Работа с геоданными
> Используй Quadtree или Geohash для быстрых запросов вроде поиска ближайших водителей
Spring Boot: на этапе разработки можно включить spring.main.lazy-initialization=true, чтобы ускорить запуск приложения.
По умолчанию Spring Boot инициализирует все бины при старте. В среде разработки это приводит к:
Более долгому старту приложения, особенно в крупных проектах
Приходится ждать загрузки бинов, которые прямо сейчас даже не нужны
Чтобы избежать этого, в application.properties укажите:
spring.main.lazy-initialization=true
Совет по Java: можно использовать Collectors.joining(", ") чтобы собрать Stream в строку с разделителем запятая (или любым другим, если нужно).
Обычный способ:
List<String> items = List.of("a", "b", "c");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < items.size(); i++) {
sb.append(items.get(i));
if (i < items.size() - 1) {
sb.append(", ");
}
}
System.out.println(sb.toString());List<String> items = List.of("a", "b", "c");
String result = items.stream().collect(Collectors.joining(", "));
System.out.println(result.toString());
Совет по Java: используй WeakHashMap для кеширования, если ключи без ссылок должны автоматически очищаться сборщиком мусора.
В HashMap, пока объект карты существует, ключи и связанные с ними значения не будут собраны сборщиком мусора.
В WeakHashMap ключи хранятся через слабые ссылки, и если на ключ больше нет сильных ссылок в других частях программы, GC может освободить его, и он будет автоматически удалён из карты.
Пример:
Map<User, String> map = new WeakHashMap<>();
User u1 = new User("Mick");
map.put(u1, "Cached data");
...
u1 = null;
// Теперь ключ u1 может быть собран сборщиком мусора.
Подсказка по Java: начиная с Java 11 можно использовать String.strip() вместо trim(), потому что strip() корректно обрабатывает пробелы в Unicode.
Так как trim() не обрабатывает некоторые типы пробелов, определенные в Unicode, он может оставлять неожиданные символы.
String.strip() использует Character.isWhitespace(int codePoint) для определения пробельных символов, учитывая полный стандарт Unicode, а не только ASCII, и удаляет все виды пробелов.
Пример:
String text = "\u2003Hello World\u2003";
System.out.println("trim(): [" + text.trim() + "]");
System.out.println("strip(): [" + text.strip() + "]");
Какой Java Map быстрее для 1 млн обращений?
Производительность (в среднем):
HashMap get(): ~0.8 ms (O(1))
TreeMap get(): ~15 ms (O(log n))
HashMap put(): ~1.2 ms
TreeMap put(): ~18 ms
Если у тебя начинают получаться сложные вложенные подзапросы, лучше перейти на CTE , чтобы сделать запросы читабельнее.
CTE позволяет вынести часть логики в отдельный блок и использовать результат как временную таблицу. Это помогает структурировать запросы и делать их понятнее.
👉 Java Portal
Совет по Spring Boot
RestTestClient для тестирования API
RestTestClient это единый инструмент для тестирования REST API, который можно использовать вместо WebTestClient, TestRestTemplate (удалён) или MockMVC.
1. Определи объект RestTestClient в тесте
2. Привяжи его к конкретному компоненту. Это может быть controller, MockMVC, server или Spring context
3. Потом используй RestTestClient в тесте
@SpringBootTest
public class PersonControllerWithHeadersTests {
private WebApplicationContext context;
private RestTestClient restTestClient;
@BeforeEach
public void setup(WebApplicationContext context) {
restTestClient = RestTestClient
.bindToApplicationContext(context)
.build();
}
@Test
void addPersonTest() {
restTestClient.post()
.uri("/persons")
.body(Instancio.create(Person.class))
.exchange()
.expectStatus().is2xxSuccessful()
.expectBody(Person.class)
.value(p -> assertNotNull(p.getId()));
}
}
Java 21. Виртуальные потоки + структурированная конкуренция в 15 строках:
Запускай 100000+ конкурентных задач почти без оверхеда с виртуальными потоками.
Структурированная конкуренция делает async-код читаемым как синхронный, без утечек и callback-адского.
В реальных проектах даёт 10-кратный рост пропускной способности при гораздо более простом коде.
👉 Java Portal
Есть забавная вещь, которая происходит со многими Java-разработчиками. Oни пишут современный код с мышлением старой Java.
И, если честно, их сложно за это винить.
Java менялась куда быстрее, чем комьюнити успевало к этим изменениям адаптироваться.
Годами Java ассоциировалась с огромными классами, кучей геттеров и сеттеров, громоздкими фабриками, бесконечными try/catch блоками. Со стороны казалось, что язык боится любого изменения.
Java была про структуры и правила.
Это был язык принципа = делай явно или не делай вообще.
Но потом подъехали серьезные обновления: лямбды, records, pattern matching, sealed-классы, streams, более выразительный switch, выведение типов, более декларативные API, и стиль, который стал ближе к функциональному.
У всего этого есть плюсы и минусы, понятно.
И внезапно Java перестала ощущаться языком начала двухтысячных и стала языком под 2025 год (ну или под 2020, кому как). Хотя многие до сих пор пишут так, будто на календаре 2010.
Но важно понимать! часто дело не в техническом выборе. Это может быть привычка, вкусы или просто невозможность перейти на новую версию.
И главный момент тут в том, что современная Java не заменяет классическую = они существуют параллельно. Хотя со временем, конечно, и "новая Java" тоже станет предметом споров и хейта, как старая.
Язык изменился, но команды, компании, легаси-фреймворки и технические решения десятилетней давности никуда не делись. Они тянут в обратную сторону и фактически продолжают поддерживать устаревшее представление о Java.
Java сегодня это умение жить сразу в двух эпохах.
Если ты понимаешь эту двойственность, тогда можешь использовать язык по максимуму.
Если нет, то просто будешь сражаться с прошлым, которого уже нет, и игнорировать настоящее, которое уже здесь и точно никуда не денется.
👉 Java Portal
В начале карьеры я удалил 5GB лог-файл на продовом сервере, который уже почти забился.
Я запустил df -h, ожидая, что использование диска упадёт. Не упало.
Всё ещё показывало 100% занято.
Ни ошибок, ни предупреждений. Просто те же цифры, будто я вообще ничего не удалял.
И вот тогда я понял, что удаление файла не всегда сразу освобождает место.
В Linux то, что мы называем "файлом", на самом деле состоит из двух частей: имени файла (по сути указателя) и inode (где лежат данные и метаданные). Когда ты удаляешь имя файла, ты просто убираешь указатель. Но inode с данными остаётся на диске, пока какой-то процесс держит файл открытым.
В моём случае веб-сервер всё ещё писал в тот самый лог. И хотя я удалил имя файла, процесс продолжал держать открытый файловый дескриптор. Inode оставался живым — его не видно обычными командами, но место он продолжал занимать.
Место освободилось только после перезапуска веб-сервера, когда все дескрипторы закрылись.
Поэтому нужны разные команды, чтобы увидеть реальную картину:
Проверить использование файловой системы:
df -h
du -sh /var/log/*
lsof +L1
Имя файла это всего лишь указатель на inode
Удаление происходит только тогда, когда inode больше никем не используется
При разборе проблем с диском всегда проверяй и df, и du
Java хотела добавить лямбды, но при этом не поломать JVM и не переписывать двадцатилетние API.
Решение? Сделать вид, что в Java есть функции первого класса, используя то, что уже было: интерфейсы с единственным абстрактным методом.
Так появились функциональные интерфейсы.
@FunctionalInterface
interface Operation {
int apply(int x, int y);
}
Operation add = (a, b) -> a + b;
System.out.println(add.apply(3, 4)); // 7
new Thread(() -> System.out.println("Hello from a thread")).start();
Хочешь избежать проблем, когда Cloudflare падает?
Или когда La Liga блокирует твои страницы?
Разработчик сделал open-source CLI, чтобы можно было легко отключать этот сервис в проектах за пару секунд.
$ npx disable-cloudflare@latest
Одна простая привычка, которая сильно упрощает поддержку Dockerfile, это сортировка многострочных списков пакетов по алфавиту.
На первый взгляд мелочь, но влияние на читаемость и будущую поддержку заметное.
Когда ставишь пакеты в многострочном apt-get install или apk add блоке, легко случайно добавить дубликат или пропустить что-то, что затерялось в середине списка.
Алфавитная сортировка полностью снимает этот вопрос. Список можно быстро просканировать, сразу увидеть дубли и держать всё в едином стиле на разных окружениях.
Плюс уменьшаются шумные диффы в код-ревью.
Когда пакеты отсортированы, добавление или удаление одного пункта выглядит как аккуратное и предсказуемое изменение.
Без сортировки маленькое обновление часто превращается в огромный diff, и ревью становится сложнее, чем должно быть.
Этот подход особенно полезен, когда команда растёт.
Несколько человек, трогающих один Dockerfile, не начинают перетасовывать строки под свой вкус или форматировать по-разному.
Структура остаётся стабильной, и поддержка со временем становится проще.
Небольшое улучшение, но на сотнях билдов оно экономит заметно нервов и времени.
👉 Java Portal
12 рекомендаций по дизайну API для бэкенд-разработчиков
(Их часто спрашивают на собеседованиях)
1. Понятные имена ресурсов
❌`GET /get-all-orders`
✅`GET /orders`
POST /users — создать пользователя GET /users/123 — получить пользователя PUT /users/123 — заменить полностью DELETE /users/123 — удалить POST /payments
Idempotency-Key: abc-123
GET /v1/products/42
GET /v2/products/42
❌ `200 OK { "error": "user not found" }`
✅ `404 Not Found`GET /articles?page=2&limit=50
GET /orders?status=shipped&sort=-created_at
Authorization: Bearer <token>
429 Too Many Requests
GET /blog/posts/123
ETag: "abc"
If-None-Match: "abc"
POST /auth/login
POST /sessions
🔍 Завтра тестовое собеседование с Java-разработчиком
19 ноября(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Java-разработчика.
Как это будет:
📂 Сергей Чамкин, старший разработчик из Uzum, ex-WildBerries, будет задавать реальные вопросы и задачи разработчику-добровольцу
📂 Cергей будет комментировать каждый ответ респондента, чтобы дать понять чего от вас ожидает собеседующий на интервью
📂 В конце можно будет задать любой вопрос Сергею
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot
Реклама.
О рекламодателе.
API VERSIONING позволяет разработчикам поддерживать несколько версий эндпоинтов веб-сервиса (например, /users или /products), не ломая интеграции у клиентов при внесении изменений.
В новом Spring Boot 4 появилась встроенная поддержка версионирования API, и настраивается она очень просто.
→ Добавь строки в конфиг (application.properties) под твой сценарий. Можно использовать оба варианта одновременно — заголовки и query-параметры.
1 способ: версионирование через заголовок
(Клиент передает версию в header, например: api-version: 1 — название заголовка можно выбрать любое)
spring.mvc.apiversion.use.header = api-version
spring.mvc.apiversion.use.query-parameter = version
spring.mvc.apiversion.default = 1.0
Многие ненавидят Java не потому что язык плохой, а потому что первое знакомство с ним вышло травматичным.
В универе его чаще всего преподавали на примерах из прошлого века, а преподаватели были больше заняты тем, чтобы сто раз написать public static void main, чем объяснить, почему всё устроено именно так. А ещё эта многословность, ошибки компиляции, необходимость сходу понимать классы, объекты, наследование… неудивительно, что после такой встречи у людей остаётся отвращение.
Плюс порог входа у Java выше, чем у более дружелюбных языков вроде Python или JavaScript. И хотя это уже давно не тот тормозной тяжёлый язык из 2000-х, многие до сих пор думают о нем именно так, не замечая, что сегодня он работает почти во всех крупных системах.
И тут самое забавное: пока в интернете его поливают грязью, компании всё так же активно ищут Java-разработчиков и платят им очень хорошие деньги. Потому что в реальном мире остаётся то, что работает и масштабируется.
👉 Java Portal