Public channel ID 77400161222906

Java. Менторинг за чашкой кода

Канал о Java и про Java, и, конечно, для тех кто на ней кодит, ну или только собирается. Подробно разбираю вопросы к собеседованию на Java различного уровня

Channels in category
1 757
This channel's rank
№ 226
Open category →
Open in MAX

Posts for the period

By reactions

Search and the whole feed — after free registration. Custom dates — with a subscription; the channel owner sees them for free. Sorting by reactions — with a subscription. Sign up

Loading…

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

Готовим фундамент: сервер Vault и роль для приложения

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

Шаг 1. Создаем «пропуск» (Политику)
Приложению нельзя давать доступ ко всем секретам. Создаем правило, которое разрешает читать только нужную базу данных.

Создаем файл `my-app-policy.hcl` на сервере Vault:

path "secret/data/myapp/db" {
capabilities = ["read"]
}

Загрузи его в Vault:

`vault policy write my-app-policy my-app-policy.hcl`

Шаг 2. Заводим «учетку для сервера» (AppRole)
У сервера не может быть логина и пароля от человека. Используем AppRole — это пара «Role ID» (кто ты) и «Secret ID» (знаешь ли ты код).

Команды:

vault auth enable approle
vault write auth/approle/role/my-app-role token_policies="my-app-policy" token_ttl="1h"

Шаг 3. Выдаем ключи
Теперь забираем данные для нашего агента.

1. Получаем «логин» (Role ID):
`vault read auth/approle/role/my-app-role/role-id`
Сохраняем вывод этой команды в файл с названием `role-id`.
2. Получаем «пароль» (Secret ID):
`vault write -f auth/approle/role/my-app-role/secret-id`
Сохраняем вывод этой команды в файл с названием `secret-id`.

У нас на руках два файла: `role-id` и `secret-id`. Это всё, что нужно агенту для входа. Передай эти файлы тому, кто настраивает сервер с приложением.
Views: 87
Go to post Open in MAX Report an inaccuracy
Знать Core Java, Stream API и многопоточность сегодня — это как уметь менять колесо. Необходимо, но недостаточно, чтобы называться инженером. Современный разработчик — это архитектор, который понимает, как его код живет внутри дата-центра.

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

---

Уровень 1: Фундамент (Must-Have)
Это база, которая встречается в 90% вакансий Middle/Senior.

1. PostgreSQL
Да, Oracle жив, но джентльменский набор — это Postgres.
Это не просто хранилище. Мощный движок с JSONB (замена MongoDB), полнотекстовым поиском и крутейшей расширяемостью.

2. Redis
Сверхбыстрое хранилище ключ-значение в оперативной памяти. Кэширование тяжелых ответов БД, сессии пользователей, счетчики. Когда база тормозит, первым делом спасает Redis.

3. Apache Kafka
Распределенная система логов. Это асинхронное общение микросервисов. Если Сервис А упал, Сервис Б всё равно получит сообщение, когда поднимется. Гарантирует, что ни одно событие (например, заказ) не пропадет.

4. Docker
Твой код работает на твоем ноутбуке, но падает на сервере? Забудь.
Упаковка твоего приложения, Java и настроек Linux в одну «коробку». Гарантия переносимости.

5. Kubernetes (K8s)
Обеспечивает запуск сотен сервисов. K8s сам перезапустит упавший сервис, масштабирует его под нагрузкой и обновит без простоя.

6. CI/CD (GitLab CI / Jenkins)
Ты написал код, нажал Commit, и магия случилась.
Пайплайн сам соберет образ, прогонит тесты и выкатит на сервер.

---

Уровень 2: Инструменты надежности.

7. Prometheus + Grafana
Сбор метрик (сколько памяти ест запрос, сколько RPS) и красивые графики.

8. OpenTelemetry (Tracing)
В системе из 50 микросервисов запрос проходит через 10 серверов.
Ты видишь всю цепочку: «Запрос зашел -> ждал 200ms в БД -> пошел в Auth». Это Jaeger или Tempo. Находит «бутылочные горлышки».

9. HashiCorp Vault
Хранить пароли в `application.yml` — это путь к увольнению.
Централизованное хранение API-ключей и паролей. Секреты лежат в памяти, а не в коде.

10. ElasticSearch (ELK Stack) — Память логов
Когда логов гигабайты, `grep` умирает.
Полнотекстовый поиск по логам. Найти одну ошибку среди миллионов строк за 1 секунду.

