Комментарии 24
Вроде ж было уже похоже
Да и от вашей компании
Да, согласен, похожая статья уже была. Основные тезисы тут остались примерно такими же, но добавили больше примеров, а также более детально рассмотрели работу методов equals()
и hashCode()
. Ну и новый инструмент заюзали, чтобы решить все проблемы с которыми столкнулись :)
И все так же ломаете мапы на проде. Уж лучше сделать чтобы hashCode исключение кидал. Тогда мапа хотя бы упадет сразу, а не в черную пятницу с тратами миллионов денег бизнеса.
Если вам не трудно, обьясните, пожалуйста, почему мапа должна упасть? Я не работал с мапами, что бы понять в чём здесь подводный камень, но для себя хотел бы знать, буду благодарен, если проясните)
Мапа в которой лежат объекты из статьи имеет сложность операции get O(n).
Мапа в которой лежат объекты из статьи имеет сложность операции get O(n).
Тут несколько пунктов
Вы наглядно показали, почему не надо использовать энтити в качестве ключей мапы. Если есть какой-то код в котором энтити используются как ключи мапы, то почти всегда там можно и нужно в качестве ключей использовать не сами энтити, а их Id.
Правда, есть один случай, когда разработчик вынужден использовать Entity как ключи мапы - это HashSet у которого под капотом HashMap. Я говорю о случае, когда на стороне many у one-to-many в hibenate тип коллекции - Set. По моему мнению, такого лучше избегать и использовать List.
А если разработчик всё-таки хочет использовать Set, то в случае связей один ко многим это не создаюёт проблем. Прежде всего, обычно разработчик просто перебирает все элементы коллекции и квадратичной сложности не возникает. А ещё сторона many в маппинге one-to-many не должна содержать много элементов. Если элементов много, то лучше эту связь не мапить. Поэтому вред от O(n) на добавление нового элемента относительно небольшой.
Спасибо за статью, познавательно.
Но JPA != Hibernate.
Как бы существуют и другие JPA runtime-ы (EclipseLink, OpenJPA)
И вообще говоря, не то чтобы это хороший стиль без острой необходимости, использовать runtime-зависимый код.
И как тогда реализовать корректно equals , без использования HibernateProxy?
Добрый день, спасибо)
Да, про то что JPA != Hibernate – хорошее замечание, согласен, что стоило это отметить в начале статьи. Учту на будущее :)
Для EclipseLink Amplicode генерирует реализацию методов придерживаясь схожего принципа, но используя org.springframework.data.util.ProxyUtils#getUserClass(java.lang.Object)
: @Override
public final boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || ProxyUtils.getUserClass(this) != ProxyUtils.getUserClass(o)) {
return false;
}
Climber climber = (Climber) o;
return getId() != null && Objects.equals(getId(), climber.getId());
}
@Override
public final int hashCode() {
return ProxyUtils.getUserClass(this)
.hashCode();
}
Хорошая статья, тоже натыкалась на эти проблемы. Особенно equals в коллекциях.
Вместо @Data лучше использовать Getter Setter.
Есть плагин для IDEA - JPA Buddy, он как раз умеет генерировать equals и hashCode почти также как и в данной статье.
Статья интересная, нужно ещё прощупать руками, чтобы понять до конца.
Чуток "причесал" вашу реализацию.
Но вообще лично я придерживаюсь подхода, который мне кажется более строгим:
entities не должны покидать границы транзакций
тогда и
equals()/hashCode()
переопредлять не нужно
Ну и посколько статья про Lombok:
как вы указываете,
toString()
можно только с явно указанными полями@Builder
считаю непременимым для Entity вообще, нужен явный конструктор безid
, чтобы гарантировать бизнес-целостностьк
@NoArgsConstructor
лучше добавитьaccess=AccessLevel.PROTECTED
, JPA достаточно, а разработчикам он не нужен по причине выше@AllArgsConstructor
скорее не нужен, потому как он включаетid
, а он обычно генерируетсяsequence
@Setter
тоже не нужен всё по той же причине необходимости бизнес-целостности@Data
отпадает, уже объяснено выше
Итого от Lombok-a остаются только `@ToString(onlyExplicitlyIncluded = true)` c явно указанными полями.
И @Getter
. Геттеры нужны для а) конвертации entity в DTO, б) для тестов. В бизнес коде их быть не должно.
Простите, я, возможно, не так много опыта имею в разных проэктах, поэтому исходя из своего опыта, я не стыкался с проблемой изменяемости обьекта, мне нравится ломбоковский @Builder
, и я не стыкался с нарушением бизнес-целостности, о которой вы говорите. Единственный случай, который мне приходит в голову - добавление нового поля в сущность, в таком случае, существует шанс, что это поле не будет добавлено в билдер, но благодаря крутым IDE можно найти все использования билдера, и будет трудно не добавить это поле) И как быть без сеттеров, если, пример: платформа для разработки кастомных продуктов. Клиент, дизайнер, производитель.
Клиент создает запрос с базовыми характеристиками продукта: тип (напр. кресло), размеры, стиль, бюджет
Дизайнер дополняет этот продукт полями: цвета, материалы, текстуры, 3-д модель продукта.
Производитель дополняет этот продукт полями: время на изготовление, цена, точные материалы, логистические параметры, какой отдел будет заниматься производством.
Самым банальным будет сделать базовый клас Product со всем набором характеристик, один эндпоинт на апдейт, и в зависимости от роли и статуса продукта, вызывать тот или иной метод из сервиса, который будет дополнять обьект соответствующими полями. В этом случае, если я правильно понимаю, без наличия сеттеров, придётся иметь несколько конструкторов в классе Product, и в соответствующем методе использовать соответствующий конструктор, пересоздавая обьект из прошлой итерации, полученный из базы данных. Или же, мы можем избежать создания разных конструкторов, используя сеттеры(или ломбоковский билдер), что, как мне кажется, удобнее. Пока я это писал, я подумал, что действительно, нет гарантии, что кто-то из разработчиков забудет добавить поле в сеттер, так как проэкт запустится, если не добавить новое поле в обьект, но он упадет, если в конструктор не передали недостающее поле. Но, опять таки, пользуясь силами IDE, сложно будет не добавить поле, на крайняк, для таких случаев, есть код ревью.
Я выдумал такую ситуацию в образовательных целях, если кто-то решится мне помочь набратся новых знаний, если кто-то поделится своим личным опытом, направив меня в нужную сторону, я буду очень вам благодарен.
Вопрос тот же, какое преимущество вот в таком бизнес-кейсе будет у иммютабл обьектов над мьютабл? Может, вам так же пришла в голову архитектура этого решения по-интереснее, буду так же рад расширить свои знания проэктирования ❤️
Аннотация
@Data
от Lombok включает в себя аж 6 аннотаций:
Наверно, все же 5? :)
@Valueв состав @Data не входит.
Миллион возможностей выстрелить себе в ногу с Hibernate.
Не видел ни одного большого и серьезного продукта, который бы в итоге от него не отказался
Вполне объяснимо. Hibernat нарушает принципы Дяди Боба - затаскивает детали реализации в entity и соответсвенно, в слой бизнес-логики.
Собственно, вся статья показывает, как детали реализации hibernat конфликтуют с деталями ломбока.
Я, возможно, в глаза долблюсь, но я не вижу способа перейти на гитхаб с проектом. Там как будто бы есть ссылка под спойлером в начале статьи, но она не кликабельна, и даже в сорсах страницы я ничего не нашел. Можете исправить это, пожалуйста? :)
Спасибо! Я поправил в теле статьи, на всякий случай ещё сюда продублирую: https://github.com/Amplicode/amplicode-tutorials/tree/main/lombok-pitfalls
Спасибо :)
Действительно очень интересные примеры для поиграться. Однако, я одного никак не пойму: а какую можно придумать более real life ситуацию с PersistenceContext и дефолтной реализацией hashCode()? Конкретно этот пример с detach() все-таки несколько искусственный и специально подобран для демонстрации
Не используйте Lombok с JPA, пока не прочтете эту статью