Библиотека джависта | Java, Spring, Maven, Hibernate
22.3K subscribers
2.45K photos
54 videos
49 files
3.6K links
Все самое полезное для Java-разработчика в одном канале.

Учиться у нас: clc.to/AATM8w

Для обратной связи: @proglibrary_feeedback_bot

По рекламе: @proglib_adv

РКН: https://gosuslugi.ru/snet/67a5bbda1b17b35b6c1a55c4
Download Telegram
Сохраняйте шпаргалку по Kubernetes

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
4👍4🔥2
🪤 «Потом отрефакторим» = «никогда»

Разраб с 5+ годами опыта собрал 5 вещей, которые он хотел бы услышать в свой первый год, но услышал гораздо позже и гораздо дороже.

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

— тот самый общий модуль, который «у всех меняется одинаково»
— ревью в стиле «так писать нельзя» без единого «почему»
— почему бесполезные тесты хуже, чем их отсутствие

Без занудства и пафоса, на живых рабочих историях (включая ту, где «красивое ТЗ с картинками» оказалось указано не на тот участок проекта).

Хороший повод сверить часы: что из этого вы уже передаёте своим джунам, а что всё ещё откладываете сами.

🔗 Подробнее

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
😁2👍1👾1
👾 Collectors.toMap падает там, где HashMap.put молчит

Привычка к map.put() подводит при переходе на стримы:
Map<String, User> byEmail = users.stream()
.collect(Collectors.toMap(User::email, u -> u));


Здесь сразу две мины:

1. Дубликаты ключей → IllegalStateException. Map.put тихо перезаписал бы значение, а toMap без merge-функции бросает «Duplicate key». Фикс — третий аргумент:
.collect(Collectors.toMap(User::email, u -> u, (a, b) -> b)); // оставить последний


2. null в значении → NullPointerException. Под капотом toMap использует Map.merge, который по контракту не допускает null-значение. HashMap сам по себе null-значения разрешает, поэтому баг ловится не на ровной коллекции, а на «редких» данных в проде:
.collect(Collectors.toMap(User::email, User::nickname)); // nickname == null -> NPE


Если значение может быть null, собирай вручную в HashMap через forEach/reduce или фильтруй заранее. Стоит держать в голове: toMap строже, чем тот Map, который он создаёт.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
6👍4🔥1
⚙️ Миграция на Spring Boot 4 и Java 25 без даунтайма

Boot 4.0 — это не апдейт одного фреймворка, а полтора десятка библиотек разом: Spring Framework 7, Jakarta EE 11, Jackson 3, Security 7, JUnit 6. Радиус поражения шире, чем кажется по номеру версии. А ветка 3.4 уже без security-патчей, поддержка 3.5 заканчивается этим летом.

Tech Lead из FinTech разложил маршрут, проверенный на нагруженных сервисах:

🔹 Один вектор за раз — сначала Boot 4 на старой Java 21, потом отдельно JDK 25. Чтобы регрессия всегда указывала на конкретного виновника.
🔹 Тихие грабли — компилируется, падает в рантайме: переезд пакетов Jackson, новые дефолты CSRF в Security 7, выпиленный Undertow, удалённые @MockBean/@SpyBean.
🔹 Виртуальные потоки — кейс Netflix про дедлок на pinning и почему JEP 491 в Java 25 чинит ровно это: обход через ReentrantLock больше не нужен.

Плюс контрольные точки на каждом шаге, раскат через канарейку и честный раздел «где маршрут не сработает».

Без гонки за версиями, по фактам и по шагам. 👇

🔗 Читать на Хабре

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
7🔥2👍1
🙈 Hibernate выполняет SQL не в том порядке, в котором ты его написал

Код читается сверху вниз, а Hibernate так не работает. При flush'е Hibernate складывает все действия в ActionQueue и выполняет их в строго фиксированном порядке по типу операции, а не по порядку вызова в коде:

1. orphan removal
2. INSERT
3. UPDATE
4. удаление коллекций
5. обновление коллекций
6. вставка коллекций
7. DELETE

Классический пример с unique-ограничением:
repo.delete(oldRow);     // (1) в коде сначала удаляем
repo.save(newRow); // (2) потом вставляем с тем же unique-значением
// commit -> ConstraintViolationException


На flush Hibernate переставит это в INSERT → DELETE. Вставка отработает до удаления, ловит конфликт по уникальному ключу, и падает, хотя в твоём коде последовательность была валидной.

Варианты решения:

— явный flush() между операциями, чтобы зафиксировать порядок;
— deferred (отложенные) constraints на стороне БД;
— пересмотр модели, чтобы коллизии в принципе не возникало.

Главное не считать, что порядок методов = порядок SQL. Это не так (для Hibernate).

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍171🔥1👏1
🪄 Магия Spring Boot, которая тихо убивает прод

Spring Boot прекрасен ровно до того момента, пока «оно само работает» не превращается в «оно само сломалось — и непонятно, где». Собрал фичи, которые на собесе звучат как удобство, а в 3 ночи как инцидент.

1️⃣ @Transactional, который держит коннект дольше, чем надо

Метод помечен @Transactional, а внутри запрос в БД, потом HTTP-вызов во внешний сервис, потом ещё один запрос. Всё это время коннект из пула занят. Даунстрим притормозил на 2 секунды → коннект висит 2 секунды → под нагрузкой пул выгребается, и приложение встаёт колом. CPU не загружен, БД не захлёбывается, а реквесты копятся.

Транзакция должна быть короткой и без сетевых вызовов внутри. Внешние интеграции выносим за её границы.

2️⃣ Self-invocation: аннотация, которая молча не работает

@Transactional, @Async, @Cacheable живут через прокси. Зовёшь аннотированный метод из этого же класса → прокси в обход, аннотация не срабатывает.

Лечится выносом в отдельный бин или self-injection. Но сначала надо просто знать, что так бывает.

3️⃣ Open Session In View — включён по умолчанию

spring.jpa.open-in-view=true из коробки. Звучит безобидно, на деле Hibernate-сессия живёт до рендера ответа: ленивые связи подтягиваются уже в слое контроллера, и ты ловишь N+1 там, где сервис давно отработал. Плюс коннект держится дольше нужного. На дев-стенде незаметно, а под нагрузкой привет latency.

Осознанно ставим false и грузим ровно то, что нужно.

4️⃣ HikariCP с дефолтным пулом

maximum-pool-size=10 по умолчанию. Для лёгкого сервиса — ок. Под реальным трафиком — узкое горлышко, о котором узнаёшь не из кода, а из графика глубины пула под нагрузкой. Тюнить надо не «побольше», а по формуле под ядра БД и характер нагрузки. Просто выкрутить в 200 — это обменять одну проблему на дедлоки в базе.

5️⃣ Actuator, открытый наружу

/actuator/env, /configprops, /heapdump — удобно для дебага и шикарно для того, кто найдёт их в проде: креды, конфиги, дампы памяти. Многое прикрыто по умолчанию, но «давайте включим всё, чтобы видеть метрики» и приехали. Открываем точечно, прячем за security, health/metrics держим отдельно от чувствительного.

6️⃣ Ретраи без circuit breaker = retry storm

Даунстрим тормозит → добавляешь 3 ретрая с бэкоффом. Но он тормозит, потому что перегружен. Твой ретрай шлёт второй запрос, третий. Помножь на всех клиентов. За полторы минуты лёгкое замедление превращается в каскадный отказ всей системы.

Ретраи — только с circuit breaker, jitter и лимитом. Иначе ты не лечишь нагрузку, а добиваешь.

🔹 Магия Spring Boot не баг, а разумные дефолты для быстрого старта. Беда в том, что дефолты «чтобы запуститься» и дефолты «чтобы держать прод» — это разные дефолты. Сеньорность тут не в том, чтобы знать всё наизусть, а в привычке спрашивать: «а что эта аннотация делает под капотом и под нагрузкой?»

💬 Какая фича Spring Boot подставила вас в проде в последний раз?

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍132🔥2😁2🤔1
ℹ️ Liveness vs Readiness vs Startup: где чаще всего путаются

Три пробы в Kubernetes решают разные задачи, но иногда бывает путаница между ними. Давайте разберемся.

🔹 Liveness — «приложение живое или его пора убить». Зафейлился → под перезапускается. Подходит для дедлоков, зависших потоков, состояний, из которых само не выйдет.

🔹 Readiness — «готово ли принимать трафик прямо сейчас». Зафейлился → под не убивается, просто выпиливается из Service. Вернулся в норму и трафик снова идёт. Это про временную недоступность: упала БД, прогревается кэш, лёг downstream.

🔹 Startup — «приложение ещё стартует». Пока активен startup probe, liveness и readiness отключены. Без него Kubernetes может прибить Spring Boot приложение, которое просто долго поднимается (привет, миграции и прогрев контекста).

Ключевая ошибка: перепутать liveness с readiness. Зафейленный readiness говорит «я живой, но занят не шли мне трафик». Зафейленный liveness — «я сломан, убей и подними заново». Перепутаешь — будешь рестартовать здоровые поды или слать трафик в мёртвые.

В Spring Boot это маппится через health groups:
management:
endpoint:
health:
probes:
enabled: true
group:
liveness:
include: livenessState
readiness:
include: readinessState, db, redis


Если readiness включает проверку БД, под автоматически выпадет из балансировки при обрыве соединения, без единой строчки кастомного кода.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥1👏1
⚠️ Self-invocation: почему аннотации иногда просто не срабатывают

@Transactional, @Async, @Cacheable, @Retryable работают через прокси (JDK dynamic proxy на интерфейсах, CGLIB на классах). Аннотация срабатывает, только когда вызов проходит через прокси, то есть приходит снаружи бина.

Вызов соседнего метода через this идёт мимо прокси и аннотация молча игнорируется:
@Service
public class OrderService {

public void handle() {
save(); // this.save() — мимо прокси
}

@Transactional
public void save() { // транзакции здесь НЕ будет
...
}
}


Сюда же: @Transactional на private, final и static методах не работает — CGLIB не может их переопределить.

🔹 Лечится так:

→ вынести метод в отдельный бин (самый чистый вариант);
→ self-injection (инъекция самого себя и вызов через него);
→ ((OrderService) AopContext.currentProxy()).save() (требует exposeProxy = true);
→ TransactionTemplate для программного управления.

Симптом всегда один «аннотация висит, а эффекта нет». Первым делом проверяй, не self-invocation ли это.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍53🔥1
📊 UUID vs автоинкремент в качестве первичного ключа

Спор древний, но решается не вкусовщиной, а тем, кто и когда генерирует ключ.

🔹 Автоинкремент хорош, когда:

— Ключ нужен только внутри одной БД
— Важна плотная индексация и предсказуемый порядок вставки (B-tree счастлив)
— Не страшно, что /users/42 выдаёт размер таблицы наружу

🔹 UUID (а лучше UUIDv7) уместен, когда:

— ID нужно сгенерировать на клиенте/в другом сервисе до записи в БД
— Идёт шардирование или слияние данных из нескольких источников
— Последовательный ID — это утечка бизнес-метрики

⚠️ Частая ловушка

Случайный UUIDv4 как кластерный ключ в InnoDB. Вставки идут вразнобой по индексу → фрагментация страниц, распухание, просадка на записи. Именно поэтому появился UUIDv7, он временно-упорядоченный, ложится в индекс почти как автоинкремент, но остаётся глобально уникальным.

Практичный компромисс: автоинкремент как внутренний PK + UUIDv7 как публичный идентификатор в URL и API. Внутри — быстро, наружу — безопасно.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥3👏1🤔1💯1
⚙️ В чем разница между балансировщиками нагрузки, обратными прокси и API-шлюзами?

1️⃣ Балансировщик нагрузки

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

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

Это увеличивает пропускную способность, снижает задержки и оптимизирует использование ресурсов.

2️⃣ Обратные прокси

Работают как посредники между клиентами и серверами, обрабатывая запросы и передавая данные, скрывая серверы и повышая их безопасность.

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

Дополнительно, они могут кэшировать контент для уменьшения нагрузки на сервер, сжимать данные для ускорения передачи и управлять SSL/TLS-шифрованием, разгружая веб-серверы.

3️⃣ API-шлюзы

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

Они упрощают взаимодействие клиентов с разными сервисами, добавляют защиту, применяют правила, переводят между веб-протоколами и агрегируют данные.

Идеально подходят для работы с микросервисной архитектурой.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍64🔥3👏1
🌐 Зачем нужен CDN и как он работает

Когда пользователь из Сиднея открывает ваш сайт, его запрос летит на сервер в Германии и обратно. Это ~300 мс только на «дорогу» — и это для каждого изображения, шрифта, JS-файла.

CDN (Content Delivery Network / Сеть доставки контента) решает эту проблему в лоб: вместо одного сервера — тысячи точек по всему миру. Пользователь получает файлы с ближайшей к нему ноды, а не с вашего origin-сервера.

🔹 Origin и Edge

В архитектуре с CDN появляются два новых понятия:

Origin Server (Главный сервер): это ваш настоящий сервер, где крутится Spring Boot, лежит база данных и хранятся оригиналы всех файлов (например, в Amazon S3).

Edge Servers (Граничные серверы / Точки присутствия - PoP): это тысячи серверов CDN-провайдера (например, Cloudflare или Akamai), раскиданные по всему земному шару: в Сиднее, Токио, Нью-Йорке, Лондоне, Москве.

🔹 Как работает кэш

Первый запрос к конкретному файлу всё равно идёт до origin. CDN его скачивает, кэширует у себя и отдаёт пользователю. Все последующие запросы на тот же файл edge-сервер обрабатывает сам, не трогая ваш бэкенд. Это называется cache hit.

🔹 Что кладем в CDN

CDN идеально подходит для статического контента:

— Картинки, видео, аудио.
— Скомпилированные файлы JavaScript и CSS.
— Шрифты.

CDN не подходит для динамического контента:

— Корзина товаров.
— Приватные данные пользователя.
(Запросы к API /api/v1/users/me должны идти напрямую на ваш сервер, минуя кэш CDN).

🔹 Защита от DDoS

Современные CDN (тот же Cloudflare) - это не просто кэш. Это гигантский щит.

Если хакеры решат «положить» ваш сайт и отправят миллион запросов в секунду, этот удар примут на себя серверы CDN. Их пропускная способность измеряется терабитами. Они отфильтруют «мусорный» трафик, и ваш маленький Origin-сервер даже не заметит атаки.

🔹 Побочные эффекты

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

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

Безопасность: CDN скрывает реальный IP-адрес вашего сервера и защищает от DDoS-атак.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍92👏1