---
Уровень 3: Специализация
для высоконагруженных систем и энтерпрайза.

11. Apache Artemis / RabbitMQ
В отличие от Kafka (где читают все), здесь сообщение лежит в очереди, пока один рабочий его не заберет.
Гарантированная обработка задач: отправка писем, расчет зарплаты, обработка фото.

12. Hazelcast / Infinispan — Распределенный интеллект
Это Redis на стероидах.
Ты можешь запускать вычисления прямо там, где лежат данные, не гоняя трафик в БД. Распределенные блокировки.

13. ClickHouse — Пушка аналитики
Когда PostgreSQL не справляется с отчетами по миллиардам строк.
Колоночная БД. Считает суммы и группировки по терабайтам данных за миллисекунды. Дашборды BI.

Java — это «двигатель», но без Docker, K8s, Prometheus, OpenTelemetry он далеко не уедет.
Views: 83
Go to post Open in MAX Report an inaccuracy
Сегодня решил немного написать не о технологии, а о реалии текущего мира, это лишь мое мнение и Вы можете не согласиться, но кто сегодня разработчики и почему не актуальны холивары «React vs Vue». В 2026 году роль разработчика фундаментально изменилась. Мы перестали писать каждую строчку кода, и стали архитекторами, которые собирают системы из готовых блоков.

Вот главные сдвиги, которые уже произошли, и как с этим жить.

1. Сборка вместо написания (AI-First Development)
Раньше мы искали Stack Overflow. Теперь мы описываем задачу нейросети.
Как это выглядит: Вы не пишете функцию валидации формы. Вы пишете промпт: «Сделай React-компонент формы регистрации. Поля: email, пароль. Валидация: email по RFC 5322, пароль минимум 8 символов с одной заглавной. Стиль Tailwind CSS. Отдай готовый код».
Проблема: Код работает, но он «не ваш». Вы не понимаете, как он устроен внутри, пока он не сломается.

2. Суперкомпьютеры из розетки (Cloud AI)
Вам больше не нужен сервер с 4 видеокартами под столом, чтобы обучить модель. Мощность суперкомпьютеров теперь продается как электричество. Любой стартап может арендовать кластер для обучения LLM на час и выключить его.
Следствие: Барьер входа в Machine Learning упал, но выросла цена ошибки — сжечь бюджет за ночь неправильного прогона теперь проще простого.

3. Конфиденциальность «в черном ящике» (Confidential Computing)
Самый горячий тренд безопасности. Проблема: даже если данные зашифрованы на диске, они расшифровываются в оперативной памяти сервера для обработки, и админ их видит.
Решение: Теперь данные обрабатываются внутри специального защищенного анклава процессора. Даже владелец сервера (облака) не может подсмотреть, что там происходит. Для медицины и финансов это стало стандартом.

4. Команды цифровых сотрудников (Multi-Agent Systems)
Один ChatGPT — это помощник. Десять специализированных ИИ, которые общаются друг с другом — это отдел.
Пример: ИИ-менеджер ставит задачу ИИ-дизайнеру. Тот генерирует макет и передает ИИ-фронтендеру. Если макет не проходит тесты, ИИ-менеджер сам возвращает задачу на доработку. Человек в этой цепочке появляется только на этапе финального утверждения (Human-in-the-loop).

5. Смерть «универсалов» и узкие ИИ-эксперты
Универсальные модели (вроде больших LLM) хороши для болтовни, но плохи для точных расчетов или специфических знаний.
Тренд: Появление узкоспециализированных моделей. ИИ-юрист, который знает только ГК РФ. ИИ-диагност, который смотрит только на рентген легких. Они точнее, дешевле и не галлюцинируют на темы, выходящие за их компетенцию.

Что делать разработчику, чтобы не остаться за бортом?
1. Учиться промпт-инжинирингу на уровне архитектуры. Уметь разбивать сложную задачу на подзадачи для разных ИИ.
2. Понимать границы. Знать, где ИИ ошибется (безопасность, сложная математика, бизнес-логика с нюансами).
3. Фокус на интеграцию. Код, который связывает 5 разных API нейросетей между собой, сейчас ценится выше, чем идеальный алгоритм сортировки.

