Java: fill the gaps
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк 🔥Тот самый курс по многопочке🔥 https://fillthegaps.ru/mt Комплименты, вопросы, предложения: @utki_letyat
Mostrar más📈 Análisis del canal de Telegram Java: fill the gaps
El canal Java: fill the gaps (@java_fillthegaps) en el segmento lingüístico de Ruso es un actor destacado. Actualmente la comunidad reúne a 12 544 suscriptores, ocupando la posición 10 116 en la categoría Tecnologías y Aplicaciones y el puesto 52 822 en la región Rusia.
📊 Métricas de audiencia y dinámica
Desde su creación el невідомо, el proyecto ha mostrado un crecimiento acelerado, reuniendo a 12 544 suscriptores.
Según los últimos datos del 09 junio, 2026, el canal mantiene una actividad estable. En los últimos 30 días la variación de miembros fue de -48, y en las últimas 24 horas de -6, conservando un alto alcance.
- Estado de verificación: No verificado
- Tasa de interacción (ER): El promedio de interacción de la audiencia es 34.74%. Durante las primeras 24 horas tras publicar, el contenido suele obtener N/A% de reacciones respecto al total de suscriptores.
- Alcance de las publicaciones: Cada publicación recibe en promedio 0 visualizaciones. En el primer día suele acumular 0 visualizaciones.
- Reacciones e interacción: La audiencia responde de forma activa: el promedio de reacciones por publicación es 0.
- Intereses temáticos: El contenido se centra en temas clave como redis, hashmap, linkedhashmap, индекс, фича.
📝 Descripción y política de contenido
El autor describe el recurso como un espacio para expresar opiniones subjetivas:
“Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк
🔥Тот самый курс по многопочке🔥
https://fillthegaps.ru/mt
Комплименты, вопросы, предложения: @utki_letyat”
Gracias a la alta frecuencia de actualizaciones (últimos datos recibidos el 10 junio, 2026), el canal mantiene la vigencia y un amplio alcance. La analítica demuestra que la audiencia interactúa activamente con el contenido, lo que lo convierte en un punto de referencia dentro de la categoría Tecnologías y Aplicaciones.
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
¡Ya disponible! Investigación de Telegram 2025 — los principales insights del año 
