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

По всем вопросам @evgenycarter
Download Telegram
🧠 @Value — тихая ловушка Spring'а

Сегодня покажу вам, почему аннотация @Value — не лучший выбор для работы с конфигурацией, особенно в Spring Boot 3+.

Допустим, у вас такой конфиг:


@Value("${app.timeout:30}")
private int timeout;


📌 Проблема #1: Плохая валидация

Если значение в application.yaml невалидное (например, timeout: abc), приложение *упадёт при старте*, но вы получите лишь общее сообщение Failed to bind…, без точной причины.

📌 Проблема #2: Нет автокомплита

IDE не сможет подсказать, какие свойства вообще есть. Рефакторинг ключей — боль.

📌 Проблема #3: Тестирование

Труднее подменить значения в тестах: @Value не так просто "подсунуть" моками.


Современный подход — использовать @ConfigurationProperties:


@ConfigurationProperties(prefix = "app")
@Configuration
public class AppProperties {
private Duration timeout = Duration.ofSeconds(30);

// геттеры/сеттеры
}


И в application.yaml:


app:
timeout: 30s


💡 Spring сам сконвертирует 30s в Duration, поддержит автокомплит в IDE, и вы сможете валидировать значения с @Validated.

⚠️ Не забудьте включить бин:


@EnableConfigurationProperties(AppProperties.class)


📌 Резюме: @Value — для быстрых хаков. Для серьёзной конфигурации — @ConfigurationProperties.

👉 @java_geek
👍6🔥1
🧠 Проблема ленивой инициализации @Transactional-сервисов в Spring Boot

Когда вы используете @Transactional на методе сервиса, Spring оборачивает бин в прокси. Но если вы решите сделать ленивую инициализацию (@Lazy) или внедрите такой бин в другой с помощью ObjectProvider.getIfAvailable() или @Autowired(required = false) — есть риск неожиданного поведения.

📌 Типичный баг:


@Service
@Lazy
public class MyService {

@Transactional
public void doSomething() {
// ...
}
}


🔍 Если где-то в коде вызвать applicationContext.getBean(MyService.class) в момент, когда прокси ещё не создан — вы получите оригинальный, не-прокси-объект. А значит, @Transactional не сработает.

⚠️ В результате — метод выполнится без транзакции, и вы не поймёте почему.

💡 Как избежать:

* Никогда не используйте @Lazy или ленивое получение @Transactional-сервисов, если не понимаете нюансов проксирования.
* Лучше разделите логику: сервис без прокси — отдельно, бизнес-методы с @Transactional — в другом бине.

Надёжный способ: вызывать @Transactional-методы только через Spring-контейнер. Даже внутри самого сервиса — через self-injection:


@Service
public class MyService {

private final MyService self;

public MyService(@Lazy MyService self) {
this.self = self;
}

public void doWrapper() {
self.doTransactional(); // прокси работает
}

@Transactional
public void doTransactional() {
// ...
}
}


👉 @java_geek
4👍2👎1👏1
🧠 Ленивая инициализация бинов в Spring Boot 3 — когда это спасает прод

Иногда на проде случаются сбои при старте из-за тяжёлых или нестабильных бинов. Это может быть внешняя интеграция, длительная инициализация или просто нестабильный ресурс. В таких случаях поможет ленивая инициализация.

📌 Как включить ленивую инициализацию глобально:


@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(App.class);
app.setLazyInitialization(true);
app.run(args);
}
}


📌 Или в application.yml:


spring:
main:
lazy-initialization: true


💡 Бины будут создаваться только при первом запросе к ним. Это уменьшает время старта и помогает "пережить" временные проблемы.

⚠️ Минусы:

* Ошибки сдвигаются с этапа запуска на runtime.
* Если бин важен для старта (например, контроллер или Scheduler), его лучше инициализировать сразу.

📌 Тонкая настройка:
Хотите ленивую инициализацию только для части приложения?


@Lazy
@Component
public class HeavyService {
// ...
}


💡 Работает и с @Bean, @Service, @Repository.

