Библиотека джависта | 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
👾 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
🔧 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
👍103🔥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
🔥20👍53
🔍 Чтение csv файла

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


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

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

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

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

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

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

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

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

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

👉 Регистрируйся