Java Geek
2.49K subscribers
259 photos
1 file
19 links
Практичные советы, лайфхаки и код для Java-разработчиков. Каждый пост — реальная польза. Учим Java на примерах.

По всем вопросам @evgenycarter
Download Telegram
Collections.disjoint()

Collections.disjoint() проверяет, не имеют ли две коллекции общих элементов. Это полезно для фильтрации, сравнения и оптимизации поиска пересечений.

👉 @java_geek
👍4
Как легко избавиться от N+1 проблемы в Spring Data JPA, используя @EntityGraph и оптимизированное fetch join. 🧠

📌 Что такое N+1?
Когда вам нужно загрузить список сущностей (N штук), а для каждой из них Hibernate выполняет дополнительный запрос к базе — получается 1 запрос для основной выборки + N запросов для связанных данных.

💡 Пример проблемы:


// Репозиторий без оптимизации
public interface UserRepository extends JpaRepository<User, Long> {}
// Сущности
@Entity
public class User {
@OneToMany(mappedBy = "user")
private List<Order> orders;
}


При вызове userRepository.findAll():

1. SELECT * FROM users
2. Для каждого пользователя: SELECT * FROM orders WHERE user_id = ?

⚠️ Почему это плохо?

* Избыточные круги по базе
* Замедление при N > 50−100
* Рост нагрузки на БД и время ответа

🔧 Решение #1: fetch join в @Query


@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.active = true")
List<User> findAllActiveWithOrders();


Все нужные данные подтягиваются одним запросом.
Но не всегда удобно, если нужно динамически выбирать связи.


🔧 Решение #2: @EntityGraph


@Entity
@NamedEntityGraph(
name = "User.withOrders",
attributeNodes = @NamedAttributeNode("orders")
)
public class User {
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders;
// ...
}

public interface UserRepository extends JpaRepository<User, Long> {
@EntityGraph(value = "User.withOrders", type = EntityGraph.EntityGraphType.LOAD)
List<User> findAllByActiveTrue();
}


* @NamedEntityGraph в сущности описывает, какие связи нужно подтянуть
* В репозитории вы указываете, какой граф выбирать для метода
* Hibernate формирует один SQL с LEFT OUTER JOIN, грузя User + Order за 1 запрос

🧠 Когда стоит использовать?

* Если часто приходят жалобы на «медленно выбирается список с зависимыми сущностями»
* Когда fetch = FetchType.LAZY установлен по-умолчанию, но иногда нужна жадная загрузка
* Для динамических графов можно строить EntityGraph прямо в коде через API

💡 Код для динамического графа:


public List<User> findActiveWithDynamicGraph() {
EntityGraph<User> graph = entityManager.createEntityGraph(User.class);
graph.addAttributeNodes("orders", "profile");
TypedQuery<User> query = entityManager.createQuery(
"SELECT u FROM User u WHERE u.active = true", User.class
);
query.setHint("javax.persistence.fetchgraph", graph);
return query.getResultList();
}


* Здесь вы сами добавляете, какие связи надо подгрузить
* Подходит, когда нужен гибкий выбор полей «на лету»

⚠️ Важные нюансы:

* @EntityGraph работает только для root - сущности (не на вложенных коллекциях)
* Следите за дублированием строк в результирующем List: вы можете получить повторяющиеся User - объекты, если у вас несколько Order. В таком случае добавьте DISTINCT в JPQL:


@Query("SELECT DISTINCT u FROM User u JOIN FETCH u.orders WHERE u.active = true")

* Если нужно подгружать множество уровней вложенности, объявите вложенные subgraph в @NamedEntityGraph

📌 Вывод:

1. По умолчанию ставьте fetch = FetchType.LAZY для всех связей, чтобы не тянуть лишние данные.
2. Когда нужна жадная загрузка — используйте @EntityGraph или fetch join.
3. Динамические графы через EntityManager помогут строить сложные выборки без избыточных запросов.


👉 @java_geek
👍6
HashMap: chain hashing и битовый спрединг

🧠 HashMap использует chain hashing + битовый спрединг

🔹 Хэш-функция (bit mixing):


int h = key.hashCode();
int hash = h ^ (h >>> 16);


🔹 Выбор бакета:


int idx = hash & (table.length - 1);


📌 Separate chaining: в каждом бакете хранится связный список (Java 8+ превращает списки длиной >8 в красно-черное дерево для ускорения).
📈 Сложность: средняя O(1), в худшем случае O(n) → O(log n) после treeification.
💡 Bit mixing объединяет старшие и младшие биты, чтобы при взятии по модулю power-of-two (маска &) распределение оставалось равномерным.
⚠️ Не для криптографии: это простая битовая операция, а не криптостойкая хэш-функция.

