Comments 18
Ваш вариант с hashCode от класса хоть и работает и даже все тесты закрывает, но он будет бесполезен. Все равно, что указать 0 или любое случайное число. Хеш код используется, например, в HashMap, чтобы можно было за почти бесплатно положить его в случайную ячейку памяти и потом за такую же стоимость получить его оттуда. В вашем варианте никаким хешмапам не светит нормальная эффективность, потому что они постоянно будут уходить в linear probing и проверять вообще все элементы на равенство с искомым.
Хотя конечно с трудом представляю использование моделей в качестве ключа ассоциативного массива, я когда работал с подобным, всегда однозначно хранил обьекты по их айди и этого хватало. Возможно, я не понял намерений автора статьи.
Хорошее замечание, меня тоже это во многом смущало, однако в вариантах от Jpa Buddy /Amplicode и джава чемпиона тоже самое)
На это высказался и провел свои замеры Vlad Mihalcea здесь: https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
Как показывает практика, сложные операции в любом случае лучше делать на стороне базы данных, а для относительно небольших коллекций все также быстро работает, как и показано в тестах с contains.
Если у вас есть вариант получше, я с удовольствием приму его на вооружение.
Ваш Vlad пишет, что вырождённый вариант хэшкода, противоречащий самому смыслу его существования, может использоваться только в случае с JPA, где маленькие коллекции, есть (долгое) чтение из БД, и есть UI, с которым взаимодействует пользователь. И то я не уверен, верно ли это (использование константного хэша) даже в случае с JPA.
Но вы-то берете JPA лишь как пример. Пример, на котором показывается некий общий случай. А в общем случае возвращать константу в качестве хэша это просто бред.
Ну и сама статья. Сначала какие-то собачки с кошечками, а потом бац, пример из Hibernate с кучей аннотаций и прочим. Вы уже определитесь, для кого текст пишете. Люди, понимающие код для Hibernate, представляют себе тонкости equals и hashCode. А люди, которым нужны примеры с собачками, ничего не поймут в дальнейших примерах кода.
Спасибо за ваш отзыв
В начале статьи я подмечаю, что объекты по сути делятся на два типа: модели и сущности. Модели сравниваем по всем полям, они неизменяемые.
И у меня черным по белому(или наоборот 🤔) написано про сравнение сущностей! Касаемо вырожденного варианта по логике это не только для JPA, а для всего, что может генерировать первичный ключ на стороне базы, а это почти внезапно все ORM для всех языков.
Даже без ORM, если мы отделяем слой того же репозитория и работаем именно как с изменяемымм объектами у нас будут сущности и у нас может возникать перерасчёт хэша при изменении поля id.
Да, в идеальном мире у определения сущность, должен быть уникальный идентификатор неизменяемый, но пригенерации в базе это можно достичь только одним путем - натуральный идентификатор. И тут либо JPA и почти все ORM с изащренной идеологией, либо DDD. Тут каждый выбирает для себя сам! Я лишь провел свой анализ и поделился своим мнением.
В целом это то можно избегать (изменяемый id) и использовать либо модели(те же проекции к ним относятся), либо генерацию ключа на стороне приложения и я об этом также написал!
Я нигде не возводил свой вариант как эталон для всего и в самой статье рекомендую использовать именно натуральный идентификатор по возможности для тех же именно сущностей.
Собачки с котиками как раз, чтобы доходчиво показать, что это проблема не только хибера и JPA, а любого сравнения изменяемых объектов!
А кто работает с хибером, все об знают)
Если бы...
Создаётся впечатление, что вы поверхносто пробежали статью. Если это не так, подскажите, что именно вам было не понятно? Или может быть какие-то мои фразы сбили столку?
Да вот именно, что после каких-то собачек много где идут какие-то с воздуха взятые, нетривиальные и не очевидные утверждения, поэтому приходилось просто побегать их, чтобы не зависнуть. Так не пишут. Ну и заголовок - не "на примере JPA", хотя теперь я понимаю, в каком смысле использовалась слово, а просто "в JPA". Далее, я не работал с JPA, но представьте себе кэш на Java, который читает всю таблицу и хранит её в памяти. Использовать в качестве hashCode константу? Бред.
В общем, в самом начале надо было сразу и очень чётко написать - рассмотрим пример, где хэш вычисляется совершенно нелогично и против всех правил. И далее по тексту. Чтобы у читателей было чёткое понимание, что это пример-исключение, а не пример-пример.
Касаемо кэша, кто вообще в эпоху кубера и децентрализованных приложений делает кэш в оперативной памяти для сущностей?)
Да и примеров 7, в 3 из которых нет описанной вами проблемы.
Честно говоря не понимаю ваших возмущений.
Статья начинается со слова "ЛучшИЕ", выбирайте любой по ситуации. Сами ситуации описаны.
Соглашусь с вами, что контекст повествования может быть запутанным и мне нужно поработать над этим, но пожалуйста не грубите. Нелогично и против всех правил, которые вы не приводите это как минимум конструктивно. А сами правила и вправду есть и это вторая ссылка в источниках в статье).
Я грублю? Ткните пальцем, плиз. Буду знать, на будущее)
очень чётко написать - рассмотрим пример, где хэш вычисляется совершенно нелогично и против всех правил
Вы правы, возможно на грубость и не тянет.
Для меня это воспринимается как: "Лучше бы не писал статью" и возможно это только мои проблемы.
При той же подготовке, вообще нигде нет полного анализа всех вариантов реализации. Что всех, хотя бы основных. Многие пишут абстракто: "Не делай так". А вопрос, как тогда? Описанные выше проблемы магическим образом не решаться, если я не буду писать так)
Поэтому и есть это статья, как попытка показать как надо и где именно.
А вообще, лучшая практика - это не класть новый объект в любой контейнер, основанный на хэш-таблицах, пока он не сохранился в БД и не получил свой Id. Делов-то)
Спасибо за ваш вариант, добавил упоминание о вашей версии.
Здесь я с вами отчасти соглашусь, но тогда связи один ко многим и многие ко многим, к сожалению, превращаются в бойлерплейт код и ограничивают возможности ORM фреймворков.
Проблема настолько стандартна, что не верю, чтобы она уже стандартно и не решалась. А если руками, то не создавайте дочерних объектов, пока не сохраните родительский объект в БД. Опять же, есть много встроенных в БД генераторов id, то есть, id можно получить до сохранения, не используя атрибут autoincrement в базе для ключа. Но это всё равно одно обращение к БД, поэтому я лучше бы просто сохранил новый объект сначала.
В том то и дело, проблема стандартна и сложна. Для связей с композицией так не работает. Должна быть единая точка сохранения родителя и ребенка и она в вашей версии будет писаться вручную, а не использовать готовые варианты, которые просты и реализуются банально быстрее, что для типового приложения важнее.
Это история про компромиссы, нельзя усидеть на всех стульях сразу.
Ваш вариант можно и точно где-то нужно брать на вооружение, но за своей простотой он требует глубокого понимания кода и понимание, что везде так делать наоборот неэффективно.
Вы возможно и имеете понимание, что серебрянной пули не существует, но имеет ли его типовой разработчик?
Самый простой способ работать с equals и hashCode - избегать их по возможности.
Хотите найти объект в списке? Используйте предикат.
Хотите использовать множество? Возможно подойдёт синтетический ключ или, быть может, на самом деле, вы не хотите. Или подойдут деревья с компаратором.
И только если вы действительно проектируете ключи для hashMap или тривиальные сущности с естественным сравнением (какой-нибудь класс Complex или Pair) - тогда да, разумно определить методы equals/hashCode. Но кажется, это не ваш случай.
Этот вариант самый первый в статье со схожим выводом.
Спасибо вам, за то, что несколько расширили данный сценарий примерами, как можно обойти использование сетов для сущностей в императивном стиле.
Также чуть дополню. Когда нам нужно одну огромную коллекцию отфильтровать по другой, можно ещё отдельное множество под айдишники завести и в цикле по сущностям вызывать contains. Сам пользуюсь, кайфую.
Сами сеты на самом деле нормально можно использовать во многом только для многие ко многим и один ко многим, поскольку в том же хибере и ряде ORM есть оптимизация при генерации запросов. Для этих целей сеты даже с вырожденным hashCode прекрасно работают (ибо под капотом там тоже самое дерево получается), сам на работе применяю и все летает, кода мало.
Лучшие практики реализации equals() и hashCode() на примере JPA сущности