Библиотека Java разработчика
10.1K subscribers
1.09K photos
603 videos
58 files
1.51K links
📚 Лайфхаки, приёмы и лучшие практики для Java-разработчиков. Всё, что ускорит код и прокачает навыки. Java, Spring, Maven, Hibernate.


По всем вопросам @evgenycarter

РКН clck.ru/3KoGeP
Download Telegram
💡 Чем опасен @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: ленивые зависимости через 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. Это способ контролировать создание и использование бинов вручную, когда это действительно нужно.

📈 Отлично помогает:

▫️при борьбе с циклическими зависимостями;
▫️для optional-бинов;
▫️чтобы ускорить старт приложения.

📲 Мы в MAX

👉@BookJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🧠 Как не словить 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
👩‍💻 HTTP-сервер на чистой Java за 30 минут

Приглашаем на открытый урок.

🗓 21 сентября в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Java разработчик. Продвинутый уровень».

Разберем, как браузер общается с сервером, и создадим работающий HTTP-сервис без Spring и сторонних библиотек.

О чем поговорим:
✔️ Как устроен HTTP-запрос
✔️ Создадим сервер на Java
✔️ Добавим несколько адресов
✔️ Откроем результат в браузере
✔️ Разберём, что скрывает Spring

🔗 Ссылка на регистрацию: https://vk.cc/d1sLwn

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🧠 Трюк с @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
🧠@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 — это идиома, которая используется для инициализации коллекций (и иногда других объектов) в краткой форме. Она выглядит как две открывающие фигурные скобки подряд {{ и имеет специфическое поведение. Пример:


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
👍21
📌 picocli — это современная библиотека для создания CLI-приложений на Java. Она упрощает разработку командных интерфейсов, обеспечивая:

* Автоматическую генерацию --help и --version
* Поддержку подкоманд (как в git commit, git push)
* Аргументы, параметры, опции с короткими и длинными флагами (-v, --verbose)
* Интеграцию с GraalVM (подходит для нативной компиляции)
* Поддержку аннотаций (аннотируй POJO — и готово!)
* Автоматическую валидацию аргументов
* Цветной вывод и гибкое форматирование
* Интерактивный режим и автодополнение

Проект активно развивается, полностью документирован и используется в сотнях продакшн-проектов. Если ты ищешь мощную и простую в использовании CLI-библиотеку на Java — picocli отличный выбор.

https://github.com/remkop/picocli

📲 Мы в MAX

👉@BookJava
👍31🔥1