Библиотека Hibernate устроена так, что любой объект, хранимый в базе данных, должен иметь идентификатор. В теории, идентификатор, он же первичный ключ соответствующей таблички в БД, может быть как естественным (то есть в качестве идентификатора можно взять какое-то уникальное поле у объекта), так и суррогатным. Но на практике, чаще всего встречается второй случай. То есть у каждого объекта есть вот такое поле:
private Integer id;
Это поле по определению уникально. Это очень полезное свойство. И им можно воспользоваться в методах equals и hashCode. И чтобы не дублировать код, я обычно описываю базовый класс с полем id и методами equals и hashCode.
public class AbstractEntity implements Serializable
{
public Integer id;
protected AbstractEntity()
{
}
protected AbstractEntity(int id)
{
this.id = id;
}
public Integer getId()
{
return id;
}
@Override
public int hashCode()
{
return id != null ? id : super.hashCode();
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (!(o instanceof AbstractEntity)) return false;
// hibernate(cglib) создает свои классы
// и getClass() == o.getClass()
// выполняется не корректно не всегда
if (!o.getClass().isAssignableFrom(getClass()) &&
!getClass().isAssignableFrom(o.getClass()))
return false;
AbstractEntity entity = (AbstractEntity) o;
return id!= null && id.equals(entity.id);
}
}
hashcode очень простой и быстрый. Он возвращает идентификатор для существующих объектов и вызывает супер-функцию для новый объектов, идентификатор для которых еще не проставлен. Такой алгоритм обеспечивает уникальный хэш для разных объектов и быстрое выполнение операций.
equals для начала сверяет равенство ссылок. Затем мы проверяем что объекты относятся к одному типу. Такая проверка необходима в абстрактном классе, от которого будет наследоваться множество классов. И если бы её не было, то разные сущности с одинаковым id были равны, что не верно. Использование равенства в данном случае опасно, так как hibernate создает proxy-объекты которые наследуются от классов сущностей.
После проверки типа остается проверить равенство идентификаторов. Не стоит забывать что у новых объектов идентификатор null. И для новых объектов применима следующая логика:
- если один из сравниваемых объектов имеет идентификатор, а другой нет, то они не равны, так как один объект уже храниться в базе, а другой нет
- если оба объекта новые, то они равны, если ссылаются на один и тот же объект, что обеспечивается первой строчкой нашего метода.
В принципе, такой базовый класс можно использовать и с xml-маппингом и hibernate annotation/JPA. В первом случае, когда мы будем описывать уже конкретную сущность, нам нужно будет указать тэг id
<class name=”ConcreteClass”>
<id name="id">
...
</class>
А вот, для случая hibernate annotation/JPA нам придется добавить аннотаций в исходный класс. Например, вот так:
@MappedSuperclass
public class AbstractEntity implements Serializable
{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "ID", nullable = false)
public Integer id;
}
Но такой вариант подойдет только если в разных таблицах идентификаторы имеют совершено одинаковое описание - то есть совпадает имя, способ генерации и т.п. Если же поля с идентификаторами в табличках различаются, хотя бы, именем, то описывать идентификатор нужно в классе сущности. Чтобы при этом сохранить наш базовый класс, мы можем схитрить и обращаться к полю id не напрямую, а через абстрактный метод. В этом случае, даже аннотировать базовый класс не нужно:
abstract public class AbstractEntity implements Serializable
{
abstract public Integer getId();
@Override
public int hashCode()
{
return getId() != null ? getId() : super.hashCode();
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass())
return false;
AbstractEntity entity = (AbstractEntity) o;
return getId() != null &&
getId().equals(entity.getId());
}
}
А сами сущности будут выглядеть примерно так:
@Entity
public class SomeEntity extends AbstractEntity
{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "SOME_ID", nullable = false)
public Integer id;
// other fields
@Override
public T getId()
{
return id;
}
// other setters and getters
}


Тема: