Библиотека джависта | Java, Spring, Maven, Hibernate – Telegram
Библиотека джависта | Java, Spring, Maven, Hibernate
22.8K subscribers
2.32K photos
51 videos
47 files
3.34K links
Все самое полезное для Java-разработчика в одном канале.

Список наших каналов: https://news.1rj.ru/str/proglibrary/9197

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

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

РКН: https://gosuslugi.ru/snet/67a5bbda1b17b35b6c1a55c4
Download Telegram
SQL Window Functions.pdf
129.5 KB
Сохраняйте шпаргалку по оконным функциям sql

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

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

#Enterprise
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥2😁1
⚡️ Как ускорить тесты в 6 раз

Знакомая история: пушишь, идёшь за кофе, возвращаешься, а CI ещё думает.

Автор разобрал реальный backend-монолит и сократил время прогона тестов в 6 раз. Только диагностика и последовательные шаги.

Что внутри:


— Почему timeoutInSeconds = 10 — это мина замедленного действия
— HikariCP exhaustion при параллельных suite'ах — и как считать нужный pool size
— Shared Testcontainer через lazy val в object: потокобезопасно и без костылей
— Кастомный Reporter, который не даёт тестам деградировать снова

Стек Scala/SBT, но всё применимо к любому JVM-проекту.

👉 Подробнее в статье

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

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

#Enterprise
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥32👍2
😮 Топ-вакансий для джавистов за неделю

Java Developer — удалёнка

Java-разработчик — от 220 000 ₽ — удалёнка

Senior Java Engineer (JavaSE, algorithms, optimization) — от 5 000 $ — удалёнка

➡️ Еще больше топовых вакансий — в нашем канале Java jobs
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥21👍1
🔧 Spring Cache + Redis: настройка которая не сломается в production

@Cacheable выглядит просто. До первого падения сериализации в production или гонки при cache stampede.

🔹 Решение

▪️ Конфигурация RedisCacheManager

@Configuration
@EnableCaching
public class CacheConfig {

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration defaults = RedisCacheConfiguration
.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new StringRedisSerializer()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues(); // null не кешируем

Map<String, RedisCacheConfiguration> configs = Map.of(
"users", defaults.entryTtl(Duration.ofHours(1)),
"products", defaults.entryTtl(Duration.ofMinutes(5)),
"sessions", defaults.entryTtl(Duration.ofDays(1))
);

return RedisCacheManager.builder(factory)
.cacheDefaults(defaults)
.withInitialCacheConfigurations(configs)
.build();
}
}


▪️ Использование с явным ключом

@Cacheable(value = "users", key = "#userId", unless = "#result == null")
public User findById(Long userId) { ... }

@CacheEvict(value = "users", key = "#user.id")
public User update(User user) { ... }

// Evict всего кеша при массовых операциях
@CacheEvict(value = "users", allEntries = true)
public void importUsers(List<User> users) { ... }


▪️ Защита от cache stampede через @CachePut + Lock

@Service
public class ProductService {

private final Map<Long, Object> locks = new ConcurrentHashMap<>();

public Product getProduct(Long id) {
Product cached = cacheManager.getCache("products").get(id, Product.class);
if (cached != null) return cached;

// только один поток пересчитывает, остальные ждут
Object lock = locks.computeIfAbsent(id, k -> new Object());
synchronized (lock) {
// double-check после захвата лока
cached = cacheManager.getCache("products").get(id, Product.class);
if (cached != null) return cached;

Product product = repository.findById(id).orElseThrow();
cacheManager.getCache("products").put(id, product);
return product;
}
}
}


Сериализация через GenericJackson2JsonRedisSerializer сохраняет информацию о типе в JSON (@class поле). Это позволяет десериализовать полиморфные структуры, но требует чтобы все классы в графе объекта были сериализуемы Jackson'ом. Проблема обычно вылезает при кешировании Hibernate entity с lazy коллекциями — они не сериализуются и роняют приложение.

⚠️ Никогда не кешируйте Hibernate entity напрямую. Создавайте DTO, сериализуйте его.

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

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

#Enterprise
Please open Telegram to view this post
VIEW IN TELEGRAM
👍61🔥1😁1
This media is not supported in your browser
VIEW IN TELEGRAM
👍 На курсе по контролируемой разработке AI-агентов мы будем разбирать ровно то, о чём говорит Владислав в голосовом, но уже в формате системной практики.

📅 Старт курса — 20 апреля.

Если хотите разобраться, как строить управляемые агентные системы:
➡️ Присоединяйтесь.

P.S. С первого занятия будет практика: код и разбор реальных ошибок, а не только теория.
Please open Telegram to view this post
VIEW IN TELEGRAM
🕯 Memory Ordering и happens-before

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