👉 @java_geek
👍1
🧠 Почему @Transactional не работает?

Один из самых частых вопросов: "Я поставил @Transactional, но транзакция не откатывается. Почему?"

📌 Ответ — в механизме прокси Spring.

Spring оборачивает бины с @Transactional в прокси, которые перехватывают вызовы и управляют транзакцией. Но работает это только при вызове метода извне. Если ты вызываешь метод с @Transactional внутри того же класса, прокси не используется, и аннотация игнорируется.

💡 Пример:


@Service
public class UserService {
public void registerUser() {
createUser(); // Транзакция не работает!
}

@Transactional
public void createUser() {
// изменения в БД
}
}


📎 Решения:

1. Вынести метод в другой бин:


@Service
public class UserService {
private final UserWriter writer;

public UserService(UserWriter writer) {
this.writer = writer;
}

public void registerUser() {
writer.createUser(); // работает
}
}

@Service
public class UserWriter {
@Transactional
public void createUser() {
// изменения в БД
}
}


2. Или вызвать себя через `ApplicationContext`:


@Autowired
private ApplicationContext context;

public void registerUser() {
context.getBean(UserService.class).createUser(); // работает
}


⚠️ Но лучше использовать первый способ — он чище архитектурно.

👉 @java_geek
4👍3
👩‍💻🎯 Юнитесты на Java: как новичку поймать баги за 5 Секунд?

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

🗓 24 июня в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Java-разработчик».

🦾 Тестирование — суперсила разработчика. Научитесь писать код, который проверяет сам себя, экономит часы на отладке и делает ваши приложения неуязвимыми.

О чём поговорим:
✔️ Что такое Unit-тесты? Для чего они нужны, даже если «код и так работает».
✔️ JUnit 5 для новичков: как установить и написать первый тест.
✔️ Тестирование = Дзен-кодинг: как тесты помогают понять свой код лучше вас самих.
✔️ Ловушки и лайфхаки: что делать, если тесты падают?

Кому будет интересно:
Начинающим Java-разработчикам, студентам и всем, кто хочет перестать бояться слов «тестирование» и «баги».

В результате вебинара вы:
Создадите свой первый тест на Java, поймёте, как тестировать методы с исключениями, и начнёте писать код, которым можно гордиться.

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

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Please open Telegram to view this post
VIEW IN TELEGRAM
Fluent Interface

Fluent Interface (Текучий интерфейс) — это шаблон проектирования, который позволяет создавать код, читающийся как текст на естественном языке.

Он достигается путем создания методов, которые возвращают ссылку на this объект, позволяя вызывать их в цепочке.

👉 @java_geek
👍3
🧠 Зачем использовать @Transactional(readOnly = true)?

Многие добавляют @Transactional(readOnly = true) на сервисные методы просто по привычке. Но давайте разберёмся, что на самом деле даёт этот флаг, и где он реально ускоряет выполнение.

📌 Что делает readOnly = true?

* Подсказывает JPA провайдеру (например, Hibernate), что изменения в сущностях можно не отслеживать (skip dirty checking).
* В некоторых БД может выставить read-only transaction flag, который предотвращает нежелательные изменения (например, в PostgreSQL).
* Может ускорить выборки, особенно при больших графах сущностей.

💡 Пример:


@Service
@RequiredArgsConstructor
public class UserService {

private final UserRepository userRepository;

@Transactional(readOnly = true)
public UserDto getUser(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("User not found"));
return UserDto.from(user);
}
}


⚠️ Важно:

* readOnly = true не блокирует изменения в БД, если вы явно вызываете save() — это НЕ защита от дурака.
* Hibernate всё равно создаст транзакцию — это не аннотация "без транзакций".

Когда использовать:

* Методы, которые только читают данные и не модифицируют сущности.
* Большие выборки без нужды в lazy-инициализации через сессию.
* Там, где критична производительность чтения.

Когда НЕ нужно:

* Если вы всё равно модифицируете сущности внутри метода.
* Если работаете с @Modifying запросами — Spring их игнорирует при readOnly = true.

📎 Документация Spring @Transactional

👉 @java_geek
👍2
🔍 Завтра тестовое собеседование с Java-разработчиком

