Java for Beginner
870 subscribers
1.01K photos
275 videos
14 files
1.69K links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Что такое Dependency Injection (DI) и Inversion of Control (IoC)? 🤓

Ответ:

Inversion of Control (IoC)
— это общий принцип, при котором фреймворк управляет потоком программы и объектами, а не наоборот.

Dependency Injection (DI) — это конкретная реализация IoC, при которой объекты получают свои зависимости извне (через конструктор, сеттер или интерфейс), а не создают их сами.

Это уменьшает связанность кода, повышает тестируемость (легко подставить моки) и гибкость.

Главный контейнер DI в Java-мире — это Spring IoC-контейнер.


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
История технологий сегодня — 13 марта

ℹ️ Кто родился в этот день

Джон Хазбрук Ван Флек (англ. John Hasbrouck van Vleck; 13 марта 1899, Мидлтаун — 27 октября 1980, Кембридж, Массачусетс)американский физик, лауреат Нобелевской премии по физике 1977 года. Работы по квантовой теории атомной структуры, магнетизму, теории валентности, изучению атомных и молекулярных спектров, ферро- и ферримагнитному резонансу. Один из создателей современных представителей о магнетизме вещества. Разработал квантовомеханическую теорию диа- и парамагнетизма (1926—1928), получил парамагнитную добавку к диамагнитной восприимчивости несимметричных атомов и молекул, названную ванфлековским парамагнетизмом (1927). Развил теорию внутрикристаллического (лигандного) поля. Дал детальную трактовку антиферромагнетизма (1941), термодинамическую теорию молекулярного поля для антиферромагнетиков, развил гейзенберговскую модель локализованных спинов и предложил эффективный спиновый гамильтониан.


🌐 Знаковые события

1781Английский астроном Вильям Гершель с помощью собственноручно изготовленного телескопа открыл седьмую планету Солнечной системы — Уран. Правда, первоначально он принял её за комету. Когда выяснилось, что это неизвестная ранее планета, он получил медаль Королевского общества и должность придворного астронома. Сам Гершель назвал планету в честь своего высокого покровителя короля Георга III «Звездой Георга», короткое время она носила имя первооткрывателя, пока немецкий астроном Иоганн Боде не придумал для неё название Уран.

1930 — американский астроном Клайд Томбо объявил в докладе об открытии им 18 февраля девятой планеты Солнечной системы. Доклад был приурочен к 75-летию основателя и первого директора крупнейшей частной обсерватории в США, Персиваля Лоуэлла, который многие годы потратил на поиски предсказанной им девятой планеты. На следующий день 14 марта Венеция Бёрни, одиннадцатилетняя школьница из Оксфорда, предложит имя для новой планеты, и в мае будет опубликовано официальное имя — Плутон.

1989 — была изобретена Всемирная паутина (World Wide Web, WWW), более известная как Интернет. Изобретателем Интернета считается английский учёный Тим Бернерс-Ли и его коллеги, работавшие в Европейском совете по ядерным исследованиям (CERN), передали начальнику своего отдела документ, озаглавленный «Информационный менеджмент: некоторые предложения», в котором были заложены основные принципы WWW.


