Forwarded from Библиотека собеса по Java | вопросы с собеседований
Задача из реального интервью в Google 👇
📦 Задание
Google хочет проанализировать самые популярные категории поиска для оптимизации результатов.
Таблицы на картинке. Напишите запрос для подсчета общего количества поисков в каждой категории по месяцам за 2024 год.
Ставьте → 🔥, если нравится формат. Если нет → 🤔
#practise
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥15❤1👍1
☕ AI-агенты в Enterprise: от прототипов на коленке к надёжным JVM-системам
Для Java-разработчика важна стабильность и предсказуемость. В новом курсе по AI-агентам мы учим строить системы, которые не «галлюцинируют» в продакшене, а работают по строгим инженерным метрикам и соблюдают законы РФ.
📚 В обновлённой программе:
— управляемый инжиниринг: трассировка ошибок, логирование и контроль производительности;
— правовое поле: развёртывание AI-решений с учётом
—
— продвинутый
Доступ к вводным материалам открывается сразу после покупки — начните погружение в архитектуру агентов 2026 года.
⏳ Специальные условия до 28 февраля:
— введите промокод
— участвуйте в **акции «3 курса по цене 1» — выберите два любых курса в дополнение к основному.
👉 Получить доступ к курсу и подаркам
Для Java-разработчика важна стабильность и предсказуемость. В новом курсе по AI-агентам мы учим строить системы, которые не «галлюцинируют» в продакшене, а работают по строгим инженерным метрикам и соблюдают законы РФ.
📚 В обновлённой программе:
— управляемый инжиниринг: трассировка ошибок, логирование и контроль производительности;
— правовое поле: развёртывание AI-решений с учётом
152-ФЗ и всей документации;—
LangGraph промышленного уровня: интеграция human-in-the-loop в бизнес-процессы;— продвинутый
RAG: работа с корпоративными данными, таблицами и сканами.Доступ к вводным материалам открывается сразу после покупки — начните погружение в архитектуру агентов 2026 года.
⏳ Специальные условия до 28 февраля:
— введите промокод
Agent для получения скидки 10 000 рублей**; — участвуйте в **акции «3 курса по цене 1» — выберите два любых курса в дополнение к основному.
👉 Получить доступ к курсу и подаркам
This media is not supported in your browser
VIEW IN TELEGRAM
Прямо в браузере можно посмотреть, что происходит под капотом кода: как создаются и взаимодействуют объекты, как данные перемещаются в памяти, и как работает стек вызовов. Всё это можно увидеть пошагово.
Если что-то непонятно, встроенный AI-помощник объяснит логику кода или поможет разобраться в чужом решении.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#Enterprise
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥3❤1
Please open Telegram to view this post
VIEW IN TELEGRAM
😁28👍3🔥1
🐳 Магия Docker CLI
Забываете, сколько места занимают контейнеры, образы и тома Docker? Не хотите каждый раз лезть в df или гадать? Просто запустите
🔹 Зачем это нужно
— Показывает сколько места занимают образы, контейнеры, тома и билд-кэш.
— Видно реальный размер и сколько можно освободить прямо сейчас.
— Незаменимо, когда диск неожиданно кончается на проде или CI/CD агенте.
🔹 Как использовать
— Краткая сводка: docker system df
— Подробный разбор по каждому объекту: docker system df -v
— Можно комбинировать с docker system prune, чтобы сразу почистить лишнее.
— Удобно добавить в cron или мониторинг, чтобы отслеживать рост потребления.
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#Enterprise
Забываете, сколько места занимают контейнеры, образы и тома Docker? Не хотите каждый раз лезть в df или гадать? Просто запустите
docker system df, и у вас будет вся картина перед глазами.🔹 Зачем это нужно
— Показывает сколько места занимают образы, контейнеры, тома и билд-кэш.
— Видно реальный размер и сколько можно освободить прямо сейчас.
— Незаменимо, когда диск неожиданно кончается на проде или CI/CD агенте.
🔹 Как использовать
— Краткая сводка: docker system df
— Подробный разбор по каждому объекту: docker system df -v
— Можно комбинировать с docker system prune, чтобы сразу почистить лишнее.
— Удобно добавить в cron или мониторинг, чтобы отслеживать рост потребления.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#Enterprise
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥2❤1
Если один запрос зависает, batch встаёт колом. Простое увеличение
max.poll.records не решит проблему, если медленные ответы — это постоянное явление.В статье описывается, как заменить пакетную синхронизацию на трёхэтапную модель: подготовка, обработка, постобработка – через специальный wait-топик. Коммит офсета происходит сразу, сохраняя гарантию at-least-once, и даже 8-часовая недоступность Postgres не сорвёт обработку.
В итоге получили 46 TPS против 170 TPS с теми же настройками. Код, конфиги Kafka Connect и результаты тестов прилагаются.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#Enterprise
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤2🔥1
Если производительность внезапно падает, хотя два потока работают с разными частями одного объекта, скорее всего, проблема в так называемом false sharing.
Вот как это работает: процессор не берет данные из памяти побайтово. Он работает с cache line, обычно это 64 байта. Если два ядра процессора держат одну и ту же cache line в своих кэшах первого уровня (L1) и хотя бы одно ядро в нее пишет, запускается протокол когерентности (MESI). Этот протокол делает копию cache line недействительной в другом ядре. И это происходит, даже если ядра пишут в разные поля объекта, но эти поля физически находятся в одной cache line.
🔹 Как объекты лежат в памяти
JVM (HotSpot) раскладывает объект примерно так:
[mark word — 8 байт]
[klass pointer — 4/8 байт]
[поля по убыванию размера: long/double → int/float → short/char → byte/boolean → ссылки]
[padding до выравнивания на 8 байт]
Порядок полей в исходнике не соответствует порядку в памяти. JVM перегруппировывает их для минимизации выравнивания. Проверить реальный layout можно через JOL:
javaSystem.out.println(ClassLayout.parseClass(MyClass.class).toPrintable());
Для класса с `long`, `int` и `boolean` JOL покажет что-то вроде:
OFFSET SIZE TYPE
0 4 (object header: mark)
4 4 (object header: mark)
8 4 (object header: class)
12 4 int value
16 8 long counter
24 1 boolean flag
25 7 (alignment/padding)
Итого 32 байта — меньше cache line. Два таких объекта рядом в массиве легко попадут в одну линию.
🔹 Классический сценарий
class Counters {
volatile long a;
volatile long b;
}Поля a и b занимают по 8 байт. Заголовок объекта – примерно 12-16 байт. Вероятно, оба поля попадают в одну 64-байтную строку кэша. Если два потока независимо увеличивают a и b, они постоянно будут делать строку недействительной друг для друга. На многоядерной системе это легко может привести к падению производительности в 5-10 раз по сравнению с независимыми счетчиками.
Решение – добавление отступов (padding). До Java 8 это делали руками, добавляя фиктивные поля:
class PaddedCounter {
volatile long value;
long p1, p2, p3, p4, p5, p6, p7; // 56 байт padding
}Уродливо и ненадёжно. Плюс JIT мог выкинуть «мёртвые» поля.
С Java 8 появилась аннотация @Contended (пакет jdk.internal.vm.annotation):
@Contended
volatile long a;
@Contended
volatile long b;
В JVM автоматически добавляется padding в 128 байт вокруг помеченного поля. Это сделано для защиты от prefetcher'а (размер двойной cache line). Чтобы это работало вне классов JDK, нужно использовать опцию -XX:-RestrictContended.
Аннотация @Contended стоит на полях внутри Thread (например, для thread-local random), ForkJoinPool и Striped64, который является базовым классом для LongAdder и LongAccumulator.
🔹 LongAdder как способ решения проблемы
LongAdder показывает, как можно решить проблему не просто добавлением отступов, а с помощью особого подхода к проектированию. Вместо одного volatile long используется массив Cell[], где у каждой ячейки стоит @Contended. Каждый поток пишет в свою ячейку, итоговое значение получается через sum(). При большой нагрузке это работает намного быстрее, чем AtomicLong.
За это приходится платить: sum() не является атомарной операцией. Если нужна полная точность в момент чтения, LongAdder не подойдет.
🔹 О массивах примитивных типов
Тут есть свои нюансы с long[]. Элементы располагаются один за другим, без каких-либо дополнительных данных между ними. Восемь элементов long занимают ровно одну cache line. Если несколько потоков одновременно пишут в соседние ячейки, false sharing практически гарантирован. Именно поэтому в Striped64 используется массив объектов Cell[], а не long[]: каждый объект с @Contended изолирован от других.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12🔥6❤4👏1
Please open Telegram to view this post
VIEW IN TELEGRAM
😁22👍5🌚2🔥1
Сайт с интерактивными задачами → 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.
🔹 Решение
▪️ Зависимость
▪️ Конфигурация через Spring Cache
▪️ Программная конфигурация с разными политиками
▪️ Метрики через Micrometer
Даёт метрики: 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
Для кеширования результатов тяжёлых вычислений или редко меняющихся справочников 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, а помечает её для фонового обновления. Следующий запрос получит устаревшее значение мгновенно, пока фоновый поток обновляет кеш. Латентность не растёт при рефреше.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#Enterprise
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤3🔥1
Middle Java/Kotlin Developer — от 150 000 до 250 000 ₽ — офис (Екатеринбург)
Senior Kotlin/Java Developer (Knowledge Hub Team) — до 400 000 ₽ — удалёнка
Senior Java developer — до 6 000 $ — удалёнка
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤1🔥1
За год мы провели три потока курса по ИИ-агентам, а теперь запускаем масштабное обновление!
В новом, четвёртом потоке мы учли все пожелания студентов, добавили большой блок про
В программе:
— стабильные интеграции по протоколу
— оркестрация в
— продвинутый
— контроль экономики агентов: детальное управление ресурсами и маршрутизация;
— развёртывание опенсорс-моделей в закрытых контурах по 152-ФЗ.
В честь старта продаж действует спецпредложение: 3 курса по цене 1 (два дополнительных курса в подарок).
Доступ к материалам для предварительной подготовки откроется сразу после оплаты.
По промокоду
👉 Присоединиться к четвёртому потоку и внедрить агентов в Enterprise
В новом, четвёртом потоке мы учли все пожелания студентов, добавили большой блок про
AgentOps и сместили фокус с базовых концепций на суровый инжиниринг. Прикрутить API к Java-бэкенду легко, а вот сделать систему масштабируемой, со строгим контролем затрат и интеграцией в Enterprise без галлюцинаций — задача со звёздочкой.В программе:
— стабильные интеграции по протоколу
MCP и мультиагентные паттерны;— оркестрация в
LangGraph: human-in-the-loop и runbook для диагностики;— продвинутый
RAG для промышленной эксплуатации;— контроль экономики агентов: детальное управление ресурсами и маршрутизация;
— развёртывание опенсорс-моделей в закрытых контурах по 152-ФЗ.
В честь старта продаж действует спецпредложение: 3 курса по цене 1 (два дополнительных курса в подарок).
Доступ к материалам для предварительной подготовки откроется сразу после оплаты.
По промокоду
Agent забирайте скидку 10 000 ₽ (89 000 ₽ вместо 99 000 ₽). Успейте занять место до 28 февраля!👉 Присоединиться к четвёртому потоку и внедрить агентов в Enterprise
Please open Telegram to view this post
VIEW IN TELEGRAM
😁30🔥4👍3
Вводные: сервис на 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 AOP использует прокси. То есть вместо настоящего бина в контейнер помещается его обёртка. Эта обёртка перехватывает вызовы и применяет к ним различные действия, такие как логирование, управление транзакциями, проверка безопасности или другие, заданные пользователем.
Способ создания такой обёртки выбирается автоматически, но у разных способов есть свои особенности и ограничения.
JDK proxy работает только с интерфейсами. Он создаёт объект, который реализует тот же интерфейс, и все вызовы идут через специальный обработчик (InvocationHandler). Внутри есть оригинальный объект, но снаружи виден только прокси.
CGLIB работает по-другому: он создаёт подкласс вашего класса прямо во время работы программы и переопределяет методы, вставляя в них перехват. Поэтому CGLIB не может работать с final-методами и классами, т.к. подкласс не может переопределить final-метод.
Начиная с Spring Boot 2.0, CGLIB используется по умолчанию даже для классов с интерфейсами (
spring.aop.proxy-target-class=true). Раньше, если был интерфейс, использовался JDK proxy.Тут всё становится не так очевидно. Разберём такой код:
@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
👍15❤6🔥3
☕️ Java-хак: не используй Optional.get() напрямую
Классическая ошибка, которую порой делают даже опытные разработчики:
Правильно — используй альтернативы:
Optional.get() — это тот же NPE, только под другим соусом. Optional нужен именно для того, чтобы явно обработать отсутствие значения.
Если пишешь get(), спроси себя: а зачем тут вообще Optional?
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#CoreJava
Классическая ошибка, которую порой делают даже опытные разработчики:
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
👍12❤2🔥1
Forwarded from Библиотека задач по Java | тесты, код, задания
Что будет результатом кода?
Anonymous Quiz
5%
Ошибка компиляции
19%
Result: 42
18%
Result: null
43%
TimeoutException
15%
Посмотреть ответ
👍4🔥2
Последний шанс: 3 курса по цене 1 и запуск AI-агентов в продакшн
Прикручивать LLM-запросы к
В обновлённой программе фокус смещён на жёсткий инжиниринг и вывод в прод. Вы изучите архитектуру ReAct-циклов, паттерны оркестрации агентов через
Почему нельзя откладывать:
— масштабная акция «3 курса по цене 1» сгорит уже завтра;
— промокод
— сразу после оформления открываются материалы для подготовки — начать учиться можно прямо сейчас.
Забронировать место на курсе и забрать бонусы до 28 февраля
Прикручивать 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, но при этом не заставляет писать сырые строки.
🔹 Как это выглядит на практике
Классы ORDER и CUSTOMER генерируются на основе вашей схемы БД. Если вы переименуете столбец в базе, но забудете обновить код, проект просто не скомпилируется. В этом и заключается главная сила JOOQ.
🔹 Кодогенерация – основа JOOQ
JOOQ читает схему вашей БД (через JDBC) и создает Java-классы для каждой таблицы, столбца, sequence и enum. Это настраивается через плагин Maven/Gradle:
После выполнения
🔹 В чем JOOQ превосходит Hibernate
GROUP BY, WINDOW functions, CTE, UPSERT, RETURNING — все это JOOQ поддерживает из коробки:
🔹 Когда использовать JOOQ, а когда Hibernate
JOOQ подходит, когда у вас сложные аналитические запросы, унаследованная схема БД, которую нельзя изменить, или команда привыкла мыслить в терминах SQL. Hibernate — хороший выбор, когда нужна сложная объектная модель с каскадами, отслеживанием изменений и CRUD-операциями над сущностями.
На практике часто используют оба инструмента: Hibernate для простых CRUD-операций, а JOOQ – для сложной аналитики и отчетов.
Интеграция со Spring Boot:
Spring Boot автоматически настраивает DSLContext, если в classpath есть
📌 Вывод
JOOQ — это не замена ORM, а другой подход к работе с базами данных. Если SQL для вас — это не страшный зверь, а полезный инструмент, JOOQ поможет вам стать более продуктивным и сэкономит время на отладке магии Hibernate.
💬 А что у вас в проде?
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#CoreJava
Устал от того, что 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🔥4❤2🥰1👏1
Forwarded from Библиотека собеса по Java | вопросы с собеседований
Ревью и рефактор логики для 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();
}
}
🔹 Задачи
— Объяснить, при каком сценарии письмо уйдёт, а пользователь не сохранится
— Исправить код, чтобы событие обрабатывалось только после сохранения юзера
Ставьте → 🔥, если нравится формат. Если нет → 🌚
#practise
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥18👍3❤2