2 июля(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Java-разработчика.

Как это будет:
📂 Сергей Чамкин, старший разработчик из Uzum, ex-WildBerries, будет задавать реальные вопросы и задачи разработчику-добровольцу
📂 Cергей будет комментировать каждый ответ респондента, чтобы дать понять чего от вас ожидает собеседующий на интервью
📂 В конце можно будет задать любой вопрос Сергею

Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.

Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot

Реклама. ООО "ШОРТКАТ", ИНН: 9731139396, erid: 2Vtzqwfa2Fy
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥 Spring Native Image vs. Обычное Spring-приложение: В чем разница и зачем это нужно?

Разбираемся, как нативные образы меняют правила игры для Spring-приложений! 👇

Обычное Spring-приложение (JVM-based):

Когда вы запускаете классический Spring Boot на Java Virtual Machine (JVM), происходит следующее:

🔵JVM запускается первой: Прежде чем ваш код заработает, JVM должна инициализироваться и загрузить классы. Это занимает время.
🔵Динамическая природа: JVM позволяет динамически загружать классы и использовать рефлексию в рантайме. Это гибко, но ресурсозатратно.
🔵"Прогрев" (Warm-up): Для пиковой производительности JVM нужно время на JIT-компиляцию кода.
🔵Большой объем памяти: JVM сама по себе требует значительного объема RAM.
🔵Размер артефакта: JAR-файл содержит байт-код, который JVM затем обрабатывает.


Spring Native Image (с GraalVM Native Image):

Это компиляция вашего Spring-приложения в самостоятельный нативный исполняемый файл, не требующий JVM!

🔵Компиляция AOT (Ahead-of-Time): Весь код (включая Spring и JDK) компилируется в машинный код на этапе сборки. Все оптимизации происходят заранее.
🔵Мгновенный старт: Нет JVM и "прогрева"! Нативные образы стартуют мгновенно (от долей секунды). Идеально для:
🔵Серверлес-функций (Lambda, FaaS): Быстрый старт снижает задержки и стоимость.
🔵Микросервисов: Для быстрого масштабирования.
🔵Batch-задач: Быстрый запуск и завершение.
🔵Низкое потребление памяти: Отсутствие JVM значительно сокращает RAM. Экономия облачных ресурсов и возможность запускать больше приложений на сервере.
🔵Меньший размер образа: Включается только используемый код без JVM, что упрощает развертывание в контейнерах.
🔵Статический анализ: GraalVM глубоко анализирует код, исключая неиспользуемые части.


Недостатки (куда без них?):

🔵Длительное время сборки: AOT-компиляция занимает значительно больше времени.
🔵Сложности с динамикой: Рефлексия и прокси требуют дополнительных настроек (runtime hints), хотя Spring Boot 3 упрощает это.
🔵Отсутствие некоторых инструментов мониторинга: Привычные JVM-инструменты (JMX, JFR) не поддерживаются напрямую.
🔵Специфичность платформы: Нужен отдельный образ для каждой ОС и архитектуры.


Когда стоит использовать Spring Native Image?

🔵Когда время старта и потребление памяти критичны.
🔵Для микросервисов, серверлес-функций и batch-задач.
🔵Для контейнеров и облачных сред с оплатой по потреблению.

👉 @java_geek
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
VK приглашает на Java AI meetup 9 июля. Обещают доклады топовых спикеров из AI-департамента, тематическую дискуссию и много полезных знакомств в индустрии.
Подробнее — здесь.
🧠 Неожиданный анти-паттерн в Spring Boot: @EventListener как тихий убийца производительности

В Spring удобно использовать @EventListener для реактивных действий — казалось бы, удобно и "чисто". Но есть нюанс.

📌 По умолчанию все @EventListener вызываются синхронно и в том же потоке, где был опубликован ивент через ApplicationEventPublisher.

Это значит:


@Component
public class OrderService {
@Autowired
private ApplicationEventPublisher publisher;

public void createOrder(Order order) {
// 1. Сохраняем заказ
orderRepository.save(order);

// 2. Публикуем ивент
publisher.publishEvent(new OrderCreatedEvent(order));
// 3. До выхода из метода все @EventListener уже будут выполнены
}
}


А теперь представим, что слушатель делает что-то тяжёлое:


@EventListener
public void sendEmail(OrderCreatedEvent event) {
emailService.sendConfirmation(event.getOrder());
}


⚠️ Если sendConfirmation висит на внешнем SMTP или уходит в сеть — метод createOrder будет ждать его завершения!

💡 Решение: сделать обработку асинхронной.

Просто добавьте @Async:


@Async
@EventListener
public void sendEmail(OrderCreatedEvent event) {
emailService.sendConfirmation(event.getOrder());
}


И не забудьте включить поддержку @Async:


@EnableAsync
@Configuration
public class AsyncConfig {}


📌 Теперь @EventListener будет исполняться в отдельном потоке из TaskExecutor, и createOrder не будет блокироваться.

👉 @java_geek
👍61