Java: fill the gaps
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк 🔥Тот самый курс по многопочке🔥 https://fillthegaps.ru/mt Комплименты, вопросы, предложения: @utki_letyat
إظهار المزيد📈 نظرة تحليلية على قناة تيليجرام Java: fill the gaps
تُعد قناة Java: fill the gaps (@java_fillthegaps) في القطاع اللغوي الروسية لاعباً نشطاً. يضم المجتمع حالياً 12 544 مشتركاً، محتلاً المرتبة 10 116 في فئة التكنولوجيات والتطبيقات والمرتبة 52 822 في منطقة روسيا.
📊 مؤشرات الجمهور والحراك
منذ تأسيسه في невідомо، حقق المشروع نمواً سريعاً وجمع 12 544 مشتركاً.
بحسب آخر البيانات بتاريخ 09 يونيو, 2026، تحافظ القناة على نشاط مستقر. خلال آخر 30 يوماً تغيّر عدد الأعضاء بمقدار -48، وفي آخر 24 ساعة بمقدار -6، مع بقاء الوصول العام مرتفعاً.
- حالة التحقق: غير موثّقة
- معدل التفاعل (ER): يبلغ متوسط تفاعل الجمهور 34.74%. وخلال أول 24 ساعة من النشر يحصد المحتوى عادةً N/A% من ردود الفعل نسبةً إلى إجمالي المشتركين.
- وصول المنشورات: يحصل كل منشور على متوسط 0 مشاهدة. وخلال اليوم الأول يجمع عادةً 0 مشاهدة.
- التفاعلات والاستجابة: يتفاعل الجمهور بانتظام؛ متوسط التفاعلات لكل منشور يبلغ 0.
- الاهتمامات الموضوعية: يركز المحتوى على مواضيع رئيسية مثل redis, hashmap, linkedhashmap, индекс, фича.
📝 الوصف وسياسة المحتوى
يصف المؤلف القناة بأنها مساحة للتعبير عن الآراء الذاتية:
“Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк
🔥Тот самый курс по многопочке🔥
https://fillthegaps.ru/mt
Комплименты, вопросы, предложения: @utki_letyat”
بفضل وتيرة التحديث المرتفعة (أحدث البيانات بتاريخ 10 يونيو, 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 — أهم رؤى العام 