#Biography #Birth_Date #Events #13марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
[Совет по Java #015]

Тема: @Async по умолчанию не работает внутри того же класса. Вызов асинхронного метода из другого метода этого же класса не пройдет через Spring Proxy.

Проблема: Аннотация @Async работает благодаря Spring AOP, который создает прокси вокруг целевого бина.

При вызове метода через прокси (например, из другого бина) запускается асинхронное выполнение. Однако при прямом вызове из того же класса через this прокси не участвует — вызывается оригинальный метод, и асинхронность теряется.

Это частая ошибка: код выглядит правильно, но метод выполняется синхронно, что приводит к неожиданным задержкам и блокировкам.

Решение: Никогда не вызывайте @Async методы напрямую внутри того же класса.

Используйте self-injection (внедрение ссылки на самого себя через @Autowired/@jakarta.inject.Inject) или вынесите асинхронную логику в отдельный @Service-компонент.

Self-injection заставляет обращаться к прокси, а не к реальному объекту.

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.concurrent.CompletableFuture;

@Service
public class AsyncService {

@Autowired
private AsyncService selfProxy; // self-injection

@Async
public CompletableFuture<String> asyncMethod() {
System.out.println("Асинхронный вызов в потоке: " + Thread.currentThread().getName());
return CompletableFuture.completedFuture("Результат");
}

//Проблема: прямой вызов
public void badCall() {
System.out.println("Прямой вызов");
asyncMethod(); // this.asyncMethod() — асинхронности НЕТ!
}

//Решение: вызов через self-proxy
public void goodCall() {
System.out.println("Вызов через self-proxy");
selfProxy.asyncMethod(); // через прокси — асинхронно
}
}

// Альтернатива: отдельный компонент
@Service
class AsyncWorker {
@Async
public CompletableFuture<String> doWork() {
return CompletableFuture.completedFuture("Работа выполнена");
}
}

@Service
class CallerService {
@Autowired
private AsyncWorker worker;

public void call() {
worker.doWork(); // асинхронно через прокси
}
}


Объяснение: Spring создает прокси-объект, который перехватывает вызовы @Async методов и передает их в пул потоков.

Когда бин внедряет сам себя (selfProxy), он получает именно прокси, а не исходный объект. Вызов через selfProxy.asyncMethod() корректно обрабатывается перехватчиком. Self-injection работает благодаря тому, что Spring после создания бина внедряет его же прокси (если включен режим @EnableAsync и прокси-таргет класс). Для этого требуется либо использовать интерфейсы (JDK proxy), либо CGLIB (proxy-target-class=true).

Альтернативное и более чистое решение — вынести асинхронный метод в отдельный бин, что полностью исключает проблему внутренних вызовов.

Разработчик всегда проверяет, через какой объект вызывается @Async метод, и избегает вызовов через this.


#Java #советы
👍5
Что выведет код?

@SpringBootApplication
@EnableAsync
public class Task130326 implements CommandLineRunner {

@Autowired
private AsyncService130326 service;

public static void main(String[] args) {
SpringApplication.run(Task130326.class, args);
}

@Override
public void run(String... args) {
service.directCall();
service.proxyCall();
}
}

@Component
class AsyncService130326 {
@Lazy
@Autowired
private AsyncService130326 self;

@Async
public void asyncMethod() {
System.out.println("asyncMethod: " + Thread.currentThread().getName());
}

public void directCall() {
System.out.println("directCall: " + Thread.currentThread().getName());
asyncMethod();
}

public void proxyCall() {
System.out.println("proxyCall: " + Thread.currentThread().getName());
self.asyncMethod();
}
}


#Tasks
👍2
Что такое Java Memory Model (JMM)? 🤓

Ответ:

Java Memory Model (JMM)
— это формальная модель, описывающая, как потоки взаимодействуют через память и какое поведение можно ожидать в многопоточной среде.

JMM определяет правила, когда изменения, сделанные одним потоком, становятся видимыми для других потоков. Она описывает отношение "happens-before", которое гарантирует видимость и упорядоченность действий. Без JMM компилятор и процессор могли бы переупорядочивать инструкции так, что многопоточный код работал бы непредсказуемо.

Ключевые слова synchronized, volatile, final обеспечивают определенные гарантии видимости в рамках JMM.


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
История технологий сегодня — 14 марта

ℹ️ Кто родился в этот день

Станислав Алексеевич Кудж (род. 14 марта 1979, Москва, РСФСР, СССР) — российский учёный, специалист в области автоматизированных систем государственного управления, многопрофильных и многофункциональных информационных систем.


🌐 Знаковые события

1956 — американская компания Ampex продемонстрировала первый в мире видеомагнитофон VR-1000.

1994 — релиз Linux версии 1.0.0.


#Biography #Birth_Date #Events #14марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
С 07.03 по 13.03
Предыдущий пост(с 28.02 по 06.03)

Воскресный мотивационный пост:
Не было мотивации

Запись встреч/видео:
Видео в процессе подготовки...

Обучающие статьи:
Раздел 8. Stream API и функциональный стиль

Глава 5: Контракты. Почему equals, hashCode и иммутабельность — закон
equals/hashCode в операциях distinct, groupingBy, toSet
Иммутабельность данных в потоке — не прихоть, а необходимость

Советы по Java:
[Совет по Java #013]
Тема: Аннотация @Transactional не работает на private и protected методах

[Совет по Java #014]
Тема: Инъекция через конструктор предпочтительнее инъекции в поле

[Совет по Java #015]
Тема: @Async по умолчанию не работает внутри того же класса


Полезные статьи и видео:
Java без розовых очков: какие знания отделяют грейды

Как и всегда, задачи можно найти под тегом - #Tasks, вопросы с собеседований - #собеседование
👍2
🤣3
🤣6
История технологий сегодня — 15 марта

ℹ️ Кто родился в этот день

Жоре́с Ива́нович Алфёров (бел. Жарэс Іванавіч Алфёраў; 15 марта 1930, Витебск — 1 марта 2019, Санкт-Петербург) — советский и российский учёный-физик. Политический деятель. Лауреат Нобелевской премии по физике (2000 год, за разработку полупроводниковых гетероструктур и создание быстрых опто- и микроэлектронных компонентов).


🌐 Знаковые события

1892 — американский изобретатель Джесс Рено (англ. Jesse Reno) запатентовал первый эскалатор.

1985 — зарегистрирован первый домен в зоне .com


#Biography #Birth_Date #Events #15марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Вы соскучились по встречам?
Anonymous Poll
56%
Да!
4%
Нет!
0%
Похер
36%
Встреча с кем?
4%
Что я тут делаю?
👍1
История технологий сегодня — 16 марта

ℹ️ Кто родился в этот день

Ричард Мэттью Столлман (англ. Richard Matthew Stallman; род. 16 марта 1953, Манхэттен, Нью-Йорк), также упоминаемый как rms, — основатель движения свободного программного обеспечения, проекта GNU, Фонда свободного программного обеспечения и Лиги за свободу программирования. Автор концепции «копилефта», призванной защищать идеалы движения; эту концепцию он с помощью юристов позже воплотил в лицензии GNU General Public License (GNU GPL) для ПО. Ранее также известный программист. Из авторских программ можно отметить GNU Emacs, Коллекция компиляторов GNU (GCC) и Отладчик GNU (GDB). С середины 1990-х годов Столлман стал программировать значительно меньше, посвятив себя распространению идей свободного ПО.

Э́ндрю Стюарт Таненба́ум (англ. Andrew Stuart Tanenbaum; род. 16 марта 1944, Нью-Йорк, Нью-Йорк), иногда упоминаемый под псевдонимом AST или Papá Tanenbaumголландский учёный-компьютерщик американского происхождения, почётный профессор компьютерных наук в Амстердамском свободном университете в Нидерландах. Известен как автор MINIX (свободная Unix-подобная операционная система для учебных целей) и RFID-вируса. Автор книг по информатике, которые считаются стандартами в данной области. Сам он считает свою преподавательскую деятельность наиболее важной. Является главным разработчиком пакета «Amsterdam Compiler Kit».

Фре́дерик Ра́йнес (англ. Frederick Reines; 16 марта 1918, Патерсон, штат Нью-Джерси, США — 26 августа 1998, Ориндж, штат Калифорния, США)американский физик, профессор, лауреат Нобелевской премии по физике (1995) за открытие нейтрино.


🌐 Знаковые события

1936 — с конвейера Горьковского автомобильного завода сошла первая советская легковая автомашина — лимузин марки «М-1».


#Biography #Birth_Date #Events #16марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Раздел 8. Stream API и функциональный стиль в Java

Глава 5: Контракты. Почему equals, hashCode и иммутабельность — закон

(Практика): Саботаж и починка в «Библиотеке»

Создание «сломанного» класса Book

Создайте или обновите класс Book с минимальными полями:
public class Book {
private final String title;
private final String author;

public Book(String title, String author) {
this.title = Objects.requireNonNull(title, "title не может быть null");
this.author = Objects.requireNonNull(author, "author не может быть null");
}

public String getTitle() { return title; }
public String getAuthor() { return author; }

@Override
public String toString() {
return "Book[title=" + title + ", author=" + author + "]";
}
}


Частичная реализация equals (только по title)

Добавьте equals, сравнивающий только название:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Book)) return false;
Book book = (Book) o;
return title.equals(book.title); // Только title! Автор игнорируется.
}


Намеренно не добавляйте hashCode!

Демонстрация проблемы в Stream API

Проблема 1: distinct() «слипает» разные книги

Создайте тестовый метод в Library или Main:
public void demonstrateDistinctProblem() {
List<Book> books = Arrays.asList(
new Book("Война и мир", "Толстой"),
new Book("Война и мир", "Достоевский"), // Другой автор!
new Book("1984", "Оруэлл")
);

List<Book> distinct = books.stream()
.distinct()
.collect(Collectors.toList());

System.out.println("Исходный список: " + books.size() + " книг");
System.out.println("После distinct: " + distinct.size() + " книг");
System.out.println("Уникальные книги: " + distinct);
}


Ожидаемый вывод:
Исходный список: 3 книг
После distinct: 2 книг
Уникальные книги: [Book[title=Война и мир, author=Толстой], Book[title=1984, author=Оруэлл]]


Что произошло: distinct() использует hashCode и equals. Поскольку hashCode не переопределён, наследуется от Object — разные адреса памяти → разные хэши. Но equals говорит, что книги равны по названию.
На самом деле поведение непредсказуемо: distinct() внутри использует HashSet, который сначала сравнивает hashCode, потом equals. Если хэши разные (а они будут разные), элементы попадают в разные бакеты и никогда не сравниваются через equals. Толстой и Достоевский останутся оба!

Проверьте: Запустите код. Если обе «Войны и мира» остались — hashCode разные спасает. Если одна пропала — вы попали в коллизию (редко, но возможно).

Проблема 2: toMap и потеря значений

Создайте Map книга → количество на складе:
public void demonstrateToMapProblem() {
List<Book> inventory = Arrays.asList(
new Book("Война и мир", "Толстой"),
new Book("Война и мир", "Достоевский")
);

Map<Book, Integer> stock = inventory.stream()
.collect(Collectors.toMap(
Function.identity(),
b -> 10, // количество на складе
(a, b) -> a + b // слияние при коллизии
));

System.out.println("Map: " + stock);

// Попытка получить значение по «эквивалентной» книге
Book lookup = new Book("Война и мир", "Толстой");
System.out.println("Поиск " + lookup + ": " + stock.get(lookup));

Book lookupWrongAuthor = new Book("Война и мир", "Гоголь");
System.out.println("Поиск " + lookupWrongAuthor + ": " + stock.get(lookupWrongAuthor));
}


Анализ:
toMap использует hashCode для выбора бакета, equals для проверки существования ключа
Без переопределённого hashCode — разные объекты, разные бакеты
Даже если equals сказал бы «равны», HashMap не найдёт элемент из-за разных хэшей
lookup и lookupWrongAuthor оба вернут null или случайное значение
👍5
Проблема 3: groupingBy ломается
public void demonstrateGroupingProblem() {
List<Book> books = Arrays.asList(
new Book("Война и мир", "Толстой"),
new Book("Война и мир", "Достоевский"),
new Book("Война и мир", "Тургенев")
);

Map<Book, List<Book>> byBook = books.stream()
.collect(Collectors.groupingBy(Function.identity()));

System.out.println("Групп: " + byBook.size());
byBook.forEach((k, v) -> System.out.println(k + " -> " + v.size()));
}


Ожидаемо: 3 группы (все разные авторы) или 1 группа (если equals/hashCode считают их одинаковыми).
Фактически: Непредсказуемо из-за отсутствующего hashCode.


Исправление: корректные equals и hashCode

Правильная реализация по всем полям

Обновите класс Book:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Book)) return false;
Book book = (Book) o;
return title.equals(book.title) &&
author.equals(book.author); // Теперь оба поля!
}

