Библиотека джависта | Java, Spring, Maven, Hibernate
22.6K subscribers
2.35K photos
52 videos
47 files
3.41K links
Все самое полезное для Java-разработчика в одном канале.

Список наших каналов: https://t.me/proglibrary/9197

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

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

РКН: https://gosuslugi.ru/snet/67a5bbda1b17b35b6c1a55c4
Download Telegram
🖥 Как LeetCode только проще

Сайт с интерактивными задачами → CodingBat Java

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

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

#Enterprise
Please open Telegram to view this post
VIEW IN TELEGRAM
😁9👍6🔥2
🔧 Caffeine: локальный кеш когда Redis избыточен

Для кеширования результатов тяжёлых вычислений или редко меняющихся справочников Redis часто избыточен. Caffeine — высокопроизводительный in-process кеш с Window TinyLFU eviction policy.

🔹 Решение

▪️ Зависимость

<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>


▪️ Конфигурация через Spring Cache

spring:
cache:
type: caffeine
caffeine:
spec: maximumSize=1000,expireAfterWrite=10m
cache:
cache-names:
- currencies
- countryDictionary
- featureFlags


▪️ Программная конфигурация с разными политиками

@Configuration
@EnableCaching
public class CaffeineConfig {

@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCacheLoader(caffeineCacheLoader());

// разные настройки для разных кешей
manager.registerCustomCache("currencies",
buildCache(500, Duration.ofHours(1)));

manager.registerCustomCache("featureFlags",
buildCache(100, Duration.ofMinutes(5)));

// дефолт для остальных
manager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.recordStats()); // метрики

return manager;
}

private Cache<Object, Object> buildCache(int size, Duration ttl) {
return Caffeine.newBuilder()
.maximumSize(size)
.expireAfterWrite(ttl)
.refreshAfterWrite(ttl.dividedBy(2)) // фоновое обновление
.recordStats()
.build();
}
}


▪️ Метрики через Micrometer

@Bean
public CacheMetricsRegistrar cacheMetricsRegistrar(MeterRegistry registry,
CacheManager cacheManager) {
return new CacheMetricsRegistrar(registry, List.of(cacheManager));
}


Даёт метрики: cache.gets, cache.puts, cache.evictions, cache.hits, hit rate.

refreshAfterWrite — ключевая настройка для production. В отличие от expireAfterWrite, он не удаляет запись по истечении TTL, а помечает её для фонового обновления. Следующий запрос получит устаревшее значение мгновенно, пока фоновый поток обновляет кеш. Латентность не растёт при рефреше.

⚠️ Caffeine — in-process кеш. При горизонтальном масштабировании каждый инстанс имеет свой кеш. Инвалидация при изменении данных требует либо короткого TTL, либо внешнего механизма (Redis pub/sub, Kafka event). Для справочников с редкими изменениями короткого TTL обычно достаточно.

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

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

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

Middle Java/Kotlin Developer — от 150 000 до 250 000 ₽ — офис (Екатеринбург)

Senior Kotlin/Java Developer (Knowledge Hub Team) — до 400 000 ₽ — удалёнка

Senior Java developer — до 6 000 $ — удалёнка

➡️ Еще больше топовых вакансий — в нашем канале Java jobs
Please open Telegram to view this post
VIEW IN TELEGRAM
👍31🔥1
За год мы провели три потока курса по ИИ-агентам, а теперь запускаем масштабное обновление!

В новом, четвёртом потоке мы учли все пожелания студентов, добавили большой блок про AgentOps и сместили фокус с базовых концепций на суровый инжиниринг. Прикрутить API к Java-бэкенду легко, а вот сделать систему масштабируемой, со строгим контролем затрат и интеграцией в Enterprise без галлюцинаций — задача со звёздочкой.

В программе:

— стабильные интеграции по протоколу MCP и мультиагентные паттерны;
— оркестрация в LangGraph: human-in-the-loop и runbook для диагностики;
— продвинутый RAG для промышленной эксплуатации;
— контроль экономики агентов: детальное управление ресурсами и маршрутизация;
— развёртывание опенсорс-моделей в закрытых контурах по 152-ФЗ.

В честь старта продаж действует спецпредложение: 3 курса по цене 1 (два дополнительных курса в подарок).

Доступ к материалам для предварительной подготовки откроется сразу после оплаты.

По промокоду Agent забирайте скидку 10 000 ₽ (89 000 ₽ вместо 99 000 ₽). Успейте занять место до 28 февраля!

👉 Присоединиться к четвёртому потоку и внедрить агентов в Enterprise
⚠️ Одна строка кода заблокировала 102 потока в проде

Вводные: сервис на 800 RPS, SLA соблюдается, никаких предупреждений нет. Но в это время 102 потока молча стоят в очереди к одному локу.