Нас не заменили нейросети. Нас «апгрейдили». Рутина ушла, осталась только сложная инженерная работа по сборке систем.
Views: 79
Go to post Open in MAX Report an inaccuracy
Счёт за облако: где найти 40% бюджета

В IT есть два типа компаний: те, кто уже оптимизировал расходы на облако, и те, кто ещё боится открывать ежемесячный счет. В 2026 году умение читать облачные отчеты — это не скейлинг-инженера, а базовый навык.

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

1. Зомби-ресурсы (The Walking Dead)
Это самая частая дыра в бюджете.
* Что это: Диски от удаленных виртуалок, неиспользуемые публичные IP-адреса, остановленные, но не удаленные Managed-базы данных, логи, которые копятся вечно.
* Как найти: Используйте тегирование (Tags). Каждый ресурс обязан иметь тег `Owner` и `Environment` (prod/dev). Затем запустите поиск по ресурсам без тегов или по тегам уволившихся сотрудников.
* Экономия: Легко срезает 10–15%

2. Неправильный тип инстансов (Right-sizing)
* Что это: Виртуалки с 64 ГБ RAM, которые потребляют 2 ГБ. CPU Credits (у T-серии в AWS), которые сгорают впустую.
* Как найти: Смотрите на среднюю утилизацию CPU и Memory за 2 недели. Если средняя загрузка CPU < 20%, а памяти < 40% — вы где-то переплачиваете.
* Действие: Смените тип инстанса на меньший или перейдите на ARM-архитектуру (Graviton), которая дешевле.
* Экономия: 20–30%.

3. Хранилища (S3 / Blob Storage)
Вы заливаете туда данные, но забываете, как их хранить.
* Что это: Хранение логов и бэкапов на «горячем» (Hot) хранилище годами.
* Как найти: Проверьте Lifecycle Policies.
* Решение:
* Логи старше 30 дней — в `Infrequent Access (IA)`.
* Логи старше 90 дней — в `Glacier / Archive`.
* Бэкапы — настроить инкрементальное хранение и удалять старые версии.
* Экономия: До 50% от стоимости хранения.

4. Data Transfer (Исходящий трафик)
Внутри региона трафик часто бесплатный, но во вне оплачивается все.
* Что это: Репликация БД между регионами «просто на всякий случай», CDN, который не кэширует, или сервисы, которые дергают друг друга по публичным IP.
* Как найти: Вкладка «Network» в биллинге.
* Решение: Держать сервисы внутри одной VPC и одного Availability Zone, где это возможно. Использовать VPC Endpoints для доступа к S3.

5. Кэш
Redis или Memcached, который живет 24/7, хотя нужен только в пики нагрузки.
* Решение: Используйте Serverless-кэши (например, AWS ElastiCache Serverless) или настройте автомасштабирование нод. Если кэш используется только днем — выключайте его ночью (если это не сессионные данные).

Главное не оптимизируйте вслепую. Сначала пометьте ресурсы тегами, подождите и проверьте теорию, посмотрите отчет, и только потом выключайте. Выключить базу данных прода — это не оптимизация, это саботаж.
Views: 90
Go to post Open in MAX Report an inaccuracy
Продолжаем разбор Hibernate. В первой части мы выучили словарь, теперь переходим к самому интересному

Hibernate: Продвинутый уровень

1. Состояния Entity (Жизненный цикл)

Объект в Hibernate — это не просто данные, это объект с состоянием. Их три:
* Transient (Временный): Объект создан через `new User()`, но Session о нем не знает. В БД его нет.
* Persistent (Постоянный): Объект прикреплен к Session (`session.save(user)` или `session.get(User.class, id)`). Любое изменение этого объекта отслеживается (Dirty Checking) и при коммите улетит в БД.
* Detached (Отсоединенный): Объект был Persistent, но Session закрылась. Он жив в памяти, но Hibernate за ним больше не следит. Чтобы вернуть его в игру, используют `merge()`.

2. Методы get() vs load() (Классика жанра)
Разница в том, как они ходят в базу:
* `get()`: Идет в БД **сразу**. Если объекта нет — возвращает `null`.
* `load()`: Возвращает **Прокси-объект** (заглушку) сразу. В БД идет только тогда, когда вы попытаетесь обратиться к полям объекта (Lazy Initialization). Если объекта нет — при обращении к полям кинет `ObjectNotFoundException`.
* Совет: Используйте `get()`, если не уверены, что объект есть. Используйте `load()`, если уверены (например, для установки связи `order.setUser(user)`).