@Override
public int hashCode() {
return Objects.hash(title, author); // Консистентно с equals
}


Контракт equals и hashCode:
Рефлексивность: x.equals(x) всегда true
Симметричность: x.equals(y) ⇔ y.equals(x)
Транзитивность: x.equals(y) и y.equals(z) → x.equals(z)
Консистентность: повторные вызовы дают тот же результат
С null: x.equals(null) всегда false
Критически: Если x.equals(y), то x.hashCode() == y.hashCode()

Проверка исправления

Повторите все демонстрации
public void demonstrateFixed() {
List<Book> books = Arrays.asList(
new Book("Война и мир", "Толстой"),
new Book("Война и мир", "Достоевский"),
new Book("1984", "Оруэлл")
);

// distinct() — теперь 3 уникальные книги
System.out.println("distinct: " + books.stream().distinct().count());

// toMap — теперь 3 записи
Map<Book, Integer> stock = books.stream()
.collect(Collectors.toMap(
Function.identity(),
b -> 10,
Integer::sum
));
System.out.println("Map size: " + stock.size());

// Поиск работает
Book lookup = new Book("Война и мир", "Толстой");
System.out.println("Найдено: " + stock.get(lookup));
}


Ожидаемый результат: 3 уникальных книги, Map с 3 записями, поиск находит значение.