Причина – вызов DatatypeFactory.newInstance(). На первый взгляд безобидно, но внутри он запускает ServiceLoader, который обращается к URLClassPath.getLoader(), а этот метод синхронизирован. Каждый раз при вызове, с каждым запросом. Миллионы раз в час.

Решение – всего одна строка статической инициализации final.

Как это было обнаружено, почему ClassLoader работает именно так, и как кэш на Caffeine усугубил проблему, можно прочитать в статье на Хабре.

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

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥1👏1
✏️ Spring Proxying и self-invocation

Spring AOP использует прокси. То есть вместо настоящего бина в контейнер помещается его обёртка. Эта обёртка перехватывает вызовы и применяет к ним различные действия, такие как логирование, управление транзакциями, проверка безопасности или другие, заданные пользователем.

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

🔵 JDK Dynamic Proxy vs CGLIB

JDK proxy работает только с интерфейсами. Он создаёт объект, который реализует тот же интерфейс, и все вызовы идут через специальный обработчик (InvocationHandler). Внутри есть оригинальный объект, но снаружи виден только прокси.

CGLIB работает по-другому: он создаёт подкласс вашего класса прямо во время работы программы и переопределяет методы, вставляя в них перехват. Поэтому CGLIB не может работать с final-методами и классами, т.к. подкласс не может переопределить final-метод.

Начиная с Spring Boot 2.0, CGLIB используется по умолчанию даже для классов с интерфейсами (spring.aop.proxy-target-class=true). Раньше, если был интерфейс, использовался JDK proxy.

🔵 Self-invocation

Тут всё становится не так очевидно. Разберём такой код:

@Service
public class OrderService {

public void placeOrder() {
// какая-то логика
sendNotification();
}

@Async
public void sendNotification() {
// должно выполняться асинхронно
}
}


sendNotification() помечен аннотацией @Async, но при вызове из placeOrder() он будет выполняться синхронно. Потому что placeOrder() вызывает this.sendNotification(), то есть напрямую метод оригинального объекта, минуя прокси. Перехватывать нечего, аспект не срабатывает.

То же самое с @Transactional, @Cacheable, любым AOP-аспектом.

🔵 Как это исправить

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

2. Можно получить прокси самого себя через контекст:

@Service
public class OrderService {

@Autowired
private ApplicationContext context;

public void placeOrder() {
context.getBean(OrderService.class).sendNotification();
}

@Async
public void sendNotification() { ... }
}


3. Использовать AopContext.currentProxy():
((OrderService) AopContext.currentProxy()).sendNotification();


Для этого потребуется @EnableAspectJAutoProxy(exposeProxy = true). Выглядит не очень красиво, но иногда оправдано, если рефакторинг слишком сложен.

4. Перейти от Spring AOP к AspectJ с compile-time или load-time weaving. Тогда аспекты вшиваются прямо в байткод, прокси не нужен, и self-invocation работает как надо. Но это уже совсем другая история, требующая более сложной настройки.

🔵 Почему это важно

Ошибки, связанные с self-invocation, не всегда легко воспроизвести. В тестах, где бин заменяется моком, поведение может быть другим. А в интеграционных тестах с поднятым контекстом – проявляются.

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

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

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍156🔥3
☕️ Java-хак: не используй Optional.get() напрямую

Классическая ошибка, которую порой делают даже опытные разработчики:
Optional<User> user = userRepository.findById(id);
return user.get(); // 💀 NoSuchElementException если пусто


Правильно — используй альтернативы:
// Дефолтное значение
user.orElse(User.guest())

// Ленивое создание дефолта (если создание дорогое)
user.orElseGet(() -> userService.createDefault())

// Своё исключение с контекстом
user.orElseThrow(() -> new UserNotFoundException(id))

// Выполнить действие только если есть значение
user.ifPresent(u -> eventBus.publish(new UserLoadedEvent(u)))


Optional.get() — это тот же NPE, только под другим соусом. Optional нужен именно для того, чтобы явно обработать отсутствие значения.

Если пишешь get(), спроси себя: а зачем тут вообще Optional?

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

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍122🔥1
Последний шанс: 3 курса по цене 1 и запуск AI-агентов в продакшн

Прикручивать LLM-запросы к Spring-приложениям — весело, но кровавому энтерпрайзу нужна предсказуемость. Как маршрутизировать мультиагентные системы под большими нагрузками и строго соблюдать 152-ФЗ?

В обновлённой программе фокус смещён на жёсткий инжиниринг и вывод в прод. Вы изучите архитектуру ReAct-циклов, паттерны оркестрации агентов через LangGraph, продвинутый RAG, интеграции по стандарту MCP и AgentOps. Все ключевые навыки в одном месте: структурная генерация, time-travel дебаггинг, изоляция выполнения, human-in-the-loop и развёртывание в изолированных контурах.

