Java: fill the gaps
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк 🔥Тот самый курс по многопочке🔥 https://fillthegaps.ru/mt Комплименты, вопросы, предложения: @utki_letyat
显示更多📈 Telegram 频道 Java: fill the gaps 的分析概览
频道 Java: fill the gaps (@java_fillthegaps) 俄语 语言赛道中的 是活跃参与者。目前社区聚集了 12 549 名订阅者,在 技术与应用 类别中位列第 10 120,并在 俄罗斯 地区排名第 52 841 位。
📊 受众指标与增长动态
自 невідомо 创建以来,项目保持高速增长,吸引了 12 549 名订阅者。
根据 07 六月, 2026 的最新数据,频道保持稳定运转。过去 30 天订阅人数变化为 -46,过去 24 小时变化为 0,整体触达仍然可观。
- 认证状态: 未认证
- 互动率 (ER): 平均受众互动率为 34.72%。内容发布后 24 小时内通常能获得 N/A% 的反应,占订阅者总量。
- 帖子覆盖: 每篇帖子平均可获得 0 次浏览,首日通常累积 0 次浏览。
- 互动与反馈: 受众积极参与,单帖平均反应数为 0。
- 主题关注点: 内容集中在 redis, hashmap, linkedhashmap, индекс, фича 等核心主题上。
📝 描述与内容策略
作者将该频道定位为表达主观观点的平台:
“Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк
🔥Тот самый курс по многопочке🔥
https://fillthegaps.ru/mt
Комплименты, вопросы, предложения: @utki_letyat”
凭借高频更新(最新数据采集于 08 六月, 2026),频道始终保持新鲜度与高覆盖。分析显示受众积极互动,使其成为 技术与应用 类别中的关键影响点。
Buffers: shared hit=32 read=5000означает, что в буфер с диска прочитано 5000 страниц/блоков. Каждый блок 8 КБ, итого с диска считано 40 Мб данных. Эти цифры часто нужны для анализа нагрузки. 💫WAL💫 Свежие обновления не сразу пишутся на диск, а некоторое время лежат только в оперативной памяти. В случае сбоя данные потеряются. Чтобы этого не произошло, суть изменений записывается в конец специального файлика, который называется WAL (write ahead log). Что, запись на диск? Это же медленно!!!1 Все верно, но сравните масштаб. В случае апдейта изменения встраиваются в огромную структуру, надо обновить индексы, плотнее укомплектовать данные, обновить статистику и ещё миллион вещей. В случае WAL мы пишем минимум информации в конец файла. Это гораздо быстрее💫 Помимо повышения надёжности, WAL используется для некоторых видов репликации. Вот и всё, ничего сложного:) Ставьте огонечки постам, это меня очень радует😊
@Entity
public class Post {
@OneToMany
private List<Comment> comments;
}
Мы хотим получить 5 постов с комментариями. Чтобы избежать N+1, загружаем комментарии через EntityGraph:
@EntityGraph
List<Post> findAllPosts(PageRequest.of(0, 5));
Метод findAllPosts вернёт 5 постов, как мы и просили. Но в логах увидим SQL запрос без лимитов, в память загрузятся все посты.
Как найти места со скрытой пагинацией?
Hibernate пишет в консоль ворнинг, который легко пропустить. Лучше сделать так:
🌷 Ставим свойство
spring.jpa.properties.hibernate.query.fail_on_pagination_over_collection_fetch=true🌷 Запускаем интеграционные тесты 🌷 Смотрим, где падают исключения Как починить скрытую пагинацию? Способов много, самое простое - добавить над списком @BatchSize. Либо поставить свойство
spring.jpa.properties.hibernate.default_batch_fetch_size=50Тогда BatchSize будет по умолчанию применяться для всех списков. Про другие решения и перфоманс читайте в этой статье на Хабре Зачем Hibernate использует in-memory пагинацию? При запросе с джойном из нескольких таблиц получается декартово произведение, для которого некорректно применить limit. Но контракт соблюдать надо хоть как-то, поэтому была выбрана пагинация на стороне клиента. Сразу возникает вопрос. Почему в таких случаях не сделать 2 запроса? ▫️ select * from posts limit 5; ▫️ Извлечь айдишники постов ▫️ select * from comments where post_id in (?,?,?,?,?); ▫️ Связать сущности Решение выглядит просто, и вариант с BatchSize примерно так и работает. Почему оно не используется по умолчанию - непонятно. Hibernate - яркий пример “протекающих абстракций”. Куча нюансов и проблем, о которых нужно знать. В документации по поводу in-memory панинации есть такая фраза: Possibility of terrible performance is left as a problem for the client to avoid. Проблема ужасного перформанса - это проблема клиента. Вся суть хибернейта🤌 По возможности не тащите его в проект. Есть более удобные и прозрачные альтернативы. Например, Spring Data JDBC💚
docker run -dit --cpus="2" --memory="512m" imagename
Если в одном сервисе произойдет беда, остальные будут работать как ни в чем ни бывало.
✨Приложение✨
Здесь возможности поменьше. JVM не контролирует загруженность процессора, этим занимается ОС. Зато тщательно считает свои обьекты и занятую память, мы можем задать рамки флажками типа -Xmx.
✨Внутри сервиса✨
В java коде нельзя явно ограничить количество памяти и нагрузку на цпу. Но в нашей власти повлиять на количество потоков, как следствие - ограничить число запросов для разных эндпойнтов.
Для этого Spring предлагает аннотацию @Bulkhead:
@GetMapping(value = "/post/{id}")
@Bulkhead(name = "getPost", fallbackMethod = "postFallback")
public Post getPost(@PathVariable int id) {…}
В проперти maxThreadPoolSize настраиваем максимальное число потоков.
Ограничение запросов иногда полезно, но это ненадежная реализация паттерна Bulkhead. Нет нужного уровня изоляции, у приложения все ещё общая память. Тяжёлый SQL запрос из одного потока может выгрузить половину БД и запросто положит сервис с OutOfMemoryError.
И ещё, обратите внимание на важный момент!
Допустим, мы почитали паттерны отказоустойчивости микросервисов, нашли среди них Bulkhead и решили, что нам надо. Гуглим bulkhead example, и вся поисковая выдача будет забита спринговыми аннотациями.
Как мы уже обсудили, это не самый надёжный способ. Самое верное - ограничить память и CPU в настройках Docker/виртуалки/Kubernetes/etc. Беда в том, что слово Bulkhead в этих инструментах не используется, там limits и constraints. Поэтому такое решение может легко пройти мимо.
При работе с паттернами нужно четко понимать, какая проблема решается и за счёт чего. Не хватать первое же решение с нужным словом, смотреть не только на форму, но и на содержание🤌Optional.stream()?
Когда ты в разработке 10+ лет, сложно чему-то удивиться. Кажется, что JDK изучен вдоль и поперек, и этот мир абсолютно понятен.
Но недавно мне встретился странный Optional.stream(). Попробуем угадать, что он делает.
Может быть, метод нужен, чтобы сделать из Optional стрим и использовать методы Stream API?
Хотя интернет предлагает нам статьи с подобными примерами, включим здравый смысл.
Зачем так усложнять код? Зачем из одного значения делать стрим?
Оставим авторов статей молча краснеть и продолжим исследование.
📚 В документации находим больше интересных деталей:
▫️ Если в Optional есть значение, метод вернёт стрим с этим значением.
▫️ Если Optional пустой, вернётся пустой стрим.
Также узнаем, что метод предназначен для работы с flatMap.
Например, некоторые пользователи указывают емейл, а некоторые нет. Если getEmail возвращает Optional, список емейлов получаем так:
List emails = users.stream().map(User::getEmail).flatMap(Optional::stream).toList();А теперь критика. Ваша любимая часть:) Что с этим методом не так, и как можно сделать лучше? Метод нужен, чтобы вытащить данные из набора Optional. В одну строчку это не сделать, минимум в две:
.filter(Optional::isPresent) .map(Optional::get)Новый метод делает все в одну строку, но Почему. Такой. Ужасный. Дизайн. 👎 Метод находится в классе Optional, но бесполезен для Optional обьекта 👎 Метод заточен только под flatMap 👎 Выглядит непонятно. flatMap(Optional::stream) похож на набор слов, приходится напрячься, чтобы его понять. Сравните с очаровательными filter, map, count, findFirst🥰 👎 Сомнительная реализация. Сначала плодим маленькие стримы из каждого элемента, потом их соединяем. Столько лишних действий! Как можно по-другому? Я бы добавила в Stream API отдельный метод, “очищающий” текущий стрим от лишнего. Пусть он будет универсальным: ▫️ Для стрима из Optional возвращает стрим значений, empty пропускаются ▫️ Для прочих стримов убирает null элементы Название под вопросом. Мне нравится getValues(), но можно и получше придумать. И всё, незачем усложнять класс Optional и плодить сложные конструкции! Простота и ясность - наше всё. Не знаю, чем думал автор метода и куда смотрели ревьюеры. Мы с вами точно бы такое не пропустили🔥
WebSecurityConfigurerAdapter#configure(HttpSecurity http)
Чтобы увидеть процесс наглядно, поставьте брейкпойнт в методе
FilterChainProxy#doFilterInternal. Там увидите все фильтры, можно погулять по ним и отследить, что происходит с запросом
2️⃣ Авторизация/аутентификация
скрывается за фасадом — бином AuthenticationManager.
Чтобы авторизовать пользователя, в коде проекта нужно вызвать метод
authenticationManager.authenticate
Может в фильтре, может в контроллере, зависит от механизма безопасности.
Метод authenticate пройдётся по всем заданным источникам авторизации. Результат можно положить в SecurityContextHolder, тогда он будет доступен из любого места в коде. Также бросится событие AuthenticationSuccessEvent, но обычно его игнорируют:)
Источники авторизации называются *Provider (например, DaoAuthenticationProvider) и определяются в конфиге. В итоге всё, что нужно от разработчика это:
☝️определить провайдеры
✌️ вызвать authenticate в нужном месте
Посмотреть список текущих провайдеров: брейкпойнт в методе ProviderManager#authenticate.
Задать список провайдеров: метод configure(AuthenticationManagerBuilder auth) в классе WebSecurityConfigurerAdapter
🦄Что с этим знанием делать на практике
Если вам дали задачу, связанную с Spring Security, не бросайтесь сразу гуглить
spring security jwt/oauth/ldap example
Шанс запутаться и сделать не то стремится к 100%. Если у вас крупный и сложный проект, идеально подходящего туториала точно не будет.
Лучше проработать каждую часть по порядку:
🛡️ Разобраться в механизме безопасности, который нужно реализовать. Что, откуда и как передаётся, где, когда и как валидируется и сохраняется.
Это самый важный шаг! Обязательно подключите тестировщика, ему тоже надо в это погрузиться.
🛡️ Прикинуть, как полученная схема ложится на архитектуру секьюрити. Что сделать в фильтрах до обработки запроса, а что после. Возможно, что-то надо сделать внутри слоя сервисов
🛡️ Желательно обсудить полученное решение со старшим товарищем
Только после этого смотрите документацию, туториалы и собирайте из них решение.
Действуя по плану выше, вы потратите меньше времени и сохраните высокую самооценку🙂Objects.hashCode(Object)
Objects.hash(Object…)
Человек, который видит их в первый раз, обязательно спросит
🤔 Чем отличается hash и hashCode?
🤔 Почему они выдают разные результаты?
(ответ на вопрос перед постом: hash3 отличается от остальных хэшей)
В хорошем апи у пользователя минимум вопросов, как им пользоваться. В идеале даже смотреть документацию на нужно. Это, наверное, самый главный принцип проектирования.
В нашем случае приходится лезть в доку и исходный код👎
Что увидим внутри:
▫️ hashCode принимает 1 аргумент - Object, и возвращает его хэш
▫️ hash принимает массив объектов переменной длины, итоговый хэш считается на основе переданных полей. Чтобы учесть порядок, hash производит дополнительные вычисления, даже для одного поля. Поэтому итоговые хэши отличаются.
Супер, я разобралась, но зачем мне в это погружаться? Я хочу просто посчитать хэш объекта, а не вникать в разницу 2 методов. Тратить даже 5 минут на такую простейшую задачу отвратительно. Bad user experience во всей красе.
В случае хэшей исправить ситуацию элементарно, просто дать методам одинаковые имена:
Objects.hashCode(Object)
Objects.hashCode(Object…)
Если передать один обьект, java не запутается и вызовет нужный метод. Плюсы очевидны:
✅ Не надо думать, какой метод вызвать
✅ Хэшкод обьекта будет одинаков в любом месте кода
Почему так не сделано изначально?
Я пыталась найти ответ, смотрела старые исходники и доки. Не нашла ни одной причины, почему имена разные и результаты не согласованы.
Видимо автор решил, что и так сойдёт.
Оставим это на совести разработчиков JDK, а себе заберём следующие выводы:
▫️ В хорошем апи пользователю не нужно смотреть документацию, чтобы сделать базовые вещи
▫️ Если в апи есть неочевидные моменты, их надо поправить, а не писать в доке warning
▫️ Если пользователь пошел смотреть исходники, чтобы разобраться - это полный провал
Чем сложнее система, тем важнее вкладываться в простоту. Простоту понимания, чтения, поддержки. Даже собственный код через пару месяцев выглядит как чужой. Понятные названия переменных, классов и методов, непротиворечивость - база для комфортной работы❤️
Это уже не первый пост, где я разбираю неудачные методы в JDK. Если вам хочется больше, предлагаю почитать
🤦 Критикую метод HashMap из java 20
🤦 Критикую методы StreamAPI в java 22
🤦 Критикую методы BigDecimal
现已上线!2025 年 Telegram 研究 — 年度关键洞察 