#Java #для_новичков #beginner #stream_api #практика
👍4
Альтернативные стратегии equals/hashCode

Стратегия 1: Только неизменяемый идентификатор (ISBN)

Если Book имеет уникальный ISBN:
private final String isbn;  // уникальный, неизменяемый

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Book)) return false;
return isbn.equals(((Book) o).isbn);
}

@Override
public int hashCode() {
return isbn.hashCode();
}


Плюсы: Эффективно, надёжно. Минусы: Требует уникального поля.

Стратегия 2: Все поля

Плюсы: Корректно для value objects. Минусы: Медленнее, чувствительно к изменениям.

Стратегия 3: Не переопределять (только identity)

Если Book — entity с уникальным ID в базе данных, и вы никогда не сравниваете разные объекты с одинаковым ID:
// Нет equals/hashCode — используем Object

Плюсы: Просто. Минусы: distinct(), toMap, groupingBy работают по ссылке, не по содержимому.

Практические задания

Задача 1: воспроизвести все три проблемы

В проекте «Библиотека» создайте класс BrokenBook с equals только по title и без hashCode.

Продемонстрируйте:
distinct() «проглатывает» книги или нет (зависит от хэш-коллизий)
toMap создаёт больше записей, чем ожидается
groupingBy разбивает на неожиданное количество групп
Зафиксируйте наблюдения в комментариях.