🔹 Проблема без volatile


Современные процессоры и компиляторы переупорядочивают инструкции для оптимизации. Каждое ядро имеет store buffer — запись в память не мгновенна, сначала попадает в буфер. Другое ядро может не увидеть запись ещё долгое время.

// Поток 1
data = 42;
ready = true;

// Поток 2
if (ready) {
System.out.println(data); // может напечатать 0
}


Без барьеров компилятор или процессор может переставить data = 42 и ready = true. Или поток 2 увидит ready = true из кеша раньше чем data = 42 из store buffer дойдёт до памяти.

🔹 Что на самом деле гарантирует volatile

Запись в volatile переменную

Сбрасывает store buffer. Все предыдущие записи этого потока становятся видимы другим потокам до того как запись в volatile будет видна.

Чтение volatile переменной

Инвалидирует локальный кеш. Поток видит актуальные значения всех записей, которые произошли до соответствующей volatile-записи в другом потоке.

Это и есть happens-before: если поток A записал в volatile переменную, а поток B прочитал это значение — всё что A делал до записи, видимо B после чтения.

volatile boolean ready;
int data; // не volatile!

// Поток 1
data = 42; // HB-предшествует записи в ready
ready = true; // volatile write

// Поток 2
if (ready) { // volatile read
// data гарантированно == 42
System.out.println(data);
}


data не volatile, но гарантия работает через happens-before цепочку.

🔹 Что volatile не гарантирует

Атомарность составных операций:

volatile long counter;
counter++; // не атомарно: read → increment → write


На 32-битных JVM даже чтение/запись long без volatile не атомарна (два 32-битных слова). volatile long делает операцию атомарной, но counter++ всё равно не атомарна как составная операция.

🔹 Модель памяти Java (JMM) и happens-before

JMM определяет happens-before не только для volatile. Полный список отношений:

— Запись в поле до разблокировки монитора HB разблокировке.
— Разблокировка монитора HB последующей блокировке того же монитора.
— Запись в volatile HB последующему чтению той же переменной.
— Завершение Thread.start() HB любому действию в запущенном потоке.
— Любое действие в потоке HB Thread.join() на этом потоке.

Эти правила транзитивны. Именно на этом строятся корректные публикации объектов: final поля объекта видны всем потокам без дополнительной синхронизации после завершения конструктора, потому что завершение конструктора HB любому доступу к объекту через корректно опубликованную ссылку.

🔹 StampedLock и оптимистичное чтение

StampedLock (Java 8+) предлагает три режима: запись, пессимистичное чтение, оптимистичное чтение. Оптимистичное чтение не берёт блокировку вообще — читает данные и потом валидирует что запись не произошла:

long stamp = lock.tryOptimisticRead();
int x = point.x;
int y = point.y;
if (!lock.validate(stamp)) {
stamp = lock.readLock();
try {
x = point.x;
y = point.y;
} finally {
lock.unlockRead(stamp);
}
}


validate — это volatile-read под капотом, устанавливающий happens-before с последней записью. Паттерн работает корректно именно из-за JMM семантики, а не "просто так".

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

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍93🔥3
💡 Collections.singletonList вместо new ArrayList<>()

Когда список точно из одного элемента — не создавай изменяемый:
//  Избыточно
List<String> roles = new ArrayList<>();
roles.add("ADMIN");
someMethod(roles);

// Лаконично и без лишней аллокации
someMethod(Collections.singletonList("ADMIN"));


singletonList возвращает неизменяемую обёртку вокруг одного объекта без внутреннего массива. Это дешевле по памяти и явно выражает намерение.

⚠️ Список иммутабельный, add() бросит UnsupportedOperationException.

✔️ Альтернатива в Java 9+: List.of("ADMIN") — то же самое, но более современный API.

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

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
8👍3🔥2
✔️ Java-тест: CompletableFuture + ThreadLocal

Классическая ловушка в многопоточке👇

📦 Задание


Написали сервис для аудит-логирования действий пользователей. В проде периодически в лог пишется чужой userId — данные одного юзера попадают в запись другого. Найдите баг и исправьте:

@Component
public class UserContext {
private static final ThreadLocal<String> currentUserId = new ThreadLocal<>();

public static void set(String userId) { currentUserId.set(userId); }
public static String get() { return currentUserId.get(); }
public static void clear() { currentUserId.remove(); }
}

@Service
@RequiredArgsConstructor
public class OrderService {
private final AuditLogger auditLogger;

public CompletableFuture<Order> createOrder(String userId, OrderDto dto) {
UserContext.set(userId);

return CompletableFuture.supplyAsync(() -> {
Order order = buildOrder(dto);
auditLogger.log("Order created by: " + UserContext.get());
return order;
});
}
}


