📚 Лайфхаки, приёмы и лучшие практики для Java-разработчиков. Всё, что ускорит код и прокачает навыки. Java, Spring, Maven, Hibernate. По всем вопросам @evgenycarter РКН clck.ru/3KoGeP
📌 picocli — это современная библиотека для создания CLI-приложений на Java. Она упрощает разработку командных интерфейсов, обеспечивая:
* Автоматическую генерацию --help
и --version
* Поддержку подкоманд (как в git commit
, git push
)
* Аргументы, параметры, опции с короткими и длинными флагами (-v
, --verbose
)
* Интеграцию с GraalVM (подходит для нативной компиляции)
* Поддержку аннотаций (аннотируй POJO — и готово!)
* Автоматическую валидацию аргументов
* Цветной вывод и гибкое форматирование
* Интерактивный режим и автодополнение
Проект активно развивается, полностью документирован и используется в сотнях продакшн-проектов. Если ты ищешь мощную и простую в использовании CLI-библиотеку на Java — picocli отличный выбор.
https://github.com/remkop/picocli
👉@BookJava
Double-brace инициализация в Java — это идиома, которая используется для инициализации коллекций (и иногда других объектов) в краткой форме. Она выглядит как две открывающие фигурные скобки подряд {{
и имеет специфическое поведение. Пример:
import java.util.*;
List<String> list = new ArrayList<String>() {{
add("one");
add("two");
add("three");
}};
new ArrayList<String>() { ... }
ArrayList
.
{{ ... }}
add()
).final
, например:
private static final Set<String> set = new HashSet<>() {{
add("A");
add("B");
}};
Stream
и Collectors
):
List<String> list = Stream.of("one", "two", "three")
.collect(Collectors.toList());
public static List<String> createList() {
List<String> list = new ArrayList<>();
list.add("one");
list.add("two");
return list;
}
List<String> list = List.of("one", "two", "three");
Set<String> set = Set.of("A", "B");
🧠@Value
vs @ConfigurationProperties
— не выбирай наобум
Оба способа хороши для конфигурации, но используют их по-разному. И если ты всё ещё везде пихаешь @Value
, держи краткий гайд, когда лучше что:
📌 @Value
— просто, но не гибко:
@Value("${my.prop}")
private String value;
TestPropertySource
) @ConfigurationProperties
— сила и масштаб:
@ConfigurationProperties(prefix = "app.feature")
public class FeatureProperties {
private boolean enabled;
private List<String> items;
}
@EnableConfigurationProperties
или аннотируй как @Component
@Validated
)/actuator/configprops
)@Value
внутрь @ConfigurationProperties
— это антипаттерн.@Value
норм. Но как только появляется структура, коллекции, логика — всегда используй @ConfigurationProperties
.🔥 Java и производительность — новая тема от онлайн-конференции Podlodka Java Crew
С 26 по 30 мая вас ждет тематическая неделя, посвящённая продвинутым практикам оптимизации Java-приложений.
В программе:
— Доклад Владимира Плизги (Tibbo System) о подходах к профилированию и инструментах, которые действительно работают;
— Воркшоп по JMH от Григория Кошелева (Контур) — научитесь писать микробенчмарки правильно;
— Круглый стол с Антоном Курако (Т-Банк) и Михаилом Поливахой (Spring АйО) — сравнение Spring, Micronaut, Quarkus и Kora через призму производительности;
— Опыт команды НСПК по нагрузочному тестированию в бою — расскажет Павел Митин.
А ещё — JFR, корутины, Kubernetes и десятки инсайтов из продакшена.
🎯 Неделя для тех, кто держит перформанс под контролем.
🔗 Подключайся: podlodka.io/javacrew
👩💻 SpELые приложения на Spring
Присоединяйтесь к открытому уроку, узнайте, как динамически выражать и обрабатывать данные в Spring-приложениях.
🗓 21 мая в 19:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Разработчик на Spring Framework».
О чём поговорим:
✔️Разоберем, для чего нужен SpEL.
✔️Рассмотрим, в каких проектах Spring его можно встретить.
Кому будет интересно:
Spring-разработчикам, Java-бэкенд-инженерам, архитекторам ПО, IT-специалистам и студентам, заинтересованным в технологиях Spring.
В результате урока:
Узнаете, для чего нужен SpEL и где его можно применять.
🔗 Ссылка на регистрацию: https://vk.cc/cLY8ET
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
🧠 Внезапная ловушка с @Transactional
и self-invocation
в Spring
Вы знали, что вызов метода с @Transactional
внутри того же класса не активирует транзакцию? Это один из самых частых подводных камней.
📌 Почему так происходит?
Spring AOP работает через прокси. Когда вы вызываете метод this.someTransactionalMethod()
, вы обходите прокси и вызываете метод напрямую — без обёртки, которая включает транзакцию.
Пример:
@Service
public class UserService {
public void createUser() {
// транзакция не начнётся
this.saveUser();
}
@Transactional
public void saveUser() {
// ожидаем, что тут будет транзакция, но её нет
}
}
LazyInitializationException
, и т.д.
@Service
public class UserSaver {
@Transactional
public void saveUser() { ... }
}
AopContext
:
public void createUser() {
((UserService) AopContext.currentProxy()).saveUser();
}
@EnableAspectJAutoProxy(exposeProxy = true)
AopContext
без крайней необходимости. Предпочтительнее делегировать логику в отдельный бин — это чище и легче тестируется.🧠 Spring Boot: ленивые зависимости через ObjectProvider
Иногда сервису не нужно всегда инжектить другую зависимость при старте — только иногда по ходу работы. Но @Autowired
всё равно тянет её сразу, даже если она вам пока не нужна. Это бьёт по времени старта и может вызвать циклические зависимости.
💡 Решение: использовать ObjectProvider<T>
.
Пример:
@Service
public class NotificationService {
private final ObjectProvider<EmailSender> emailSenderProvider;
public NotificationService(ObjectProvider<EmailSender> emailSenderProvider) {
this.emailSenderProvider = emailSenderProvider;
}
public void sendEmailIfEnabled(String to, String body) {
if (featureEnabled()) {
EmailSender sender = emailSenderProvider.getIfAvailable();
if (sender != null) {
sender.send(to, body);
}
}
}
}
ObjectProvider
:getIfAvailable()
/ ifAvailable(...)
);stream()
— для коллекций бинов.💡 Чем опасен @Scheduled(fixedRate)
без @Transactional
?
Расписание в Spring через @Scheduled
— удобный способ запускать задачи по таймеру. Но часто разработчики забывают про транзакции, особенно с fixedRate
, и попадают в ловушку.
📌 Пример проблемы:
@Scheduled(fixedRate = 10_000)
public void cleanUp() {
List<Job> jobs = jobRepository.findAllByStatus(PENDING);
jobs.forEach(job -> {
job.setStatus(PROCESSING);
jobRepository.save(job);
});
}
PENDING
-записи.
@Transactional
@Scheduled(fixedRate = 10_000)
public void cleanUp() {
List<Job> jobs = jobRepository.findAllByStatusForUpdate(PENDING); // SELECT ... FOR UPDATE
jobs.forEach(job -> {
job.setStatus(PROCESSING);
jobRepository.save(job);
});
}
@Scheduled(fixedDelay)
— он ждёт завершения предыдущего запуска. Это безопаснее по умолчанию.@Scheduled
на:🔧 Maven vs. Gradle: что выбрать разработчику?
Когда речь заходит о сборке Java-проектов, выбор обычно падает на два главных инструмента: Maven и Gradle. Оба давно стали стандартом индустрии, но каждый имеет свои особенности. Разберёмся, что выбрать 👇
☕ Maven — проверенная классика
✅ Строгая структура: легче читать и сопровождать
✅ Надёжность и предсказуемость сборки
✅ Большое комьюнити и множество плагинов
⚠️ XML-конфигурация громоздкая
⚠️ Медленнее по сравнению с Gradle
⚡ Gradle — гибкость и скорость
✅ Поддержка Kotlin и Groovy DSL
✅ Инкрементальные сборки и кэширование → быстрее
✅ Более гибкий подход к настройке
⚠️ Порог входа выше
⚠️ Иногда сложно отлаживать конфигурацию
💡 Вывод:
* 🔹 Выбирай Maven, если важны стабильность, простота и читаемость.
* 🔹 Выбирай Gradle, если хочешь максимум производительности и гибкости.
🎯 В крупных проектах Gradle становится всё популярнее, особенно при использовании Kotlin. Но в enterprise-среде Maven по-прежнему правит бал.
А ты чем пользуешься? Делись опытом в комментах ⬇️
👉@BookJava
🚀 Подборка Telegram каналов для программистов
Системное администрирование, DevOps 📌
/channel/bash_srv Bash Советы
/channel/win_sysadmin Системный Администратор Windows
/channel/sysadmin_girl Девочка Сисадмин
/channel/srv_admin_linux Админские угодья
/channel/linux_srv Типичный Сисадмин
/channel/devopslib Библиотека девопса | DevOps, SRE, Sysadmin
/channel/linux_odmin Linux: Системный администратор
/channel/devops_star DevOps Star (Звезда Девопса)
/channel/i_linux Системный администратор
/channel/linuxchmod Linux
/channel/sys_adminos Системный Администратор
/channel/tipsysdmin Типичный Сисадмин (фото железа, было/стало)
/channel/sysadminof Книги для админов, полезные материалы
/channel/i_odmin Все для системного администратора
/channel/i_odmin_book Библиотека Системного Администратора
/channel/i_odmin_chat Чат системных администраторов
/channel/i_DevOps DevOps: Пишем о Docker, Kubernetes и др.
/channel/sysadminoff Новости Линукс Linux
1C разработка 📌
/channel/odin1C_rus Cтатьи, курсы, советы, шаблоны кода 1С
/channel/DevLab1C 1С:Предприятие 8
/channel/razrab_1C 1C Разработчик
/channel/buh1C_prog 1C Программист | Бухгалтерия и Учёт
/channel/rabota1C_rus Вакансии для программистов 1С
Программирование C++📌
/channel/cpp_lib Библиотека C/C++ разработчика
/channel/cpp_knigi Книги для программистов C/C++
/channel/cpp_geek Учим C/C++ на примерах
Программирование Python 📌
/channel/pythonofff Python академия.
/channel/BookPython Библиотека Python разработчика
/channel/python_real Python подборки на русском и английском
/channel/python_360 Книги по Python
Java разработка 📌
/channel/BookJava Библиотека Java разработчика
/channel/java_360 Книги по Java Rus
/channel/java_geek Учим Java на примерах
GitHub Сообщество 📌
/channel/Githublib Интересное из GitHub
Базы данных (Data Base) 📌
/channel/database_info Все про базы данных
Мобильная разработка: iOS, Android 📌
/channel/developer_mobila Мобильная разработка
/channel/kotlin_lib Подборки полезного материала по Kotlin
Фронтенд разработка 📌
/channel/frontend_1 Подборки для frontend разработчиков
/channel/frontend_sovet Frontend советы, примеры и практика!
/channel/React_lib Подборки по React js и все что с ним связано
Разработка игр 📌
/channel/game_devv Все о разработке игр
Библиотеки 📌
/channel/book_for_dev Книги для программистов Rus
/channel/programmist_of Книги по программированию
/channel/proglb Библиотека программиста
/channel/bfbook Книги для программистов
БигДата, машинное обучение 📌
/channel/bigdata_1 Big Data, Machine Learning
Программирование 📌
/channel/bookflow Лекции, видеоуроки, доклады с IT конференций
/channel/rust_lib Полезный контент по программированию на Rust
/channel/golang_lib Библиотека Go (Golang) разработчика
/channel/itmozg Программисты, дизайнеры, новости из мира IT
/channel/php_lib Библиотека PHP программиста 👨🏼💻👩💻
/channel/nodejs_lib Подборки по Node js и все что с ним связано
/channel/ruby_lib Библиотека Ruby программиста
/channel/lifeproger Жизнь программиста. Авторский канал.
QA, тестирование 📌
/channel/testlab_qa Библиотека тестировщика
Шутки программистов 📌
/channel/itumor Шутки программистов
Защита, взлом, безопасность 📌
/channel/thehaking Канал о кибербезопасности
/channel/xakep_2 Хакер Free
Книги, статьи для дизайнеров 📌
/channel/ux_web Статьи, книги для дизайнеров
Математика 📌
/channel/Pomatematike Канал по математике
/channel/phis_mat Обучающие видео, книги по Физике и Математике
/channel/matgeoru Математика | Геометрия | Логика
Excel лайфхак📌
/channel/Excel_lifehack
/channel/mir_teh Мир технологий (Technology World)
Вакансии 📌
/channel/sysadmin_rabota Системный Администратор
/channel/progjob Вакансии в IT
👩💻 Открытый урок «Облака и Mongo DB Atlas»
🗓 12 мая в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Разработчик на Spring Framework»
Погружаемся в мир облачных технологий и учимся разворачивать кластер MongoDB бесплатно.
Программа вебинара:
✔️ Облачные технологии: что такое облака, какие бывают уровни (IaaS, PaaS, SaaS) с простыми аналогиями для понимания.
✔️ Практическая демонстрация: как создать кластер MongoDB в Atlas и подключиться к нему.
Вебинар будет полезен:
Разработчикам, начинающим backend-программистам, студентам IT-курсов и всем, кто хочет разобраться в облачных сервисах.
В результате вебинара вы:
Научитесь создавать кластеры MongoDB в облаке.
🔗 Ссылка на регистрацию: https://vk.cc/cLJqe0
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
🧠 Spring Boot: как НЕ попасть в ловушку с @Scheduled
и многопоточностью
Когда используете @Scheduled
для периодических задач в Spring Boot, важно понимать: по умолчанию все задачи выполняются в одном потоке.
@Scheduled(fixedRate = 5000)
public void syncData() {
// долгая операция
}
@Configuration
@EnableScheduling
public class SchedulerConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar registrar) {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5); // количество параллельных задач
scheduler.setThreadNamePrefix("scheduled-task-");
scheduler.initialize();
registrar.setTaskScheduler(scheduler);
}
}
@Scheduled
- методы будут использовать пул потоков, а не один.@Scheduled
выполняет реальные задачи, а не просто println
.💡 Не делай этого в @PostConstruct
— особенно в проде
Сейчас расскажу, почему инициализировать важную бизнес-логику в @PostConstruct
— плохая идея.
Типичный пример:
@Component
public class CacheLoader {
private final SomeService service;
public CacheLoader(SomeService service) {
this.service = service;
}
@PostConstruct
public void init() {
service.loadDataIntoCache(); // ⚠️ обращение к БД
}
}
@PostConstruct
вызывается до того, как приложение полностью поднялось.ApplicationListener
:
@Component
public class CacheLoader implements ApplicationListener<ApplicationReadyEvent> {
private final SomeService service;
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
service.loadDataIntoCache(); // 👍 вызывается только после старта
}
}
@EventListener
:
@EventListener(ApplicationReadyEvent.class)
public void onReady() {
// безопасно загружаем данные
}
@PostConstruct
— только для простой инициализации бинов.@EventListener(ApplicationReadyEvent.class)
.📊 Данные — это топливо цифрового бизнеса. Однако передача данных между системами по-прежнему требует времени, ресурсов и нервов. Kafka Connect меняет правила игры: минимум кода, максимум автоматизации. 🔄
📅 12 мая в 18:00 МСК на открытом вебинаре от OTUS:
— Разберём архитектуру Kafka Connect;
— Запустим коннекторы для БД и файловых систем;
— Научим масштабировать и отлаживать интеграции;
— Покажем, как избежать типовых ошибок.
👤 Спикер: Валентин Шилин — старший программист/аналитик данных в зарубежной компании.
Этот вебинар будет полезен разработчикам, инженерам данных, архитекторам и всем, кто работает с интеграциями. 🌐
Открытый урок проходит в преддверии старта курса «Apache Kafka». Все участники получат скидку на обучение.
🔗 Регистрируйтесь прямо сейчас: https://vk.cc/cLEosU
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
🧠 ExecutorService vs Virtual Threads: подводный камень с shutdownNow()
Java 21 принес Virtual Threads (Preview), и всё чаще появляется соблазн запускать их через ExecutorService
. Но вот что важно помнить:
📌 shutdownNow()
— опасная ловушка при работе с виртуальными потоками.
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
Future<?> future = executor.submit(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println("Interrupted!");
}
});
executor.shutdownNow(); // ❗️Ничего не произойдёт
shutdownNow()
не может прервать виртуальные потоки, если они запущены через Executors.newVirtualThreadPerTaskExecutor()
. Он лишь помечает пул как завершённый и возвращает список задач, которые ещё не стартовали.Thread.interrupt()
не работает, потому что у виртуальных потоков отсутствует связь с ThreadGroup
, к которому привязан shutdownNow.shutdownNow()
"остановит всё" — вы можете получить утечку задач или зависания.Future.cancel(true)
— он вызывает interrupt()
на конкретной задаче.Thread.interrupted()
внутри задачи.Future
задач и отменяйте вручную.👩💻 Как использовать SpEL в приложениях на Spring?
Присоединяйтесь к открытому уроку «SpELые приложения на Spring» и узнайте, как динамически выражать и обрабатывать данные в Spring-приложениях.
SpEL (Spring Expression Language) — это мощный инструмент для внедрения динамических выражений, который широко применяется в проектировании приложений на Spring.
🗓 21 мая в 19:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Разработчик на Spring Framework».
Что вас ждёт:
✔️ Погружение в SpEL и его возможности.
✔️ Применение SpEL в реальных Spring-проектах.
✔️ Понимание, как Spring использует SpEL для обработки и динамических вычислений.
Урок для Spring-разработчиков, Java-бэкенд-инженеров и архитекторов ПО.
🔗 Ссылка на регистрацию: https://vk.cc/cM6W9C
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
🚀 Открой для себя идеальный путь к лидерству с карьерным тестом от ОЭЗ «Алабуга»! 🌟
Мечтаете о карьере в крупной компании, где ваш потенциал раскроется на полную? Наш тест поможет вам определить вашу уникальную лидерскую роль. Может быть, именно вы станете тем лидером, который выведет команду на новый уровень?
После прохождения теста вы можете заполнить заявку и получить приглашение на эксклюзивную лидерскую программу. Участие в программе открывает реальные перспективы трудоустройства в ОЭЗ «Алабуга», предоставляя шанс начать путь к профессиональному признанию.
Сделайте первый шаг к своему будущему сегодня! Пройдите тест, подайте заявку и начните строить свою карьеру вместе с нами. 🎯
🧠 Трюк с @EventListener
в Spring — убираем лишний @TransactionalEventListener
Когда тебе нужно обрабатывать события в рамках транзакции, мы часто пишем:
@TransactionalEventListener
public void handleEvent(MyEvent event) {
// ...
}
@TransactionalEventListener
по умолчанию срабатывает после коммита. Иногда это не очевидно и вызывает баги, особенно если ожидаешь, что событие обработается внутри транзакции.@EventListener
, но вместе с TransactionSynchronizationManager
.
@Component
public class MyEventHandler {
@EventListener
public void handle(MyEvent event) {
if (TransactionSynchronizationManager.isActualTransactionActive()) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
// обработка события после коммита
}
}
);
} else {
// fallback: нет активной транзакции — выполняем сразу
}
}
}
🧠 Как не словить LazyInitializationException
в Spring Boot + Hibernate
Одна из самых частых ошибок при работе с JPA:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection
LAZY
) обращается к БД вне транзакции — например, в слое контроллера или после закрытия Session
.@Transactional
в сервисе@Transactional
:
@Transactional
public UserDto getUser(Long id) {
User user = userRepository.findById(id)
.orElseThrow();
// OK: коллекция friends будет инициализирована в транзакции
return new UserDto(user.getName(), user.getFriends());
}
@Transactional
в контроллерах — это плохая практика.JOIN FETCH
:
@Query("SELECT u FROM User u LEFT JOIN FETCH u.friends WHERE u.id = :id")
Optional<User> findByIdWithFriends(@Param("id") Long id);
@Query("""
SELECT new com.example.UserDto(u.name, f.name)
FROM User u
LEFT JOIN u.friends f
WHERE u.id = :id
""")
List<UserDto> findUserWithFriendNames(@Param("id") Long id);
fetch join
— ваши лучшие друзья, если нужен контроль и производительность.❓Хотите овладеть Spark на профессиональном уровне?
Приглашаем дата-инженеров 26 мая в 20:00 на открытый урок «Spark в Kubernetes».
На занятии мы рассмотрим особенности и варианты запуска Spark в Kubernetes.
🔊 Вебинар проведет Вадим Заигрин, Team Lead команд инженеров данных на разных проектах.
Продолжить освоение инструментов дата-инжиниринга вы сможете на онлайн-курсе «Spark Developer» от OTUS.
➡️ Ссылка для регистрации: https://vk.cc/cLWZuO
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Возможности Kotlin для создания DSL на примере JsonBuilder
Приглашаем на открытый урок.
🗓 22 мая в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Kotlin Backend Developer. Professional».
Что вас ждёт:
✔️ рассмотрим общую теорию о DSL: назначение, особенности, практика;
✔️ попрактикуемся в создании DSL на примере JsonBuilder;
✔️ рассмотрим возможности Kotlin, полезные для создания DSL.
🎁 Всем участникам вебинара дарим промокод, который дает скидку на обучение - Kotlin5
👉 Регистрация на вебинар: https://vk.cc/cLUdvA
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
🔍Тестовое собеседование на Middle Java-разработчика завтра
14 мая(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Java-разработчика.
Как это будет:
📂 Константин Лесняк, Java-разработчик с большим опытом проведения собесов, будет задавать реальные вопросы и задачи разработчику-добровольцу
📂 Костя будет комментировать каждый ответ респондента, чтобы дать понять чего от вас ожидает собеседующий на интервью
📂 В конце можно будет задать любой вопрос Косте
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot
Реклама. ООО "ШОРТКАТ", ИНН: 9731139396, erid: 2VtzqxZCqXi
🧠 Трюк с @Configuration
и @ComponentScan
: как не словить баг при миграции на Spring Boot 3+
Когда вы выносите конфигурацию в отдельный модуль или создаёте библиотеку с @Configuration
-классами — не забывайте:
📌 Spring Boot 3+ по умолчанию НЕ сканирует пакеты вне стартового (main
).
Пример:
// Внутри библиотеки
@Configuration
public class MyLibConfig {
@Bean
public MyService myService() {
return new MyService();
}
}
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
MyService
не создаётся! Почему?@ComponentScan
по умолчанию сканирует ТОЛЬКО package текущего класса и ниже.
@SpringBootApplication
@Import(MyLibConfig.class)
public class App { ... }
@AutoConfiguration
и подключить через spring.factories
или META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
— актуально для библиотек.MyLibConfig
в подпакет стартового класса (не всегда возможно или удобно).NoSuchBeanDefinitionException
в рантайме.🧠 Неочевидный performance-трюк с @Transactional(readOnly = true)
Многие используют @Transactional(readOnly = true)
просто по инерции. Но вы знали, что в Spring это влияет не только на семантику, но и на производительность?
📌 Что делает readOnly = true
:
* Подсказывает Hibernate
, что внутри транзакции не будет изменений сущностей.
* Это позволяет избежать затрат на грязную проверку (dirty checking).
* Не создаётся snapshot состояний сущностей → меньше памяти и операций.
💡 Пример:
@Transactional(readOnly = true)
public List<User> findActiveUsers() {
return userRepository.findByActiveTrue();
}
@Transactional(readOnly = true)
не как декор, а как инструмент для оптимизации.🧠 Spring Boot: правильно логируем @ExceptionHandler
Сейчас покажу простой, но часто упускаемый момент при обработке ошибок в Spring Boot.
Если у вас есть глобальный @ExceptionHandler
, убедитесь, что вы не теряете stacktrace при логировании.
❌ Плохо:
@ExceptionHandler(MyException.class)
public ResponseEntity<String> handle(MyException ex) {
log.error("MyException occurred: {}", ex.getMessage()); // stacktrace теряется!
return ResponseEntity.status(500).body("Error");
}
@ExceptionHandler(MyException.class)
public ResponseEntity<String> handle(MyException ex) {
log.error("MyException occurred", ex); // stacktrace будет видно в логе
return ResponseEntity.status(500).body("Error");
}
log.error(String, Throwable)
— правильный способ логировать исключения. Это позволяет:getCause()
);log.error("message: {}", ex.getMessage())
, вы теряете почти всю полезную информацию об ошибке.@ExceptionHandler
под каждую категорию исключений.👩💻 Java-разработчик? Хотите ускорить разработку и избавиться от рутины?
На открытом уроке «Kotlin Multiplatform: лайфхак для Java-разработчиков» от OTUS мы покажем, как с помощью Kotlin Multiplatform (KMP) использовать один и тот же код для различных проектов — от Android и iOS до backend-систем.
Что вас ждёт:
✔️ Узнаете, как интегрировать Kotlin Multiplatform в Java-проекты и настроить совместимость с существующим стеком.
✔️ Сможете избежать дублирования логики и сэкономите время на поддержке разных модулей для разных платформ.
✔️ Получите практические знания, как создавать общий код для JVM, Android и iOS.
Открытый урок проходит в преддверии старта курса «Kotlin Backend Developer. Professional».
🎁 Всем участникам вебинара дарим промокод, который дает скидку на обучение - Kotlin5
➡️ Встречаемся 14 мая в 20:00 МСК — присоединяйтесь и узнайте, как сэкономить время и силы с Kotlin Multiplatform: https://vk.cc/cLEsDR
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
🧠Понимание архитектуры памяти JVM
В этой статье мы углубимся в архитектуру памяти JVM (Java Virtual Machine), исследуя, как она управляет памятью, чтобы эффективно выполнять Java-программы.
🔹Что такое JVM?
JVM — это абстрактная вычислительная машина, которая позволяет запускать Java-программы, независимо от аппаратного или операционного окружения. Она отвечает за интерпретацию байт-кода Java и выполнение его на хост-машине.
Архитектура памяти JVM
Архитектура памяти JVM делится на несколько компонентов, каждый из которых играет ключевую роль в управлении памятью во время выполнения программы. Основные области памяти JVM:
🔹1. Heap (Куча)
* Куча — это основная область памяти, используемая для хранения объектов.
* Это самая большая часть памяти и управляется сборщиком мусора (Garbage Collector).
* Объекты, созданные во время выполнения программы, размещаются в куче.
Куча делится на:
* Young Generation (Молодое поколение):
* Хранит недавно созданные объекты.
* Подразделяется на Eden Space и два Survivor Space (S0 и S1).
* Old Generation (Старое поколение):
* Хранит долго живущие объекты, которые пережили несколько сборок мусора в молодом поколении.
🔹2. Stack (Стек)
* У каждой нити (потока) есть свой собственный стек.
* Хранит фреймы методов, включая локальные переменные, операнды и возвращаемые адреса.
* Память в стеке освобождается, когда метод завершает выполнение.
🔹3. Method Area (Область методов)
* Используется для хранения метаданных классов, таких как:
* Информация о типах,
* Константы,
* Статические переменные,
* Методы и их байт-код.
* В некоторых реализациях JVM эта область называется Metaspace (начиная с Java 8).
🔹4. Program Counter (PC Register)
* Каждая нить имеет собственный регистр PC.
* Хранит адрес текущей выполняемой инструкции.
🔹5. Native Method Stack (Стек нативных методов)
* Используется для выполнения нативных (не-Java) методов, написанных на других языках, таких как C или C++.
Управление памятью и сборка мусора
JVM использует автоматическую сборку мусора для управления кучей. Различные алгоритмы (например, Mark and Sweep, Generational GC) используются для отслеживания и удаления неиспользуемых объектов. Это повышает производительность и предотвращает утечки памяти.
https://thedeveloperstory.com/2025/04/06/understanding-jvm-memory-architecture/
👉@BookJava
🧠 Нюанс с Optional.map()
и методами, возвращающими Optional
Многие Java-разработчики на автомате пишут что-то вроде:
Optional<User> user = findUserById(id); // возвращает Optional<User>
Optional<String> email = user.map(User::getEmail); // getEmail тоже возвращает Optional<String>
map()
в Optional
не "разворачивает" вложенные Optional
. В этом примере email
будет типа Optional<Optional<String>>
, что почти всегда нежелательно.flatMap()
:
Optional<String> email = user.flatMap(User::getEmail);
flatMap()
позволяет избежать "двойной обёртки", если метод внутри map()
уже возвращает Optional
.Stream.map()
— если внутри map()
вызывается метод, возвращающий Stream, то получится Stream<Stream<T>>
, и опять же нужно использовать flatMap()
.map()
— когда метод возвращает обычный тип (T → R
);flatMap()
— когда метод возвращает Optional
или Stream
(T → Optional<R>
или T → Stream<R>
).🧠 Знаешь ли ты, что @Transactional
на private
- методах не работает?
Да, Spring просто не применяет прокси к private
-методам. Это частый баг, который трудно отловить: ты вызываешь приватный метод внутри бина, а транзакция… не начинается 🤷♂️
📌 Почему так происходит?
Spring AOP по умолчанию использует динамические прокси (JDK или CGLIB), которые перехватывают внешние вызовы. А вызов private
- метода из того же класса — это внутренний вызов, который обходит прокси.
Пример, который НЕ работает:
@Service
public class UserService {
public void createUser() {
saveUser(); // Вызов мимо прокси 😞
}
@Transactional
private void saveUser() {
// Транзакция НЕ начнется!
}
}
public
или хотя бы protected
,
@Service
public class UserService {
private final TxUserSaver txUserSaver;
public UserService(TxUserSaver txUserSaver) {
this.txUserSaver = txUserSaver;
}
public void createUser() {
txUserSaver.saveUser(); // Теперь через прокси ✅
}
}
@Service
public class TxUserSaver {
@Transactional
public void saveUser() {
// Всё сработает как надо
}
}
@Transactional
ставят "на всякий случай".