Задача 2: исправить и сравнить

Создайте FixedBook с корректной парой equals/hashCode. Повторите те же операции.

Убедитесь, что:
Толстой, Достоевский, Тургенев — три разные книги с одинаковым названием
distinct() оставляет все три
toMap создаёт три записи
Поиск по ключу находит правильное значение

Задача 3: специфический equals для бизнес-логики

Добавьте в Library метод findByTitle(String title), который находит все книги с данным названием (независимо от автора).
Реализуйте через filter с кастомным предикатом, не через equals:
public List<Book> findByTitle(String title) {
return books.stream()
.filter(b -> b.getTitle().equals(title))
.collect(Collectors.toList());
}


Объясните, почему это безопаснее, чем менять equals для всего класса.

Задача 4: кеширование hashCode (звёздочка)


Если поля title и author неизменяемы, закешируйте hashCode:
private int hashCode;  // 0 = не вычислено

@Override
public int hashCode() {
int result = hashCode;
if (result == 0) {
result = Objects.hash(title, author);
hashCode = result;
}
return result;
}


Сравните производительность при частом использовании в HashMap для миллиона операций.

#Java #для_новичков #beginner #stream_api #практика
👍5
Что выведет код?

public class Task160326 {
public static void main(String[] args) {
String s1 = " hello ".trim();
String s2 = " hello ".strip();
String s3 = "\u2000hello\u2000".trim();
String s4 = "\u2000hello\u2000".strip();

System.out.println(s1.equals(s2));
System.out.println(s3.length());
System.out.println(s4.length());
}
}


#Tasks
👍2
Варианты ответа:
Anonymous Quiz
27%
true 5 5
13%
false 7 5
60%
true 7 5
0%
false 5 5
👍2
Что такое Profiling и для чего он нужен? 🤓

Ответ:

Profiling
— это процесс динамического анализа работающего приложения для измерения его производительности, использования памяти и поведения потоков.

Профилировщики (например, JProfiler, VisualVM, YourKit) собирают данные о:
CPU Profiling — какие методы потребляют больше всего процессорного времени;
Memory Profiling — какие объекты занимают память, поиск утечек памяти;
Thread Profiling — состояние потоков, поиск deadlock'ов.

Это незаменимый инструмент для оптимизации производительности и диагностики проблем в сложных приложениях.


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
История технологий сегодня — 17 марта

ℹ️ Кто родился в этот день

Влади́мир Па́влович Барми́н (4 (17) марта 1909 года, Москва — 17 июля 1993 года, там же) советский учёный, конструктор реактивных пусковых установок, ракетно-космических и боевых стартовых комплексов. Один из основоположников советской космонавтики. С 1947 года под руководством Бармина были разработаны стартовые комплексы для многих ракет конструкции Королёва: Р-1, Р-2, Р-11, Р-5, Р-5М — первой стратегической ракеты с ядерным боезарядом Р-5М. В 1957 году завершены работы над стартовым комплексом первой в мире межконтинентальной баллистической ракеты Р-7, которая вывела на орбиту Земли первый искусственный спутник Земли и первого космонавта Юрия Гагарина.