🔹 Задачи

— Объяснить, почему UserContext.get() внутри supplyAsync может вернуть чужой userId или null
— Исправить так, чтобы контекст корректно передавался в асинхронный поток
— Бонус: объяснить, почему ThreadLocal вообще опасен с пулами потоков типа ForkJoinPool

Ставьте → 🔥, если нравится формат. Если нет → 🌚

💬 Решения под спойлер. Сравним, какое будет лучше.

🐸 Библиотека собеса по Java

#practise
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥17👍53
🔍 Чтение csv файла

Простая команда на случай, когда надо быстро и в удобном формате прочитать CSV-файл в терминале:
$ cat inventory.csv | column -t -s,


Флаг -s указывает на использование запятых в качестве разделителей, а -t форматирует выходные данные в чистую таблицу.

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

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
4👍4🔥3
💥 Открытый вебинар | ИИ-агенты в продакшене: от хайпа к деньгам

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

Полина Полунина, руководитель AI-направления Альфа-Банка, расскажет честно:

▪️ Чем агент отличается от «просто GPT с промптом» и когда бизнесу достаточно обычного LLM
▪️ 3 реальных кейса из корпоративной среды: что взлетело, а что нет
▪️ Live-демо работающего агента
▪️ ТОП-5 граблей, на которые наступают команды при внедрении

⏱️ 10 марта в 19:00 (МСК)

🎁 Участники получат промокод на скидку на самый полный курс по ИИ-агентам

👉 Регистрируйся
🌸 Поздравляем с 8 марта

Дорогие девушки, кто выбрал этот безумный и прекрасный путь в IT. Спасибо, что вы есть в нашей профессии.

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

С праздником, коллеги 💐

🐸 Библиотека джависта
Please open Telegram to view this post
VIEW IN TELEGRAM
14👍3🔥2👾1
👑 Магия IntelliJ IDEA

Если используешь Ctrl + P (подсказка параметров метода), то вот ещё один полезный хот кей: Shift + Ctrl + I → быстрый просмотр определения.

🔹 Зачем это нужно

— Позволяет посмотреть реализацию метода/класса/интерфейса без перехода в другой файл.
— Работает с любыми символами: методами, переменными, константами, даже SQL-мэпперами в MyBatis.
— Незаменимо, если не хочешь терять контекст текущего кода.

🔹 Как использовать


— Наведи курсор на метод, поле или класс, нажми Ctrl + Shift + I — появится всплывающее окно с реализацией.
— Работает и в дебаге, и при просмотре внешних библиотек (если есть исходники).

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

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥63
💭 Apache Kafka: как не терять данные под нагрузкой

Классическая проблема растущего проекта: сервис уведомлений начинает захлёбываться. В пике прилетает 10к запросов в секунду, а он обрабатывает 3к. Остальные просто теряются.

Можно горизонтально масштабировать, но это не решает проблему архитектурно. А Kafka решает.

Идея простая: Producer пишет, Consumer читает в своём темпе. Никто никого не ждёт. Никто никого не роняет.

1️⃣ Producer: отправляем событие

@Service
@RequiredArgsConstructor
public class OrderService {

private final KafkaTemplate<String, OrderEvent> kafkaTemplate;

public void createOrder(Order order) {
orderRepository.save(order);

OrderEvent event = new OrderEvent(order.getId(), order.getUserId());
kafkaTemplate.send("order-created", order.getUserId().toString(), event);
// топик ключ партиции payload
}
}


Ключ партиции — важная деталь. Kafka гарантирует порядок сообщений внутри одной партиции. Если передаёшь userId как ключ — все события одного пользователя попадут в одну партицию и будут обработаны строго по порядку.

2️⃣ Consumer: читаем и обрабатываем

@Component
public class NotificationConsumer {

@KafkaListener(
topics = "order-created",
groupId = "notification-group",
concurrency = "3" // 3 потока = читаем 3 партиции параллельно
)
public void handle(OrderEvent event) {
notificationService.send(event.getUserId());
}
}


groupId определяет логическую группу потребителей. Kafka гарантирует: одно сообщение получит ровно один инстанс внутри группы. Хочешь, чтобы событие получили оба сервиса уведомлений и аналитики? Разные groupId и каждый читает топик независимо.

3️⃣ Партиции и масштабирование

order-created (3 партиции)
├── partition-0 → consumer-instance-1
├── partition-1 → consumer-instance-2
└── partition-2 → consumer-instance-3


Не хватает скорости обработки → поднимаешь ещё инстансов. Kafka сама перераспределит партиции. Но инстансов больше, чем партиций держать смысла нет, лишние будут просто простаивать.

4️⃣ Что делать, если Consumer упал

Kafka хранит сообщения на диске (по умолчанию 7 дней). Consumer сам трекает, до какого offset он дочитал.