📈 Используйте в микросервисах с большим числом зависимостей или при миграции на Spring Boot 3+, где время старта стало критичным (например, в serverless-архитектуре).

👉 @java_geek
👍3
🔍Тестовое собеседование на Middle Java-разработчика завтра

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

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

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

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

Реклама. ООО "ШОРТКАТ", ИНН: 9731139396, erid: 2VtzqvEJJNG
Please open Telegram to view this post
VIEW IN TELEGRAM
🧠 Почему @Transactional не работает в private-методах?

В Spring @Transactional работает через прокси. Это значит, что Spring оборачивает бин в обёртку, которая и управляет транзакцией. Но есть важный нюанс:

📌 Прокси перехватывает только ВНЕШНИЕ вызовы метода.

Если вы вызываете @Transactional-метод изнутри того же класса, транзакция не начнётся.

Пример:


@Service
public class UserService {

public void createUser() {
// вызов внутреннего метода — транзакция не активируется
saveUser();
}

@Transactional
private void saveUser() {
// НЕ будет работать как транзакционный
}
}


💡 Даже если вы сделаете метод public, но вызов будет из того же класса — эффект тот же: Spring-прокси его не "перехватит".

⚠️ Аннотация @Transactional не работает на private-методах вообще. Прокси просто не может их подменить.

Правильный подход:
Вынесите транзакционный метод в отдельный бин:


@Service
public class UserSaver {

@Transactional
public void saveUser() {
// теперь работает!
}
}


И используйте его из другого бина:


@Service
public class UserService {
private final UserSaver userSaver;

public UserService(UserSaver userSaver) {
this.userSaver = userSaver;
}

public void createUser() {
userSaver.saveUser(); // транзакция начнётся
}
}


📌 Или используйте аспектно-ориентированную реализацию с @EnableAspectJAutoProxy(exposeProxy = true) + AopContext.currentProxy(), но это уже хардкор.

👉 @java_geek
👍51
🚀 Открой для себя идеальный путь к лидерству с карьерным тестом от ОЭЗ «Алабуга»! 🌟

Мечтаете о карьере в крупной компании, где ваш потенциал раскроется на полную? Наш тест поможет вам определить вашу уникальную лидерскую роль. Может быть, именно вы станете тем лидером, который выведет команду на новый уровень?

После прохождения теста вы можете заполнить заявку и получить приглашение на эксклюзивную лидерскую программу. Участие в программе открывает реальные перспективы трудоустройства в ОЭЗ «Алабуга», предоставляя шанс начать путь к профессиональному признанию.

Сделайте первый шаг к своему будущему сегодня! Пройдите тест, подайте заявку и начните строить свою карьеру вместе с нами. 🎯
1🔥1🍾1
Метод isEmpty()

isEmpty() – проверяет список на наличие элементов. Если список пустой, то возвращает true, в противном случае – false.

👉 @java_geek
👍3😐2🤯1
Зачем нужны и какие бывают блоки инициализации?

Блоки инициализации представляют собой код, заключенный в фигурные скобки и размещаемый внутри класса вне объявления методов или конструкторов.

• Существуют статические и нестатические блоки инициализации.
• Блок инициализации выполняется перед инициализацией класса загрузчиком классов или созданием объекта класса с помощью конструктора.
• Несколько блоков инициализации выполняются в порядке следования в коде класса.
• Блок инициализации способен генерировать исключения, если их объявления перечислены в throws всех конструкторов класса.
• Блок инициализации возможно создать и в анонимном классе.

👉 @java_geek
2👍2🔥1
🔍Тестовое собеседование с Java-разработчиком из Т1 Иннотех уже завтра

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

Как это будет:
📂 Илья Аров, старший разработчик в Т1, будет задавать реальные вопросы и задачи разработчику-добровольцу
📂 Илья будет комментировать каждый ответ респондента, чтобы дать понять чего от вас ожидает собеседующий на интервью
📂 В конце можно будет задать любой вопрос Илье

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

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

Реклама. ООО "ШОРТКАТ", ИНН: 9731139396, erid: 2VtzqxFHKss
Please open Telegram to view this post
VIEW IN 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