3. save() vs persist() vs saveOrUpdate()
Это главная боль Junior-разработчиков.
* `persist()`: Делает объект Persistent. Работает только внутри транзакции. Не делает `INSERT` сразу (может сделать позже при flush). Гарантирует соблюдение спецификации JPA.
* `save()`: Старое Hibernate-API. Возвращает `Serializable` (ID объекта). Может сделать `INSERT` сразу (вне транзакции), что ломает логику JPA.
* `saveOrUpdate()`: Устаревший метод. Если ID есть — делает `UPDATE`, если нет — `INSERT`. Сейчас лучше использовать `merge()`.

4. Кэширование (Уровни кэша)
* L1 Cache (Session Cache): Есть у каждой Session по умолчанию. Если в рамках одной сессии вы дважды попросили юзера с `id=1`, Hibernate пойдет в БД только один раз. Очищается при закрытии Session.
* L2 Cache (SessionFactory Cache): Общий для всех сессий. Нужен, если данные читаются часто, а меняются редко (например, справочники: города, страны). Настраивается через провайдеры (EhCache, Redis).

5. Lazy Loading и Проблема N+1
* Lazy: Связанные коллекции (`@OneToMany`) не грузятся из БД, пока вы к ним не обратитесь. Это экономит память.
* Проблема N+1: У вас есть 100 заказов. В цикле вы делаете `order.getItems()()`. Hibernate делает 1 запрос, чтобы найти 100 заказов, и еще 100 запросов, чтобы для каждого заказа найти его товары. Итого 101 запрос.
* Решение: `JOIN FETCH` в HQL (`FROM Order o JOIN FETCH o.items`) или `@EntityGraph`.

6. Flush и Commit
* `flush()`: Выгружает изменения из Session в БД (генерирует SQL), но **не завершает транзакцию**. Если после `flush()` случится ошибка, можно откатить.
* `commit()`: Завершает транзакцию. Обычно внутри себя делает `flush()`.
* Зачем вручную? Если вам нужно получить ID объекта *до* коммита (например, чтобы записать его в лог или передать в другой сервис).

Итог:
Hibernate кажется магией, пока вы не поймете, что это просто умный SQL-генератор, который держит граф объектов в памяти. Как только вы поймете, как работает Session и Proxy, 90% багов исчезнут.
Views: 104
Go to post Open in MAX Report an inaccuracy
🛠️ Словарь Java-разработчика: 10 терминов Hibernate, которые нужно знать

Hibernate — это не просто библиотека, это целый мир со своим языком. Если вы начинаете новый проект со Spring Boot, без этих определений вы «поплывете» на первом же вопросе техлида.

1. ORM (Object-Relational Mapping)
Это сама концепция. Мост между миром Java (объекты, классы, наследование) и миром БД (таблицы, строки, связи). Hibernate берет ваш класс `User` и сам превращает его в строку в таблице `users`.

2. Entity (Сущность)
Обычный Java-класс (POJO), помеченный аннотацией `@Entity`. Это «паспорт» объекта. Hibernate говорит: «Ага, этот класс — не просто код, это данные, которые я должен сохранить в БД».

3. Session (org.hibernate.Session)
Главный рабочий инструмент. Именно через `Session` вы просите: «Сохрани это», «Найди то», «Удали вон то». Важно: `Session` — это не поток (Thread), это обычно короткоживущий объект внутри транзакции.

4. SessionFactory (org.hibernate.SessionFactory)
Тяжелая, неповоротливая, но очень важная штука. Она производит `Session` и создается один раз при старте приложения. Она читает конфиги, маппинги и строит в памяти карту вашей базы данных.

5. JPA (Java Persistence API)
Это спецификация (набор правил и аннотаций), написанная Oracle. Hibernate — это реализация этой спецификации.
Аналогия: JPA — это USB-порт (стандарт), а Hibernate — это флешка, которую вы в него втыкаете. Вы можете заменить Hibernate на EclipseLink, и ваш код почти не изменится, потому что оба работают по правилам JPA.

6. HQL (Hibernate Query Language)
Язык запросов, похожий на SQL, но пишется для объектов, а не для таблиц.
*SQL:* `SELECT * FROM users WHERE name = 'Ivan'`
*HQL:* `FROM User u WHERE u.name = 'Ivan'`
Вы просите «Дай мне объекты User», а не «Дай мне строки из таблицы».

