Библиотека Java разработчика
📚 Лайфхаки, приёмы и лучшие практики для Java-разработчиков. Всё, что ускорит код и прокачает навыки. Java, Spring, Maven, Hibernate. По всем вопросам @evgenycarter РКН clck.ru/3KoGeP
Показати більше📈 Аналітичний огляд Telegram-каналу Библиотека Java разработчика
Канал Библиотека Java разработчика (@bookjava) у мовному сегменті Російська є активним учасником. На даний момент спільнота об'єднує 10 121 підписників, посідаючи 11 760 місце в категорії Технології та додатки та 63 248 місце у регіоні Росія.
📊 Показники аудиторії та динаміка
З моменту свого створення невідомо, проект продемонстрував стрімке зростання, зібравши аудиторію у 10 121 підписників.
За останніми даними від 25 серпня, 2026, канал демонструє стабільну активність. Хоча за останні 30 днів спостерігається зміна кількості учасників на -74, а за останні 24 години на -6, загальне охоплення залишається високим.
- Статус верифікації: Не верифікований
- Рівень залученості (ER): Середній показник залученості аудиторії становить 7.20%. Протягом перших 24 годин після публікації контент зазвичай збирає 3.71% реакцій від загальної кількості підписників.
- Охоплення публікацій: В середньому кожен допис отримує 729 переглядів. Протягом першої доби публікація в середньому набирає 376 переглядів.
- Реакції та взаємодія: Аудиторія активно підтримує контент: середня кількість реакцій на один пост – 5.
- Тематичні інтереси: Контент зосереджений навколо ключових тем, таких як string, интерфейс, строка, boot, api.
📝 Опис та контентна політика
Автор описує ресурс як майданчик для висловлення суб'єктивної думки:
“📚 Лайфхаки, приёмы и лучшие практики для Java-разработчиков. Всё, что ускорит код и прокачает навыки. Java, Spring, Maven, Hibernate.
По всем вопросам @evgenycarter
РКН clck.ru/3KoGeP”
Завдяки високій частоті оновлень (останні дані отримано 26 серпня, 2026), канал підтримує актуальність та високий рівень охоплення публікацій. Аналітика показує, що аудиторія активно взаємодіє з контентом, що робить його важливою точкою впливу в категорії Технології та додатки.
flush() перед clear() — иначе словишь баг
Если ты используешь EntityManager вручную, например, при батчевой вставке или обновлении, то, скорее всего, пишешь что-то вроде:
for (int i = 0; i < entities.size(); i++) {
em.persist(entities.get(i));
if (i % 50 == 0) {
em.clear(); // чтобы не росла память
}
}
⚠️ Но тут баг: без flush() перед clear() ты теряешь все неперсистенные изменения! Hibernate просто забудет про них.
✅ Правильно так:
for (int i = 0; i < entities.size(); i++) {
em.persist(entities.get(i));
if (i % 50 == 0) {
em.flush();
em.clear();
}
}
📌 flush() гарантирует, что все накопленные изменения пойдут в базу, а clear() уже безопасно очищает контекст.
💡 Эта ошибка особенно коварна, потому что не всегда проявляется — зависит от настроек, триггеров в БД, кэша и т.д.
📲 Мы в MAX
👉@BookJava@Configuration
Есть распространённый анти-паттерн: ты используешь @Configuration и внутри создаёшь бины с @Bean, а в этих методах — инжектишь зависимости через параметры. Всё выглядит красиво и «по фэншую»... но только на первый взгляд.
@Configuration
public class MyConfig {
@Bean
public MyService myService(SomeDep dep) {
return new MyService(dep);
}
@Bean
public SomeDep someDep() {
return new SomeDep();
}
}
⚠️ Проблема: такие методы вызываются при старте контекста, и если SomeDep создаётся долго (например, подтягивает настройки из удалённого конфига, делает init-запрос в БД, или тянет секьюрити-контекст), это тормозит весь старт.
📌 Хуже всего, если ты не подозреваешь об этом: ведь @Bean -методы не видны как "инициализация", и кажется, что контекст тормозит "где-то ещё".
💡 Совет:
- Используй @Lazy в нужных местах, особенно если bean тяжёлый или редко используется.
- Разделяй конфигурацию: отдельно core, отдельно init-heavy.
- Не бойся отказаться от @Configuration в пользу @Component + @Service, если это упрощает понимание.
И главное — профилируй старт. spring-boot-starter-actuator + --debug могут открыть глаза.
📲 Мы в MAX
👉@BookJava
@Service
public class EmailService {
private final SmtpClient client = new SmtpClient(); // дорогая инициализация
}
⚠️ Проблема: SmtpClient создаётся сразу при старте приложения. Даже если EmailService ни разу не вызовется. Это не только waste of resources, но и может сломать запуск, если SmtpClient требует специфического окружения.
📌 Решение — ленивая инициализация, но не через старый добрый null-check, а красиво, безопасно и читаемо:
💡 Способ #1: Lazy<T> wrapper
@Component
public class EmailService {
private final Supplier<SmtpClient> client = Suppliers.memoize(SmtpClient::new);
public void sendEmail(...) {
client.get().send(...);
}
}
Можно и без Guava:
public class Lazy<T> {
private Supplier<T> supplier;
public Lazy(Supplier<T> supplier) {
this.supplier = () -> {
T value = supplier.get();
this.supplier = () -> value;
return value;
};
}
public T get() {
return supplier.get();
}
}
💡 Способ #2: через Spring
@Service
public class EmailService {
private final ObjectProvider<SmtpClient> client;
public EmailService(ObjectProvider<SmtpClient> client) {
this.client = client;
}
public void sendEmail(...) {
client.getObject().send(...);
}
}
🧵 Итог:
- Не инициализируй тяжёлые объекты зря.
- Используй Supplier, ObjectProvider или Lazy<T>.
- Это особенно критично для тестов, лямбд и кэширования.
📲 Мы в MAX
👉@BookJava@Transactional в Spring Boot 3+.
📌 Проблема — транзакция не работает, потому что метод вызывается изнутри того же класса.
Рассмотрим пример:
@Service
public class UserService {
@Transactional
public void createUser(User user) {
saveUser(user);
sendWelcomeEmail(user); // бросает исключение
}
public void saveUser(User user) {
userRepository.save(user);
}
}
💥 Если sendWelcomeEmail выбросит исключение — транзакция не откатится, потому что @Transactional работает через прокси. Вызов createUser() должен идти извне, чтобы Spring "знал", что нужно обернуть вызов в транзакцию.
✅ Решения:
1. Вынести transactional-метод в отдельный бин:
@Service
public class UserCreationService {
@Transactional
public void createUser(User user) {
// ...
}
}
2. Внедрить self-прокси:
@Autowired
private UserService self;
public void externalCaller(User user) {
self.createUser(user);
}
3. Использовать AopContext:
((UserService) AopContext.currentProxy()).createUser(user);
⚠️ Не забудь включить exposeProxy = true в @EnableAspectJAutoProxy.
💡 Современный подход — разделение ответственности: transactional-методы живут в отдельных сервисах, их проще тестировать и не возникает подобных ловушек.
📲 Мы в MAX
👉@BookJava🥇место – 1 000 000 рублей 🥈место – 700 000 рублей 🥉место – 500 000 рублей Награда за 4-8 места - 100 000 рублейПройди заочный онлайн-этап и получи путевку на очный этап турнира в Республику Татарстан! Перелет, питание, проживание - за счет организаторов. 🇷🇺 Подать заявку и узнать подробности 🇷🇺
@Transactional методов в Spring Boot
Знаете, что @Transactional по умолчанию оборачивает метод в прокси?
Это значит:
- Внутренние вызовы в том же классе не проходят через транзакцию;
- Каждый такой прокси — это AOP-магия, которую можно обойти ради производительности.
📌 Если вы точно знаете, что метод будет вызываться только извне, и вам не нужна прокси-обёртка — используйте @Transactional на уровне интерфейса и включите interface-based proxy.
@Configuration
@EnableTransactionManagement(proxyTargetClass = false) // JDK proxy
public class TransactionConfig {
}
public interface UserService {
@Transactional
void createUser(User user);
}
📉 Это немного снижает overhead, особенно в высоконагруженных сервисах, где сотни тысяч вызовов @Transactional-методов.
💡 Подходит, если:
- У вас слоистая архитектура;
- Транзакции нужны только снаружи;
- Вы не используете вызовы this.someMethod() внутри сервиса.
⚠️ Не забывайте:
- JDK Proxy работает только с интерфейсами;
- Если вызываете методы внутри того же класса — прокси не сработает (и транзакция не начнётся).
📊 Профильте. Иногда замена proxyTargetClass = true на false даёт +3-5% к throughput.
📲 Мы в MAX
👉@BookJavamain.
✔️ Как создавать и запускать потоки через Thread и Runnable.
✔️ Зачем нужен join и как дождаться завершения запущенных потоков.
✔️ Почему многопоточность может ускорить программу, а может добавить лишние накладные расходы.
✔️ Какие типовые проблемы возникают в многопоточном коде: гонки, starvation и deadlock.
🔗 Ссылка на регистрацию: https://vk.cc/cZD2sI
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576@SpringBootApplication, которая включает в себя @EnableAutoConfiguration.
💡 Решение — использовать spring.autoconfigure.exclude, чтобы явно выключить ненужное. Например:
# application.yaml
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
❗️Это особенно актуально:
- в микросервисах без UI (отключаем MVC)
- если datasource создаётся вручную
- в тестах и lightweight-режимах
👀 Как понять, что мешает?
1. Запусти приложение с флагом --debug или логгером org.springframework.boot.autoconfigure уровня DEBUG.
2. Посмотри, какие автоконфигурации "matched", но тебе не нужны.
3. Добавь их в exclude.
🧰 Альтернатива — использовать аннотацию @ImportAutoConfiguration с явным списком нужных автоконфигураций. Это тонкая настройка, идеально для библиотек и SDK.
⚠️ Не переусердствуй: отключение нужной конфигурации может привести к тихим багам. Лучше вырезать по одной и смотреть на эффект.
👉 Это один из способов сделать Spring Boot предсказуемым и быстрым, особенно в CI/CD или serverless-окружениях.
📲 Мы в MAX
👉@BookJava
spring:
main:
lazy-initialization: true
Все бины станут ленивыми — создадутся только при первом обращении. Это может сократить старт приложения на 30-60%!
✅ Локально (избирательно):
@Component
@Lazy
public class HeavyBean {
public HeavyBean() {
System.out.println("HeavyBean init...");
}
}
Или через @Lazy на зависимостях:
@Service
public class MyService {
public MyService(@Lazy HeavyBean heavyBean) {
this.heavyBean = heavyBean;
}
}
🧠 Когда использовать:
- В dev-окружении — чтобы ускорить локальный dev cycle.
- В CLI/Batch-приложениях, где используется 1-2 бина.
- Когда есть тяжёлые бины, не нужные на старте (например, интеграции, большие клиенты и т.п.).
⚠️ Осторожно:
- Если забыть @Lazy на зависимостях, Spring всё равно создаст бин.
- Некоторые бины должны быть загружены сразу (например, @Scheduled, @EventListener), иначе они не сработают.
📲 Мы в MAX
👉@BookJavaSimpleDateFormat или текущего пользователя в рамках запроса. Но с ним легко получить утечку памяти, особенно в thread pool'ах.
📌 Почему?
ThreadLocal-хранилище (Thread.threadLocals) живёт столько же, сколько поток. А потоки из пулов живут долго. Если ты забыл вызвать remove() — данные останутся в памяти навсегда.
Пример:
private static final ThreadLocal<UserContext> context = ThreadLocal.withInitial(UserContext::new);
public void handleRequest() {
try {
context.set(new UserContext("user123"));
// работа с контекстом
} finally {
context.remove(); // ОБЯЗАТЕЛЬНО!
}
}
⚠️ Что пойдёт не так без remove()?
- Поток из пула закончит обрабатывать запрос, но UserContext останется висеть в ThreadLocalMap этого потока.
- Если UserContext содержит ссылки на другие объекты (например, HttpSession, EntityManager и т.д.) — вся эта цепочка не будет GC-шиться.
- И так накапливается утечка.
💡 Советы:
- Всегда вызывай remove() в finally.
- Для Spring можно использовать RequestScope или @ControllerAdvice вместо ThreadLocal.
- Проверяй код сторонних библиотек, если они используют ThreadLocal, особенно в фильтрах и интерсепторах.
📲 Мы в MAX
👉@BookJava
-Dspring.context.cache.applicationContext=true
💡 Что это такое?
Это встроенный механизм кэширования ApplicationContext, появившийся в Spring Boot 3.2.
Он сохраняет результат построения контекста и позволяет повторно использовать его между запусками, особенно в тестах и development-сценариях.
⚙️ Как работает:
- При первом запуске контекст билдится как обычно.
- Затем сериализуется и сохраняется на диск.
- При следующем запуске он подгружается из кеша (если не изменился), что даёт ускорение в 2-3 раза и больше.
🚀 Отлично подходит для:
- Тестов (@SpringBootTest);
- Dev tools и локального запуска;
- Разработки больших монолитов.
⚠️ Не влияет на продакшн (там кеш не используется по умолчанию)
⚠️ Не поддерживает все конфигурации (например, динамические настройки могут инвалидировать кеш)
Простой флаг — а экономит кучу времени каждый день.
📲 Мы в MAX
👉@BookJava@Transactional в Spring
Сейчас покажу вам один распространённый анти-паттерн, который легко пропустить — вызов транзакционного метода внутри того же класса.
📌 Пример:
@Service
public class UserService {
@Transactional
public void registerUser(UserDto dto) {
saveUser(dto);
}
@Transactional
public void saveUser(UserDto dto) {
// сохранение пользователя
}
}
💥 Проблема:
Spring не применит транзакцию к saveUser(), потому что вызов происходит внутри одного и того же бина — минуя прокси.
Spring AOP работает через прокси, и @Transactional "срабатывает", только если метод вызывается извне, через прокси-объект.
⚠️ Это может привести к очень странным багам: вы думаете, что транзакция есть, а её нет.
💡 Как исправить:
1. Вынести метод в отдельный бин:
@Service
public class UserSaver {
@Transactional
public void save(UserDto dto) {
// сохраняем
}
}
@Service
public class UserService {
private final UserSaver saver;
public UserService(UserSaver saver) {
this.saver = saver;
}
public void registerUser(UserDto dto) {
saver.save(dto);
}
}
2. Или использовать TransactionTemplate вручную.
✅ Всегда проверяйте, как вызываются методы с @Transactional. Особенно при рефакторинге!
📲 Мы в MAX
👉@BookJava@Value vs @ConfigurationProperties — кого выбрать?
Часто вижу, как даже опытные разработчики по привычке используют @Value для инъекции конфигурации:
@Value("${app.timeout}")
private Duration timeout;
⚠️ Но с ростом приложения @Value становится хрупким и неудобным.
📌 Лучший подход — использовать @ConfigurationProperties:
@ConfigurationProperties(prefix = "app")
public record AppProperties(Duration timeout, String apiKey) {}
@Bean
@ConfigurationPropertiesBinding
public AppProperties appProperties() {
return new AppProperties();
}
✅ Преимущества @ConfigurationProperties:
- 💡 Группирует настройки логически
- 🔍 Работает с валидацией (@Validated, @NotNull, и т.д.)
- 📚 Отлично поддерживается IDE (автокомплит, рефакторинг)
- 🔧 Удобно тестировать и мокать
🆕 Начиная с Spring Boot 2.2+, можно использовать record-классы и просто зарегистрировать бин через @EnableConfigurationProperties:
@Configuration
@EnableConfigurationProperties(AppProperties.class)
public class AppConfig {}
Так что, если у вас в проекте до сих пор десятки @Value — самое время навести порядок.
📲 Мы в MAX
👉@BookJava@Value в Spring — это ловушка, если вы используете списки или map'ы
Многие знают, что можно заинжектить список строк из application.yml вот так:
app:
langs:
- en
- fr
- de
@Value("${app.langs}")
private List<String> langs;
Но знаете, что вы получите?
⚠️ ОШИБКУ. @Value не умеет парсить YAML-массивы. Он ожидает строку, и даже с CSV-строкой (en,fr,de) — всё не так очевидно: Spring не применяет ConversionService для списков.
📌 Решение — использовать @ConfigurationProperties:
app:
langs:
- en
- fr
- de
@ConfigurationProperties(prefix = "app")
@Component
public class AppProps {
private List<String> langs;
// геттеры/сеттеры
}
💡 Профит:
- работает с List, Map, вложенными объектами;
- валидация через @Validated и @NotEmpty;
- легко покрыть тестами;
- меньше магии.
⚠️ @Value хорош для простых скаляров. Всё остальное — через @ConfigurationProperties.
📲 Мы в MAX
👉@BookJavaOptional — это не замена null везде и всегда
Привет! Сегодня хочу поделиться одной из часто встречающихся ошибок при использовании Optional в Java.
Многие разработчики, особенно начинающие, начинают использовать Optional везде, где может быть null, думая, что это автоматически делает код "безопасным". Но так ли это?
📌 Ключевая идея Optional — сигнализировать о возможном отсутствии значения в результате вызова метода.
А не заменять все поля и параметры на Optional.
Примеры плохой практики:
public class User {
private Optional<String> name; // ❌ Не нужно так делать
}
Почему это плохо:
- Увеличивается сложность сериализации (особенно с Jackson, GSON).
- Не соответствует архитектурной задумке: Optional — это не контейнер для полей.
- Проблемы с JPA (Hibernate не дружит с Optional-полями).
- Понижается читаемость кода.
💡 Лучше использовать Optional вот так:
public Optional<User> findUserById(Long id) {
// Возвращаем Optional, потому что пользователь может не существовать
}
То есть Optional — это про контракт на метод, а не про хранение данных.
Если кратко:
- ✅ Используй Optional в сигнатурах методов, когда результат может отсутствовать.
- ❌ Не используй Optional в полях и параметрах конструктора.
А ты как используешь Optional в проектах? Был ли опыт с его неправильным применением? Пиши в комментах👇
📲 Мы в MAX
👉@BookJava@Transactional в Spring
✔️Почему важны proxy и вызов метода через Spring Bean
✔️Как работают propagation-режимы на примере REQUIRED и REQUIRES_NEW
✔️Когда происходит rollback и почему checked exceptions не всегда откатывают транзакцию
✔️Типовые ошибки при работе с транзакциями в сервисном слое
Урок будет полезен Java/Kotlin-разработчикам, которые уже пишут приложения на Spring или начинают использовать Spring в реальных backend-проектах и хотят лучше понимать поведение транзакций.
🔗 Ссылка на регистрацию: https://vk.cc/cZ9GGY
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576boolean в Java — это не просто true или false. А за этим простым типом скрывается интересный нюанс, особенно если ты задумываешься об экономии памяти.
В Java нет отдельного типа, который занимает всего 1 бит. Хотя логично было бы ожидать, что boolean — это один бит (true/false), на самом деле в памяти он занимает 1 байт (а иногда и больше, в зависимости от структуры объекта).
Пример:
public class Flags {
boolean flag1;
boolean flag2;
boolean flag3;
}
Ты думаешь — три бита. Но JVM выравнивает поля, и из-за этого объект может занимать 16 байт или больше, в зависимости от архитектуры. Почему так?
📌 Причина:
JVM упрощает модель памяти ради производительности — доступ к байтам быстрее, чем к битам. Нет битовых сдвигов, масок и лишней логики.
💡 Что делать, если хочется сэкономить память?
Используй BitSet:
BitSet flags = new BitSet(3);
flags.set(0, true);
Это уже реальная битовая структура. Отличный выбор, если у тебя десятки или сотни логических флагов.
А ты знал об этом нюансе хранения boolean? Пиши в комментариях, сталкивался ли с перерасходом памяти из-за простых типов.
📲 Мы в MAX
👉@BookJava