Какими свойствами должен обладать класс, чтобы быть сущностью (Entity) в Hibernate?

Ответ

Требования к классу-сущности в Hibernate совпадают со спецификацией JPA и включают:

Обязательные условия:

  1. Аннотация @Entity.
  2. Наличие поля с @Id (первичный ключ).
  3. Пустой конструктор (доступ public или protected).
  4. Класс не должен быть объявлен как final.
  5. Методы-аксессоры (геттеры/сеттеры) для персистентных полей не должны быть final.

Рекомендации и best practices:

  • Поля обычно объявляются как private.
  • Реализуйте методы equals() и hashCode(), основанные на бизнес-ключе (уникальном наборе полей, кроме @Id), а не на сгенерированном ID, так как ID присваивается после сохранения.
  • Используйте аннотации для маппинга: @Table, @Column, @OneToMany, @ManyToOne и т.д.

Пример сущности с бизнес-ключом:

@Entity
@Table(name = "products")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @Column(unique = true, nullable = false)
    private String sku; // Часть бизнес-ключа

    private String name;

    public Product() {}

    // equals и hashCode на основе sku (бизнес-ключ)
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Product product = (Product) o;
        return Objects.equals(sku, product.sku);
    }

    @Override
    public int hashCode() {
        return Objects.hash(sku);
    }
    // Геттеры и сеттеры...
}

Почему важен бизнес-ключ для equals/hashCode? Пока объект не сохранен в БД, его id равен null. Сравнение по id сломает контракт этих методов. Сравнение по неизменяемому уникальному полю (например, артикулу sku) решает эту проблему.

Ответ 18+ 🔞

Давай разберём эту хрень с сущностями в Hibernate, а то народ тут как будто в первый раз на лекцию по джаве пришёл, ей-богу.

Смотри, чтобы твой класс стал полноценной сущностью, от которой Hibernate не сломается в припадке, нужно соблюсти несколько простых, но обязательных правил. Не сделаешь — получишь MappingException прямо в ебало, и будешь потом гадать, че не так.

Что ОБЯЗАТЕЛЬНО нужно, блядь:

  1. @Entity сверху класса. Без этой аннотации Hibernate на твой класс посмотрит как баран на новые ворота и проигнорирует нахуй. Это как паспорт для таблицы в БД.
  2. Хотя бы одно поле с @Id. Это первичный ключ, чувак. Без него — пиздец и анархия, база данных просто не поймёт, как отличать одну запись от другой. Это как лицо у человека, только для строки в таблице.
  3. Пустой конструктор. public или protected — не важно, главное, чтобы он был. Hibernate сам создаёт объекты через рефлексию, и ему нужен этот конструктор, чтобы не еб*ться. Если его нет — готовься к NoSuchMethodException.
  4. Класс НЕ должен быть final. Hibernate любит создавать прокси-объекты (для ленивой загрузки, например), а для этого он наследуется от твоего класса. Если класс final — наследоваться не получится, и Hibernate обидится, как сука.
  5. Геттеры и сеттеры НЕ должны быть final. По той же самой причине — для прокси он их может подменять своей магией. Сделаешь final — магия не сработает.

А теперь про то, как делать НЕ как последний лузер:

  • Поля, как правило, делают private. Это просто хороший тон, хотя Hibernate может и в поля напрямую лезть, если ему разрешить.
  • Вот это важно, блядь, слушай сюда! Все эти equals() и hashCode() — реализуй их на основе бизнес-ключа, а не на сгенерированном id! Почему? Да потому что id у нового, ещё не сохранённого объекта — null, ёпта! Представь, добавляешь ты два одинаковых продукта в HashSet, а у них id = null. По логике equals() они будут равны? Да нихуя! Контракт методов сломается, и будет тебе больно и неприятно. Бизнес-ключ — это какое-то уникальное поле (или комбинация полей) в рамках твоей логики, типа артикула (sku) или email пользователя.

Смотри, как это выглядит в коде, чтобы не быть мудаком:

@Entity
@Table(name = "products") // Говорим, в какой таблице храниться
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE) // ID генерится базой
    private Long id; // Пока не сохранил — id = null, помни это, кретин!

    @Column(unique = true, nullable = false) // Артикул уникальный и обязательный
    private String sku; // Вот он, наш спаситель — бизнес-ключ!

    private String name;

    public Product() {} // Обязательный пустой конструктор, блядь!

    // equals и hashCode на основе sku, а не на основе ебучего id!
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Product product = (Product) o;
        return Objects.equals(sku, product.sku); // Сравниваем по артикулу!
    }

    @Override
    public int hashCode() {
        return Objects.hash(sku); // Хэш тоже по артикулу!
    }
    // Дальше идут стандартные геттеры/сеттеры, их я тут за тебя писать не буду, не маленький...
}

Итог, блядь: Сделал @Entity, @Id, пустой конструктор, не final класс — уже молодец. Но если ещё и equals/hashCode на бизнес-ключе напишешь — будешь вообще красавчик, и коллекции у тебя не поедут в разнос. Всё просто, как три копейки.