7. Lazy Loading (Ленивая загрузка)
Спасение от `OutOfMemoryError`. По умолчанию Hibernate не грузит связанные данные (например, список `Order` для `User`), пока вы явно их не попросите.
Проблема (N+1): Если в цикле попросить заказы для 100 пользователей, Hibernate сделает 101 запрос к БД. Лечится `JOIN FETCH`.

8. Dirty Checking (Проверка на грязь)
Магия Hibernate. Перед закрытием транзакции он сравнивает состояние объектов в памяти с тем, что было считано из БД. Если поле `name` изменилось, он сам сгенерирует `UPDATE`. Вам не нужно писать `save()`, если вы просто поменяли геттер/сеттер внутри транзакции.

9. Cascade (Каскадирование)
Правило «семейственности». Если вы удаляете `User`, что делать с его `Order`?
`CascadeType.ALL` скажет: «Удаляй всё вместе с хозяином». `CascadeType.PERSIST` скажет: «Если сохраняешь юзера, сохрани и его новый адрес автоматически».

10. Flush (Сброс)
Это момент, когда Hibernate берет накопленные в памяти изменения (`save`, `update`) и отправляет их в БД в виде SQL-запросов. Обычно происходит при коммите транзакции. Но `session.flush()` можно вызвать принудительно, если нужно, чтобы ID сгенерировался прямо сейчас (например, чтобы записать его в лог).

Итог: Hibernate берет на себя рутину (JDBC, ResultSet, Connection pooling), но взамен требует понимания того, как работает сессия и ленивая загрузка.
Views: 84
Go to post Open in MAX Report an inaccuracy
Всем привет, сегодня заглянем под капот JVM, где на самом деле живёт int в JVM?
Все пишут int a = 10;
но что под капотом делает JVM?
int — это не объект, а примитив.

Он не создаётся через new, не лежит в куче по умолчанию и не проходит через конструктор. Для него у JVM есть отдельные байт-код инструкции: iload — положить на стек, istore — сохранить обратно, iadd — сложить напрямую. Никаких вызовов методов, никакого заголовка объекта, никакого участия GC.

Где хранится int — зависит от того, чем является переменная:
- Локальная переменная
→ лежит в массиве locals текущего стекового фрейма. Компилятор превращает имя переменной в «номер слота», а рантайм просто читает и пишет по индексу. Метод завершился — фрейм вытолкнулся, и этот int исчез. Сборщику мусора тут делать нечего.

- Поле экземпляра → лежит внутри объекта в куче. У объекта фиксированный layout, а у поля есть смещение (offset). Есть ссылка на объект — прибавили смещение — нашли этот int.
- Статическое поле → живёт вместе с объектом Class, который сам по сути лежит в куче. Время жизни привязано к загрузчику классов.

Почему int такой «низкоуровневый»? Потому что Java с самого начала разделила типы на примитивные и ссылочные. int — это 32-битное знаковое число в дополнительном коде, диапазон от -2³¹ до 2³¹−1. JVM работает с ним как с числом в регистре, без всякой «распаковки».
И только когда int нужно использовать как объект (например, положить в List<Integer>), компилятор автоматически упаковывает его в Integer: вот тогда появляется объект, заголовок, куча и GC.

Коротко: int в JVM — это по сути «отрезок из 32 бит». Где он лежит — зависит от того, где ты его объявил. На стеке — живёт мгновение, в куче — вместе с объектом, статический — вместе с классом.
Views: 60
Go to post Open in MAX Report an inaccuracy
Всем привет,
Сегодня поговорим о таком недооцененно инструменте, как Row-Level Security (RLS) — политика безопасности на уровне строк. Это фича PostgreSQL (и других СУБД), которая работает как невидимый `WHERE`, автоматически добавляемый к каждому вашему запросу.

Как это работает?
Представьте таблицу `documents`. Там лежат документы и для бухгалтерии, и для юристов, и для гендиректора.
Обычно вы пишете: `SELECT * FROM documents WHERE department_id = 5`.

С RLS вы просто пишете: `SELECT * FROM documents`.
А база сама дописывает: `...AND current_user = documents.author`.

Пользователь `ivan` даже не увидит строки, созданные пользователем `petr`. Для него таблица `documents` будет содержать только его собственные файлы.

