Базовый класс для сущностей Hibernate и JPA.

Библиотека 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. И для новых объектов применима следующая логика:

  1. если один из сравниваемых объектов имеет идентификатор, а другой нет, то они не равны, так как один объект уже храниться в базе, а другой нет
  2. если оба объекта новые, то они равны, если ссылаются на один и тот же объект, что обеспечивается первой строчкой нашего метода.

В принципе, такой базовый класс можно использовать и с 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

}