Готтлиб Вильгельм Даймлер (нем. Gottlieb Wilhelm Daimler); собственно Доймлер (Däumler; 17 марта 1834, Шорндорф, Королевство Вюртемберг, Германский союз — 6 марта 1900, Бад-Канштатт, Королевство Вюртемберг, Германская империя) — немецкий инженер, конструктор и промышленник. Совместно с Вильгельмом Майбахом Даймлер разработал один из первых автомобилей и несколько типов бензиновых двигателей внутреннего сгорания.


🌐 Знаковые события

1966 — первый пуск с космодрома Плесецк: ракетой-носителем «Восток-2» на орбиту Земли выведен искусственный спутник «Космос-112».


#Biography #Birth_Date #Events #17марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
[Совет по Java #016]

Тема: Ленивая загрузка в Hibernate (FetchType.LAZY) не работает после закрытия сессии.

Проблема: Hibernate использует шаблон ленивой загрузки для оптимизации производительности — связанные сущности подгружаются только при первом обращении к ним.

Однако для этого необходима открытая сессия (persistence context). Когда сессия закрывается (например, после завершения транзакции), доступ к неинициализированным прокси-объектам вызывает исключение LazyInitializationException.

Это классическая проблема при передаче сущностей на уровень представления (view) или при работе с ними вне транзакционного контекста.

Решение: Существует три основных подхода.

Первый — продлить транзакцию на весь сервисный метод, чтобы сессия оставалась открытой до полной обработки данных.
Второй — инициализировать все необходимые связи явно через JOIN FETCH в HQL/JPQL или через EntityGraph.
Третий — использовать паттерн Open Session in View (OSIV), который держит сессию открытой до завершения HTTP-запроса, но это антипаттерн для production из-за проблем с производительностью и риска утечек соединений.

@Entity
public class Order {
@Id private Long id;

@ManyToOne(fetch = FetchType.LAZY)
private User user; // ленивая загрузка

@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
private List<Item> items;
// геттеры/сеттеры
}

@Service
public class OrderService {

@PersistenceContext
private EntityManager em;

//Антипаттерн: сессия закрыта при доступе к ленивым полям
public Order badFind(Long id) {
Order order = em.find(Order.class, id);
// Транзакция закончилась, сессия закрыта
order.getUser().getName(); // LazyInitializationException!
return order;
}

//Решение 1: @Transactional на уровне сервиса
@Transactional
public Order goodFindTransactional(Long id) {
Order order = em.find(Order.class, id);
order.getUser().getName(); // Ок, сессия открыта
return order;
}

//Решение 2: JOIN FETCH в запросе
@Transactional(readOnly = true)
public Order findWithJoinFetch(Long id) {
return em.createQuery(
"SELECT o FROM Order o JOIN FETCH o.user WHERE o.id = :id", Order.class)
.setParameter("id", id)
.getSingleResult(); // user уже загружен
}

//Решение 3: EntityGraph
@Transactional(readOnly = true)
public Order findWithEntityGraph(Long id) {
EntityGraph<?> graph = em.getEntityGraph("Order.withUserAndItems");
return em.find(Order.class, id,
Collections.singletonMap("javax.persistence.fetchgraph", graph));
}
}

// DTO-подход — лучшая практика
@Data
class OrderDTO {
private Long id;
private String userName; // только нужные поля, без прокси
}

@Service
class OrderDTOService {
@PersistenceContext
private EntityManager em;

public OrderDTO findDTO(Long id) {
return em.createQuery(
"SELECT NEW com.example.OrderDTO(o.id, u.name) " +
"FROM Order o JOIN o.user u WHERE o.id = :id", OrderDTO.class)
.setParameter("id", id)
.getSingleResult(); // Никаких прокси, только данные
}
}


Объяснение: Корень проблемы — отделение уровня персистентности от уровня представления.

Самое надежное решение — не передавать сущности наружу, а преобразовывать их в DTO (Data Transfer Objects) внутри транзакции.

Если все же нужны сущности, то либо продлевайте транзакцию (@Transactional на весь метод контроллера — плохо), либо инициализируйте все необходимое до закрытия сессии. OSIV (spring.jpa.open-in-view=true) включен по умолчанию в Spring Boot — это удобно для разработки, но в production может держать соединения с БД долго и маскировать проблемы с производительностью.


#Java #советы
👍6