Где это спасает?

1. SaaS-сервисы (Борьба с «проблемой соседа»).
Самый частый баг в стартапах — когда один клиент видит данные другого из-за ошибки в `JOIN` или забытого `WHERE` в сложном отчете.
С RLS это невозможно физически. Вы ставите политику: `USING (tenant_id = current_setting('app.current_tenant')::int)`. Теперь, даже если разработчик напишет `SELECT * FROM orders` без фильтра по клиенту, база отдаст только заказы текущей компании.

2. Мягкое удаление (Soft Delete).
Вместо того чтобы писать `WHERE deleted_at IS NULL` в каждом запросе (и обязательно забыть его в одном из 100 мест), вы делаете это политикой RLS. Для всех «обычных» запросов удаленные строки просто перестают существовать. Они есть в таблице, но база их скрывает.

3. Аудит и защита от «слива».
Вы можете запретить `SELECT *` даже администратору БД (если он залогинен под обычным пользователем), ограничив видимость только определенными колонками или строками.

Подводный камень:
RLS — это не бесплатно. Это дополнительная нагрузка на планировщик. Если политик много и они сложные, время планирования запроса (`Planning Time`) может вырасти. И главное — если вы забудете включить `RLS ENABLE` на таблице, политика работать не будет.

RLS — это, если бэкенд ошибся, база данных не даст украсть данные. Он делает вашу систему по-настоящему защищенной
Views: 81
Go to post Open in MAX Report an inaccuracy
Всем привет, сегодня поговорим про индексы в PostgreSQL.

Мы привыкли думать, что индекс — это просто «скоростная шпаргалка» для поиска. Но у него есть темная сторона, о которой молчат.

Факт: Индекс живет своей отдельной жизнью
Когда вы обновляете всего одну строку в таблице, база данных не меняет её «по месту».
1. Она создает новую версию строки (это называется MVCC).
2. А в каждом индексе, который есть на этой таблице, она тоже создает новую запись.

Если у вас на таблице 10 индексов, то при изменении одной строки база делает 11 физических записей на диск.

Почему это важно?
Это главная причина, почему индексы нужно удалять.
Каждый «лишний» индекс не просто занимает место на SSD. Он превращает легкие операции `UPDATE` и `INSERT` в тяжелую работу. База вынуждена обновлять дерево индекса, пересортировывать его и записывать на диск снова и снова.

Например:
У вас есть колонка `last_login_date`. Вы решили сделать по ней индекс, «на всякий случай».
Теперь каждый раз, когда пользователь просто заходит на сайт, Postgres:
* Записывает новую строку в таблицу.
* Записывает новую запись в индекс `last_login_date`.
* Записывает новые записи во все остальные 5 индексов таблицы.

Если пользователей много, этот «лишний» индекс может стать бутылочным горлышком и замедлить работу всего приложения.

Как проверить?
Зайдите в `pg_stat_user_indexes`. Если вы видите индекс, у которого `idx_scan` (счетчик использования) равен 0, а `idx_tup_upd` (счетчик обновлений) растет — этот индекс вреден. Он ничего не ускоряет, но заставляет базу работать на износ при каждой записи.

Индекс — это не бесплатно. Это компромисс.
Правило: Если вы ищете по полю редко, а пишете в него часто — индекс, скорее всего, не нужен.
Views: 76
Go to post Open in MAX Report an inaccuracy
CTE (WITH) в PostgreSQL: как превратить лапшу из SQL в читаемый конвейер

Вы открываете запрос на 500 строк, написанный вашим предшественником. Там `JOIN` внутри `JOIN`, а в `WHERE` зарыт подзапрос, который считает среднее. Грязный ди это код? И нужно ли переписать это через CTE?

Что это такое?
Простыми словами: это временная именованная таблица, которую вы создаете в начале запроса и используете дальше.
Вместо того чтобы прятать логику глубоко в скобках, вы выносите её наверх, даете имя и используете как обычную таблицу.

Задача: найти сотрудников, чья зарплата выше средней по их отделу.

Без CTE (Спагетти-код):

SELECT e.name, b.name AS boss
FROM employees e
JOIN employees b ON e.boss_id = b.id
WHERE e.salary > (
SELECT AVG(salary)
FROM employees e2
WHERE e2.department_id = e.department_id
);