spring:
kafka:
consumer:
auto-offset-reset: earliest # читать с начала, если offset не найден
enable-auto-commit: false # коммитим offset вручную — только после успешной обработки


enable-auto-commit: false — критически важная настройка. Если Consumer упал в середине обработки, он перечитает сообщения с последнего закоммиченного offset. При true — offset уже сдвинулся, сообщение потеряно.

5️⃣ Dead Letter Topic: что делать с ядовитыми сообщениями

Иногда одно сообщение падает раз за разом: битые данные, баг в логике. Consumer уходит в бесконечный retry и встаёт колом.

@Bean
public DefaultErrorHandler errorHandler(KafkaTemplate<String, Object> kafkaTemplate) {
var recoverer = new DeadLetterPublishingRecoverer(kafkaTemplate);
var backoff = new FixedBackOff(1000L, 3); // 3 попытки с паузой 1с
return new DefaultErrorHandler(recoverer, backoff);
}


После 3 неудачных попыток сообщение уедет в топик order-created.DLT. Основной поток не заблокирован, разбираешься с проблемой отдельно.

📌 Когда Kafka, а когда нет

Нужен ответ прямо сейчас → REST
Получатель может быть недоступен → Kafka
Один источник и много потребителей → Kafka
Аудит и история событий → Kafka
Простой CRUD без нагрузки → REST

Kafka не серебряная пуля. Она добавляет операционную сложность: нужно думать об idempotency, порядке сообщений, мониторинге lag у Consumer-групп. Но когда система начинает терять данные под нагрузкой, цена этой сложности оправдана.

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

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6👍51
🌐 Как настроить Spring Cloud Gateway

Spring Cloud Gateway — это API Gateway поверх Project Reactor. Маршрутизация запросов, балансировка нагрузки, rate limiting, аутентификация.

Реактивный, non-blocking, легко расширяемый через фильтры.

1️⃣ Добавляем зависимости

Добавьте spring-cloud-starter-gateway — это вытащит Reactor Netty как сервер. Важно: НЕ добавляйте spring-boot-starter-web, они несовместимы. Gateway работает на WebFlux стеке.

Для service discovery добавьте spring-cloud-starter-netflix-eureka-client или spring-cloud-starter-consul-discovery.

2️⃣ Настраиваем маршруты через application.yml

spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://USER-SERVICE
predicates:
- Path=/api/users/**
filters:
- StripPrefix=1


lb:// — это magic prefix для балансировки через service discovery. StripPrefix=1 срезает /api перед проксированием запроса вниз.

3️⃣ Пишем кастомный GlobalFilter

@Component
public class AuthFilter implements GlobalFilter, Ordered {

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest()
.getHeaders()
.getFirst(HttpHeaders.AUTHORIZATION);

if (token == null || !isValid(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}

@Override
public int getOrder() { return -1; } // выполняется первым
}


getOrder() с отрицательным значением — фильтр выполнится до встроенных. Возвращайте chain.filter(exchange) чтобы пропустить запрос дальше.

4️⃣ Rate Limiting через Redis

filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
key-resolver: "#{@userKeyResolver}"

@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
);
}


replenishRate — токенов в секунду, burstCapacity — максимальный burst. Требует spring-boot-starter-data-redis-reactive.

5️⃣ Circuit Breaker с Resilience4j

filters:
- name: CircuitBreaker
args:
name: userServiceCB
fallbackUri: forward:/fallback/users

@RestController
public class FallbackController {
@GetMapping("/fallback/users")
public Mono<String> usersFallback() {
return Mono.just("Users service unavailable");
}
}


Circuit Breaker открывается при превышении порога ошибок и перенаправляет на fallback endpoint вместо каскадного падения.

6️⃣ Настраиваем CORS централизованно

@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("*");
config.addAllowedMethod("*");
config.addAllowedHeader("*");

UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}


Настраивайте CORS на уровне Gateway — не нужно дублировать это в каждом микросервисе.

✔️ Что происходит под капотом

Запрос приходит → RoutePredicateHandlerMapping матчит маршрут по predicates → цепочка GatewayFilter обрабатывает запрос → NettyRoutingFilter проксирует вниз → цепочка фильтров обрабатывает ответ в обратном порядке.

Всё это non-blocking на Project Reactor. Один поток может обрабатывать тысячи одновременных соединений.

💡 Бонус-совет

Actuator endpoint /actuator/gateway/routes покажет все зарегистрированные маршруты в runtime — удобно для дебага. Добавьте management.endpoint.gateway.enabled=true в конфиг и можно динамически обновлять маршруты через /actuator/gateway/refresh без перезапуска.

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

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

#Enterprise
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🔥21🎉1