💡 Чем опасен
Расписание в Spring через
📌 Пример проблемы:
🧨 Каждые 10 секунд метод запускается заново. Если выполнение предыдущего ещё не закончено, начнётся второй поток, который заберёт те же
В итоге — дублирование обработки, гонки, повреждение данных.
📉 Особенно критично при долгих задачах или высокой нагрузке.
✅ Решение — обернуть метод в транзакцию + использовать блокировки:
📌 Или добавить флаг “locked”, чтобы явно помечать взятые задачи.
💡 Лучше использовать
🧠 Подумайте о том, чтобы заменить
* Spring Batch (если сложные джобы)
* Spring Integration / Flowable / Camunda (если нужны гарантии и retry)
* Quartz (если нужен контроль и очереди)
📲 Мы в MAX
👉@BookJava
@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);
});
}
🧨 Каждые 10 секунд метод запускается заново. Если выполнение предыдущего ещё не закончено, начнётся второй поток, который заберёт те же
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);
});
}
📌 Или добавить флаг “locked”, чтобы явно помечать взятые задачи.
💡 Лучше использовать
@Scheduled(fixedDelay) — он ждёт завершения предыдущего запуска. Это безопаснее по умолчанию.🧠 Подумайте о том, чтобы заменить
@Scheduled на:* Spring Batch (если сложные джобы)
* Spring Integration / Flowable / Camunda (если нужны гарантии и retry)
* Quartz (если нужен контроль и очереди)
📲 Мы в MAX
👉@BookJava
❤2👍2🔥1
🧠 Spring Boot: ленивые зависимости через
Иногда сервису не нужно всегда инжектить другую зависимость при старте — только иногда по ходу работы. Но
💡 Решение: использовать
Пример:
📌
▫️ не создаёт бин сразу — ленивый доступ;
▫️ позволяет проверить наличие бина (
▫️ можно использовать
⚠️ Это не альтернатива DI. Это способ контролировать создание и использование бинов вручную, когда это действительно нужно.
📈 Отлично помогает:
▫️ при борьбе с циклическими зависимостями;
▫️ для optional-бинов;
▫️ чтобы ускорить старт приложения.
📲 Мы в MAX
👉@BookJava
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() — для коллекций бинов.⚠️ Это не альтернатива DI. Это способ контролировать создание и использование бинов вручную, когда это действительно нужно.
📈 Отлично помогает:
📲 Мы в MAX
👉@BookJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🧠 Как не словить
Одна из самых частых ошибок при работе с JPA:
📌 Причина: лениво загружаемая коллекция (
💡 Как избежать?
✅ Решение 1:
Убедитесь, что вы обращаетесь к ленивым коллекциям внутри метода с
⚠️ Не используйте
✅ Решение 2: Fetch Join
Подгрузите нужные данные сразу через
📌 Плюс: 1 запрос вместо N (N+1 проблема решается).
📌 Минус: может быть избыточная загрузка, особенно с большими коллекциями.
✅ Решение 3: DTO проекция
Лучший способ в большинстве случаев - проецировать сразу в DTO:
📌 Выгружает только нужные данные. Быстро, безопасно, эффективно.
Ленивая инициализация - ок, если вы контролируете границы транзакций.
Проекции и
📲 Мы в MAX
👉@BookJava
LazyInitializationException в Spring Boot + HibernateОдна из самых частых ошибок при работе с JPA:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection
📌 Причина: лениво загружаемая коллекция (
LAZY) обращается к БД вне транзакции — например, в слое контроллера или после закрытия Session.💡 Как избежать?
✅ Решение 1:
@Transactional в сервисеУбедитесь, что вы обращаетесь к ленивым коллекциям внутри метода с
@Transactional:
@Transactional
public UserDto getUser(Long id) {
User user = userRepository.findById(id)
.orElseThrow();
// OK: коллекция friends будет инициализирована в транзакции
return new UserDto(user.getName(), user.getFriends());
}
⚠️ Не используйте
@Transactional в контроллерах - это плохая практика.✅ Решение 2: Fetch Join
Подгрузите нужные данные сразу через
JOIN FETCH:
@Query("SELECT u FROM User u LEFT JOIN FETCH u.friends WHERE u.id = :id")
Optional<User> findByIdWithFriends(@Param("id") Long id);
📌 Плюс: 1 запрос вместо N (N+1 проблема решается).
📌 Минус: может быть избыточная загрузка, особенно с большими коллекциями.
✅ Решение 3: DTO проекция
Лучший способ в большинстве случаев - проецировать сразу в DTO:
@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 - ваши лучшие друзья, если нужен контроль и производительность.📲 Мы в MAX
👉@BookJava
👍2
Приглашаем на открытый урок.
🗓 21 сентября в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Java разработчик. Продвинутый уровень».
Разберем, как браузер общается с сервером, и создадим работающий HTTP-сервис без Spring и сторонних библиотек.
О чем поговорим:
🔗 Ссылка на регистрацию: https://vk.cc/d1sLwn
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🧠 Трюк с
Когда тебе нужно обрабатывать события в рамках транзакции, мы часто пишем:
⚠️ Но есть нюанс:
📌 Альтернатива: обычный
💡 Сниппет:
📎 Плюсы:
— Лучше контроль: ты сам решаешь, когда обрабатывать (до/после/вне транзакции)
— Можно централизовать поведение через utility-метод
— Гибкость: логика обработки не зависит от аннотаций Spring'а
⚠️ Минус: чуть больше кода, но понятнее поведение.
📲 Мы в MAX
👉@BookJava
@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: нет активной транзакции — выполняем сразу
}
}
}
📎 Плюсы:
— Лучше контроль: ты сам решаешь, когда обрабатывать (до/после/вне транзакции)
— Можно централизовать поведение через utility-метод
— Гибкость: логика обработки не зависит от аннотаций Spring'а
⚠️ Минус: чуть больше кода, но понятнее поведение.
📲 Мы в MAX
👉@BookJava
👍2🔥2
🧠
Оба способа хороши для конфигурации, но используют их по-разному. И если ты всё ещё везде пихаешь
📌
✅ Хорошо для единичных значений
❌ Плохо для сложных структур, списков, валидации
❌ Трудно покрыть тестами (без
❌ Нет биндинга по префиксу → нет группировки
📌
💡 Используй с
✅ Удобно группировать и документировать
✅ Работает с вложенными структурами, коллекциями
✅ Поддерживает JSR-303 валидацию (
✅ Легче мокать в тестах
✅ Интеграция с Spring Boot Actuator (
⚠️ Не смешивай: не нужно тянуть
Если конфигурация простая —
📲 Мы в MAX
👉@BookJava
@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✅ Удобно группировать и документировать
✅ Работает с вложенными структурами, коллекциями
✅ Поддерживает JSR-303 валидацию (
@Validated)✅ Легче мокать в тестах
✅ Интеграция с Spring Boot Actuator (
/actuator/configprops)⚠️ Не смешивай: не нужно тянуть
@Value внутрь @ConfigurationProperties — это антипаттерн.Если конфигурация простая —
@Value норм. Но как только появляется структура, коллекции, логика — всегда используй @ConfigurationProperties.📲 Мы в MAX
👉@BookJava
👍4🔥2
Double-brace инициализация в Java — это идиома, которая используется для инициализации коллекций (и иногда других объектов) в краткой форме. Она выглядит как две открывающие фигурные скобки подряд
Как это работает:
Double-brace инициализация — это комбинация двух конструкций:
1. Анонимный внутренний класс:
Создаётся новый безымянный подкласс
2. Инициализатор экземпляра:
Это блок, который выполняется при создании объекта. В него можно вставлять вызовы методов (например,
Преимущества:
* Компактный и удобочитаемый синтаксис для заполнения коллекций.
* Можно использовать в полях
Недостатки:
1. Создаётся лишний анонимный класс — это увеличивает количество байткода и может мешать сериализации.
2. Утечки памяти — если такой класс находится внутри внешнего класса, он может неявно хранить ссылку на него.
3. Читаемость — не все разработчики знают, как это работает, и это может сбивать с толку.
4. Нарушение принципов OOP — логика инициализации размещается в конструкторе, который не явно виден.
Альтернативы:
Java 8+ (через
Статический метод инициализации:
Java 9+ (immutable):
Вывод:
Double-brace инициализация — это удобный, но потенциально опасный трюк, который не рекомендуется использовать в продакшене. Лучше предпочесть более читаемые и безопасные альтернативы, особенно с учётом новых возможностей Java 8+.
📲 Мы в MAX
👉@BookJava
{{ и имеет специфическое поведение. Пример:
import java.util.*;
List<String> list = new ArrayList<String>() {{
add("one");
add("two");
add("three");
}};
Как это работает:
Double-brace инициализация — это комбинация двух конструкций:
1. Анонимный внутренний класс:
new ArrayList<String>() { ... }
Создаётся новый безымянный подкласс
ArrayList.2. Инициализатор экземпляра:
{{ ... }}
Это блок, который выполняется при создании объекта. В него можно вставлять вызовы методов (например,
add()).Преимущества:
* Компактный и удобочитаемый синтаксис для заполнения коллекций.
* Можно использовать в полях
final, например:
private static final Set<String> set = new HashSet<>() {{
add("A");
add("B");
}};
Недостатки:
1. Создаётся лишний анонимный класс — это увеличивает количество байткода и может мешать сериализации.
2. Утечки памяти — если такой класс находится внутри внешнего класса, он может неявно хранить ссылку на него.
3. Читаемость — не все разработчики знают, как это работает, и это может сбивать с толку.
4. Нарушение принципов OOP — логика инициализации размещается в конструкторе, который не явно виден.
Альтернативы:
Java 8+ (через
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;
}
Java 9+ (immutable):
List<String> list = List.of("one", "two", "three");
Set<String> set = Set.of("A", "B");
Вывод:
Double-brace инициализация — это удобный, но потенциально опасный трюк, который не рекомендуется использовать в продакшене. Лучше предпочесть более читаемые и безопасные альтернативы, особенно с учётом новых возможностей Java 8+.
📲 Мы в MAX
👉@BookJava
👍2❤1
📌 picocli — это современная библиотека для создания CLI-приложений на Java. Она упрощает разработку командных интерфейсов, обеспечивая:
* Автоматическую генерацию
* Поддержку подкоманд (как в
* Аргументы, параметры, опции с короткими и длинными флагами (
* Интеграцию с GraalVM (подходит для нативной компиляции)
* Поддержку аннотаций (аннотируй POJO — и готово!)
* Автоматическую валидацию аргументов
* Цветной вывод и гибкое форматирование
* Интерактивный режим и автодополнение
Проект активно развивается, полностью документирован и используется в сотнях продакшн-проектов. Если ты ищешь мощную и простую в использовании CLI-библиотеку на Java — picocli отличный выбор.
https://github.com/remkop/picocli
📲 Мы в MAX
👉@BookJava
* Автоматическую генерацию
--help и --version* Поддержку подкоманд (как в
git commit, git push)* Аргументы, параметры, опции с короткими и длинными флагами (
-v, --verbose)* Интеграцию с GraalVM (подходит для нативной компиляции)
* Поддержку аннотаций (аннотируй POJO — и готово!)
* Автоматическую валидацию аргументов
* Цветной вывод и гибкое форматирование
* Интерактивный режим и автодополнение
Проект активно развивается, полностью документирован и используется в сотнях продакшн-проектов. Если ты ищешь мощную и простую в использовании CLI-библиотеку на Java — picocli отличный выбор.
https://github.com/remkop/picocli
📲 Мы в MAX
👉@BookJava
👍3❤1🔥1