Какими свойствами должен обладать класс, чтобы быть Entity в JPA?

Ответ

Класс, чтобы быть Entity в JPA (Java Persistence API), должен соответствовать следующим обязательным требованиям:

  1. Аннотация @Entity — обязательная метка класса как сущности.
  2. Пустой конструктор (может быть protected) — требуется для рефлексии и создания прокси.
  3. Класс не должен быть final — иначе Hibernate не сможет создать прокси для ленивой загрузки.
  4. Наличие первичного ключа — поле с аннотацией @Id (может быть составным через @EmbeddedId).
  5. Поля должны быть private/protected с геттерами/сеттерами — для доступа через отражение.
  6. Методы доступа (геттеры/сеттеры) не должны быть final — по той же причине, что и класс.

Пример Entity-класса:

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "user_name", nullable = false, length = 100)
    private String name;

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

    // Геттеры и сеттеры (не final)
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

Почему эти правила важны:

  • Пустой конструктор и не-final класс/методы нужны для работы механизма проксирования Hibernate, который реализует ленивую загрузку (Lazy Loading).
  • Приватные поля с геттерами/сеттерами обеспечивают инкапсуляцию и дают Hibernate контролируемый доступ к данным через отражение.
  • @Id однозначно идентифицирует запись в базе данных.

Ответ 18+ 🔞

Ну, ты смотри, чтобы твой класс стал полноценной Entity в этой JPA-вотчине, ему надо соблюсти несколько правил, как в строгом монастыре. Не просто так, а чтобы фреймворк не взбесился и не начал кидаться исключениями.

Вот, блядь, свод неписаных, но охуенно важных законов:

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

Вот, смотри, как это выглядит в дикой природе, на примере какого-нибудь User:

@Entity
@Table(name = "users") // Говорим, что в таблице "users" валяться будем
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // ID само растёт, как на дрожжах
    private Long id;

    @Column(name = "user_name", nullable = false, length = 100) // Вот так поле к колонке привяжем
    private String name;

    // Вот он, священный пустой конструктор! Без него — пиздец.
    public User() {}

    // И вот эти самые геттеры с сеттерами. Не final, заметь.
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

А теперь, сука, почему это всё так, а не иначе?

  • Пустой конструктор и не-final — это всё ради магии ленивой загрузки (Lazy Loading). Hibernate создаёт прокси-объекты (подставные куклы), которые наследуются от твоего класса. Если класс или методы final — наследоваться не получится, и вся магия нахуй сломается. Прокси — они хитрожопые, им свобода нужна.
  • Приватные поля с геттерами — это чтобы не светить кишки направо и налево. Инкапсуляция, мать её! Hibernate через рефлексию аккуратно в сеттер заходит и значение кладёт, а не напрямую в поле плюёт.
  • @Id — это вообще основа основ. Как в армии — без жетона ты никто. Без первичного ключа запись в базе не идентифицировать, связывать таблицы — нихуя не получится.

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