У меня есть объекты JPA, где некоторые свойства аннотируются @Transient
.
Если я использую эти свойства в equals/hashCode/toString
методы?
Моя первая мысль НЕ, но я не знаю почему.
Случай с toString()
отличается, вы можете делать все, что хотите с toString()
, поэтому я рассмотрю только equals()
(и hashCode()
).
Во-первых, правило: если вы хотите хранить объект в List
, Map
или Set
то это требование, чтобы equals
и hashCode
были реализованы так, чтобы они подчинялись стандартному контракту, как указано в документации.
Теперь, как реализовать equals()
и hashCode()
? "Естественной" идеей было бы использовать свойства, отображенные как Id
, как часть equals()
:
public class User {
...
public boolean equals(Object other) {
if (this==other) return true;
if (id==null) return false;
if ( !(other instanceof User) ) return false;
final User that = (User) other;
return this.id.equals( that.getId() );
}
public int hashCode() {
return id==null ? System.identityHashCode(this) : id.hashCode();
}
}
К сожалению, у этого решения есть основная проблема: при использовании сгенерированных идентификаторов значения не присваиваются, пока сущность не станет постоянной, поэтому если переходная сущность будет добавлена в набор
до сохранения, ее хэш-код изменится, пока она находится в наборе
, и это нарушит контракт набора
.
Таким образом, рекомендуемый подход заключается в использовании атрибутов, которые являются частью бизнес-ключа, т.е. комбинации атрибутов, уникальной для каждого экземпляра с одинаковым идентификатором базы данных. Например, для класса User это может быть имя пользователя:
public class User {
...
public boolean equals(Object other) {
if (this==other) return true;
if ( !(other instanceof User) ) return false;
final User that = (User) other;
return this.username.equals( that.getUsername() );
}
public int hashCode() {
return username.hashCode();
}
}
Справочная документация Hibernate резюмирует это следующим образом:
"Никогда не используйте идентификатор базы данных для реализации равенства; используйте бизнес-ключ, комбинацию уникальных, обычно неизменяемых, атрибутов. Идентификатор базы данных изменится, если переходный объект станет постоянным. Если переходный экземпляр (обычно вместе с отделенными экземплярами) содержится в
наборе
, изменениехэш-кода
нарушает контрактнабора
. Атрибуты для бизнес-ключей не обязаны быть такими же стабильными, как первичные ключи базы данных, вы должны гарантировать стабильность только до тех пор, пока объекты находятся в одном и том же Наборе." - 12.1.3. Учет идентичности объектов"Рекомендуется реализовать
equals()
иhashCode()
с использованием равенства бизнес-ключей. Равенство бизнес-ключей означает, что методequals()
сравнивает только те свойства, которые образуют бизнес-ключ. Это ключ, который будет идентифицировать наш экземпляр в реальном мире (естественный ключ-кандидат)" - 4.3. Реализация equals() и hashCode()
Итак, вернемся к первоначальному вопросу:
Атрибуты @Transient
, скорее всего, не являются частью такого ключа. List
, Map
, Set
. Два типичных использования @Transient
и transient
, о которых я знаю, - это их использование либо для вещей, которые не могут быть сериализованы / сохранены (например, дескриптор удаленного ресурса ) или вычисленные свойства, которые могут быть восстановлены из других.
Для вычисленных данных нет смысла использовать их в отношении равенства ( equals / hashCode
), потому что они будут избыточными.Значение вычисляется из другого значения, которое уже используется в равенстве. Однако все же имеет смысл распечатать их в toString
(например, базовая цена и соотношение используются для вычисления фактической цены).
Для несериализуемых / сохраняемых данных это зависит. Я могу представить дескриптор ресурса, который нельзя сериализовать, но вы все равно можете сравнить имя ресурса, которое представляет дескриптор. То же самое для toString
, возможно, будет полезно распечатать имя ресурса дескриптора.
Это были мои 2 цента, но если вы объясните свое конкретное использование @Transient
, кто-нибудь может дать лучший совет.