Почему нельзя откладывать:
— масштабная акция «3 курса по цене 1» сгорит уже завтра;
— промокод Agent на скидку 10 000 рублей действует последние часы;
— сразу после оформления открываются материалы для подготовки — начать учиться можно прямо сейчас.

Забронировать место на курсе и забрать бонусы до 28 февраля
👾 JOOQ: Основы

Устал от того, что Hibernate «умнее» тебя и генерирует запросы, которые ты не просил? Тогда стоит взглянуть на JOOQ.

🔹 Что это такое

JOOQ (Java Object Oriented Querying) — это библиотека для построения типобезопасных SQL-запросов прямо в Java-коде. В отличие от JPA/Hibernate, здесь SQL — это основа, а не просто деталь. Пиши SQL запросы, но с поддержкой компилятора.

🔹 Проблемы, которую решает JOOQ

При работе с Hibernate часто возникают такие проблемы:

* N+1 проблема, которую сложно отловить.
* HQL/JPQL, которые не поддерживают все возможности SQL.
* Магия FetchType.LAZY, которая может неприятно удивить в production.
* Потеря контроля над тем, какие запросы выполняются на самом деле.

JOOQ возвращает контроль над SQL, но при этом не заставляет писать сырые строки.

🔹 Как это выглядит на практике

// Hibernate (непонятно, что выполнится)
List<order> orders = em.createQuery(
SELECT o FROM Order o WHERE o.status = :status, Order.class)
.setParameter(status, ACTIVE)
.getResultList();

// JOOQ (читаемо, безопасно, предсказуемо)
Result<record> result = dsl
.select(ORDER.ID, ORDER.STATUS, CUSTOMER.NAME)
.from(ORDER)
.join(CUSTOMER).on(ORDER.CUSTOMER_ID.eq(CUSTOMER.ID))
.where(ORDER.STATUS.</record>eq(ACTIVE))
.orderBy(ORDER.CREATED_AT.desc())
.limit(50)
.fetch();


Классы ORDER и CUSTOMER генерируются на основе вашей схемы БД. Если вы переименуете столбец в базе, но забудете обновить код, проект просто не скомпилируется. В этом и заключается главная сила JOOQ.

🔹 Кодогенерация – основа JOOQ

JOOQ читает схему вашей БД (через JDBC) и создает Java-классы для каждой таблицы, столбца, sequence и enum. Это настраивается через плагин Maven/Gradle:
<plugin>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen-maven</artifactId>
<configuration>
<jdbc>
<url>jdbc:postgresql://localhost/mydb</url>
</jdbc>
<generator>
<database>
<inputSchema>public</inputSchema>
</database>
<target>
<packageName>com.example.generated</packageName>
</target>
</generator>
</configuration>
</plugin>


После выполнения mvn generate-sources у вас появится пакет с классами, и компилятор станет вашим линтером для SQL.

🔹 В чем JOOQ превосходит Hibernate

GROUP BY, WINDOW functions, CTE, UPSERT, RETURNING — все это JOOQ поддерживает из коробки:

// CTE + оконная функция – попробуйте сделать это в Hibernate
dsl.with(ranked).as(
select(EMPLOYEE.asterisk(),
rowNumber().over()
.partitionBy(EMPLOYEE.DEPARTMENT_ID)
.orderBy(EMPLOYEE.SALARY.desc())
.as(rn))
.from(EMPLOYEE)
)
.select()
.from(table(ranked))
.where(field(rn).eq(1))
.fetch();


🔹 Когда использовать JOOQ, а когда Hibernate

JOOQ подходит, когда у вас сложные аналитические запросы, унаследованная схема БД, которую нельзя изменить, или команда привыкла мыслить в терминах SQL. Hibernate — хороший выбор, когда нужна сложная объектная модель с каскадами, отслеживанием изменений и CRUD-операциями над сущностями.

На практике часто используют оба инструмента: Hibernate для простых CRUD-операций, а JOOQ – для сложной аналитики и отчетов.

Интеграция со Spring Boot:

@Configuration
public class JooqConfig {
@Bean
public DSLContext dslContext(DataSource dataSource) {
return DSL.using(dataSource, SQLDialect.POSTGRES);
}
}


Spring Boot автоматически настраивает DSLContext, если в classpath есть spring-boot-starter-jooq.

📌 Вывод

JOOQ — это не замена ORM, а другой подход к работе с базами данных. Если SQL для вас — это не страшный зверь, а полезный инструмент, JOOQ поможет вам стать более продуктивным и сэкономит время на отладке магии Hibernate.

💬 А что у вас в проде?

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

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥42🥰1👏1
✔️ Java-тест: Transactional + EventListener

Ревью и рефактор логики для production-кода 👇

📦 Задание

