Сейчас будет ещё одно чисто профессиональное «открытие чайника в Java».
Сравнение сущностей Hibernate (Hibernate entities) вообще вопрос достаточно нетривиальный. Кто-то предлагает сравнивать по ключу, но это подходит только для чистых и персистирующих объектов. Кто-то предлагает сравнивать по ключу и состоянию, но это не подходит для новых объектов, ещё не персистирующих. Лично мне пока навскидку кажется наиболее правильным сравнение по ключу, бизнес-полям и, если есть, по полям ManyToOne и OneToOne, однако, непонятно, что делать, если, скажем, у двух новых объектов разница только в OneToMany или в ManyToMany.
Ясно одно – при всём великолепии такого инструмента, как Lombok, его вариант EqualsAndHashCode нельзя применить без тонкой подстройки. А подстройка заключается в том, чтобы исключить (при помощи атрибута onlyExplicitlyIncluded в аннотации EqualsAndHashCode и явного применения аннотации EqualsAndHashCode.Include к «кашерным» полям) поля-коллекции.
Что происходит, если мы этого не сделаем?
А происходит то, что FetchType.LAZY перестаёт по факту работать.
Допустим, у нас есть сущность Author и сущность Book. Связь между ними должна быть ManyToMany, поскольку у одного автора, как правило, больше одной книги, и одна книга может иметь несколько авторов. На уровне СУБД они связаны через таблицу связей, на уровне же объектной модели у Author есть поле (я предполагаю, что при помощи Lombok мы из него делаем свойство легко и непринуждённо) Set<Book> books, а у Book есть Set<Author> authors.
Как оно должно работать в условиях FetchType.LAZY?
Мы подгрузили из базы, например, интересующую нас книгу. При этом авторы не подгружаются, пока мы не захотели на них посмотреть. Как только мы захотели взглянуть на авторов, они подгружаются, запузыриваются в поле authors, но их поля books опять же не подгружаются, пока мы не захотим посмотреть, что ещё кто из них написал. То есть, пока дополнительные запросы нам не нужны, они и не делаются. В этом и состоит польза FetchType.LAZY.
Что происходит, если мы используем EqualsAndHashCode от Lombok без подстройки? Когда очередной подгруженный автор засовывается в authors, класс Set<T> вычисляет его хэш, чтобы проверить, нет ли там такого уже (а в худшем случае потом ещё и на равенство проверяет, если вдруг хэш-коды совпали). Для этого, при использовании варианта по умолчанию, он задействует все поля. Включая поля-коллекции. Соответственно, в процессе просмотра одной книги подсасываются ВСЕ книги ВСЕХ соавторов (а если ещё и у книг хэш-код берётся по всем полям, включая коллекции, то дело легко может закончится подсасыванием всей библиотеки в конечном итоге, причём, что особенно доставляет, не одним запросом, а по нескольку запросов на каждую сущность, система просто умрёт в процессе). Как говорится, «знаю одного дебила, который так сделал» (произносится, задумчиво глядя в зеркало, ага).
Мораль.
Hibernate – офигенная вещь. Lombok – офигенная вещь. Но нужно представлять, как они устроены. С той именно целью и задают на собеседованиях каверзные вопросы, которые, казалось бы, взяты не из практики, а из теории.
Профессиональное. В процессе изучения Java.
2 минуты
6 декабря 2022