Какая тут проблема: Подзапрос в `WHERE` может выполняться для каждой строки. Читать это тяжело.

С CTE (Чистый код):

WITH avg_by_dept AS (
SELECT department_id, AVG(salary) as avg_sal
FROM employees
GROUP BY department_id
)
SELECT e.name, b.name as boss
FROM employees e
JOIN employees b ON e.boss_id = b.id
JOIN avg_by_dept a ON e.department_id = a.department_id
WHERE e.salary > a.avg_sal;

Почему это лучше? Мы читаем код сверху вниз, как обычную программу. Сначала подготовили «ингредиенты» (`avg_by_dept`), потом из них «приготовили» результат.

Магия конвейера
Настоящая сила CTE — в разбиении сложной задачи на шаги.
Представьте аналитику для интернет-магазина: нужно найти активных пользователей, посчитать их выручку и сегментировать. Без CTE это монстр. С CTE это конвейер:

WITH
-- Шаг 1: Кто покупал в этом месяце?
active_users AS (
SELECT user_id FROM orders WHERE order_date >= NOW() - INTERVAL '30 days'
),
-- Шаг 2: Сколько они потратили?
recent_revenue AS (
SELECT user_id, SUM(amount) as total
FROM orders
WHERE user_id IN (SELECT user_id FROM active_users)
GROUP BY user_id HAVING SUM(amount) > 1000
)
-- Финал: Собираем всё вместе
SELECT u.name, r.total
FROM users u
JOIN recent_revenue r ON u.id = r.user_id;

Каждая часть запроса — отдельный бизнес-этап. Тестировать и отлаживать такой код в разы проще.

Важный нюанс: Оптимизация (Ограда оптимизатора)
Это то, что нужно знать, чтобы не выстрелить себе в ногу.
* До Postgres 12: CTE всегда вычислялись целиком и сохранялись во временную память. Это был «барьер»: планировщик не мог заглянуть внутрь CTE и применить к нему фильтры. Если вы делали `WITH cte AS (SELECT * FROM huge_table) SELECT * FROM cte WHERE id = 1`, база сначала читала *всю* огромную таблицу, и только потом фильтровала.
* Postgres 12+: Теперь CTE по умолчанию «встраиваются» (inlined) в основной запрос, как обычный подзапрос. Оптимизатор умнеет.
* Когда использовать `MATERIALIZED`? Если вы используете тяжелый CTE несколько раз в запросе. Чтобы не считать его заново, напишите: `WITH heavy_calc AS MATERIALIZED (...)`.
* Когда использовать `NOT MATERIALIZED`? Если CTE большой, а вам из него нужна всего пара строк через `LIMIT`. Это заставит базу «протолкнуть» лимит внутрь CTE.

Рекурсивные CTE: Лифт по дереву
Нужно обойти дерево категорий или структуру компании (где есть `parent_id`)?
Раньше для этого писали циклы на бэкенде или хранимые процедуры. Теперь это 10 строк SQL:

WITH RECURSIVE org_tree AS (
-- База: Топ-менеджмент (у кого нет начальника)
SELECT id, name, parent_id, 1 AS level
FROM departments WHERE parent_id IS NULL
UNION ALL
-- Рекурсия: Ищем подчиненных
SELECT d.id, d.name, d.parent_id, t.level + 1
FROM departments d
JOIN org_tree t ON d.parent_id = t.id
)
SELECT * FROM org_tree ORDER BY level;

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

CTE — это не про скорость (хотя иногда помогает). Это про читаемость и поддерживаемость. Код, написанный через `WITH`, говорит: «Смотрите, я сначала считаю это, потом это, а в конце соединяю всё здесь».
Views: 100
Go to post Open in MAX Report an inaccuracy
The full feed — after free sign-up
VKontakte Telegram Odnoklassniki
Favorites at datool.ru

Follow the channel in your account

Sign up at datool.ru to add the channel “Java. Менторинг за чашкой кода” to favorites and track its metrics.

Sign up
For channel owners

Verify your channel

Sign in or sign up at datool.ru, then add our bot to the channel following the instructions in your account. Once ownership is verified, the lock disappears and channel management becomes available.

Sign in or sign up

Report an inaccuracy

Describe what needs fixing. If you have a source, add a link.

You are leaving datool.ru

The link leads to an external website. datool.ru is not responsible for its content — check the address before entering passwords or payment details.

Redirecting in 10 s

Go