Команда написала логику для отправки письма после регистрации пользователя. На проде иногда возникает ситуация, что письма приходят, а юзера в БД нет. Найдите проблему и исправьте:

@Service
@RequiredArgsConstructor
public class UserService {

private final UserRepository userRepository;
private final ApplicationEventPublisher eventPublisher;

@Transactional
public void register(UserDto dto) {
User user = new User(dto.email());
userRepository.save(user);

eventPublisher.publishEvent(new UserRegisteredEvent(user));
}
}

@Component
@RequiredArgsConstructor
public class EmailListener {

private final EmailSender emailSender;
private final SomeOtherService someOtherService;

@EventListener
public void onUserRegistered(UserRegisteredEvent event) {
emailSender.sendWelcome(event.user().getEmail());
someOtherService.doSomething();
}
}


🔹 Задачи

— Объяснить, при каком сценарии письмо уйдёт, а пользователь не сохранится
— Исправить код, чтобы событие обрабатывалось только после сохранения юзера

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

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

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

#practise
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥18👍32
Стек — это только инструмент. Готов ли ты стать кофаундером продукта? 🚀

Энтерпрайз даёт стабильность, но часто забирает драйв. Proglib App предлагает другое: роль технического лидера в EdTech-платформе. Мы обучаем разработчиков через курсы, квизы и ИИ-агентов. MVP готов, юзеры учатся.

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

🛠️ Стек (современный Fullstack):

TypeScript, React 18, Express 5, PostgreSQL, Drizzle ORM.

Почему это вызов для тебя:

Никакой бюрократии: ты и основатель (он тоже кодит) решаете всё сами. • Инструменты будущего: активное использование Claude Code и Cursor. • Масштаб: путь от MVP до лидера рынка образовательных платформ.

Ожидания:

• Крепкий бэкграунд в разработке и проектировании БД. • Готовность быстро включиться в работу с TS/React/Node.js. • Автономность и продуктовое мышление.

Удалёнка, гибкий график, отсутствие «стеклянного потолка».

Готов сменить привычный цикл обновлений на драйв кофаундера? Пиши в бота 👇

@proglibrary_feedback_bot
😁32
👑 Магия IntelliJ IDEA

Ты явно используешь Ctrl + Alt + L (форматирование кода), но мало знаешь ли это сочетание:

Ctrl + Alt + Shift + L → Гибкое форматирование

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


— Позволяет выбрать, что именно форматировать: весь файл, выделенный код или даже только измененные строки.
— Можно отключить автоформатирование аннотаций, импортов или пробелов, если не хотите, чтобы IDEA ломала ваш стиль.
— Полезно, если работаете в команде с жесткими code style правилами, можно форматировать только нужные части, не трогая остальной код.

🔹 Дополнительные трюки

— Выделите код, затем Ctrl + Alt + Shift + L, чтобы форматировать только его.
— Используйте Settings → Editor → Code Style, чтобы настроить форматирование под себя.

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

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍85🔥3
Как работает @Async в Spring?

Spring позволяет выполнять методы асинхронно с помощью аннотации @Async. Это один из самых простых способов разгрузить основной поток и не блокировать выполнение.

🔧 Как включить

Добавь @EnableAsync на конфигурационный класс (или на @SpringBootApplication). Без этого @Async работать не будет:
@EnableAsync
@SpringBootApplication
public class App { ... }


Затем помечаешь нужный метод:
@Async
public void sendEmail(String to) {
// выполнится в отдельном потоке
}


🔍 Что под капотом

Spring создаёт AOP-прокси вокруг бина. При вызове @Async-метода прокси перехватывает вызов и отправляет задачу в TaskExecutor (по умолчанию — SimpleAsyncTaskExecutor, который создаёт новый поток на каждый вызов).

Шаги:

1. Вызов метода перехватывается прокси (AsyncExecutionInterceptor)
2. Метод оборачивается в Callable / Runnable
3. Задача отправляется в Executor
4. Вызывающий поток сразу получает управление обратно

📦 Возвращаемые типы

▪️ void
▪️ Future<T>
▪️ CompletableFuture<T>

⚙️ Свой Executor


SimpleAsyncTaskExecutor — плохой выбор для прода, т.к. не переиспользует потоки. Настрой пул:
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.initialize();
return executor;
}


Или укажи конкретный executor прямо в аннотации:
@Async("taskExecutor")
public void process() { ... }


⚠️ Нюансы

@Async не работает при вызове метода внутри того же класса (та же проблема, что с @Transactional)
— Метод должен быть public
— Исключения из void-методов тихо проглатываются — нужно настроить AsyncUncaughtExceptionHandler
— При CompletableFuture исключение попадёт в объект и его можно обработать через .exceptionally(...)

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

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

#Enterprise
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🔥2🤔21
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