Сущности и ORM: @Entity, @Id и маппинг таблиц

JPA связывает мир объектов Java с миром таблиц SQL: класс становится таблицей, поле — колонкой, экземпляр — строкой. Это и есть ORM.
Суть: @Entity помечает класс как таблицу, @Id — первичный ключ, @GeneratedValue — автогенерацию id. В Spring Boot 3 все эти аннотации живут в пакете jakarta.persistence.

База данных хранит строки в таблицах, а Java работает с объектами. Между ними пропасть, которую вручную закрывать утомительно: писать SQL, читать ResultSet, раскладывать значения по полям. ORM (Object-Relational Mapping) автоматизирует этот перевод. В мире Spring стандарт ORM — это JPA, а его самая популярная реализация — Hibernate.

Сущность

import jakarta.persistence.*;

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

    @Column(unique = true)
    private String email;

    private int age;

    // геттеры, сеттеры, конструкторы
}

Разберём аннотации: @Entity делает класс управляемой сущностью; @Table задаёт имя таблицы; @Id отмечает первичный ключ; @GeneratedValue поручает генерацию id базе; @Column уточняет свойства колонки (обязательность, длина, уникальность).

Важно про namespace: в Spring Boot 3 импорт — jakarta.persistence.*, а не javax.persistence.*. Если в туториале вы видите javax — это устаревший Boot 2.

Как работает под капотом

При старте Hibernate сканирует классы с @Entity и строит карту отображения: какому полю какая колонка соответствует, какой тип, какие ограничения. Дальше, когда вы сохраняете объект, Hibernate сам генерирует INSERT, а при чтении — раскладывает строки в объекты.

  Класс User (@Entity)        Таблица users
  ┌───────────────┐          ┌───────────────────────┐
  │ Long id       │  <-->    │ id    BIGINT  PK       │
  │ String name   │  <-->    │ name  VARCHAR(100)     │
  │ String email  │  <-->    │ email VARCHAR  UNIQUE  │
  │ int age       │  <-->    │ age   INT              │
  └───────────────┘          └───────────────────────┘
  объект (экземпляр)  <-->  строка таблицы

Смоделируем маппинг «объект ↔ строка» вручную:

# ORM-mapping: object <-> table row
mapping = {"id": "id", "name": "name", "email": "email", "age": "age"}

def to_row(entity):
    # объект -> строка (для INSERT)
    return {column: entity.get(field) for field, column in mapping.items()}

def to_entity(row):
    # строка -> объект (для SELECT)
    return {field: row.get(column) for field, column in mapping.items()}

user = {"id": 1, "name": "Анна", "email": "[email protected]", "age": 30}
row = to_row(user)
print("INSERT строка:", row)
print("Обратно объект:", to_entity(row))

Запустите «Попробуй сам ▶»: ORM делает ровно это, только генерируя реальный SQL и учитывая типы.

Частые ошибки

  • Импорт из javax. В Boot 3 это не скомпилируется или не подхватится — используйте jakarta.persistence.
  • Нет конструктора без аргументов. Hibernate требует пустой конструктор для создания объектов из строк.
  • Бизнес-логика в сущности. Сущность — про данные и их маппинг, а не про сложную логику.

Best practices

  • Импортируйте аннотации из jakarta.persistence (Spring Boot 3).
  • Указывайте ограничения в @Column (nullable, length, unique) — это отразится в схеме.
  • Не выставляйте сущности наружу в JSON — для этого есть DTO (отдельный раздел).

Итог: JPA-сущность — это мост между классом и таблицей. @Entity, @Id, @GeneratedValue, @Column описывают отображение, а Hibernate генерирует SQL за вас. Помните про jakarta-namespace в Boot 3.

Закрепим главное

Сущность — это место встречи двух миров: объектного и реляционного. Чем точнее вы опишете отображение через аннотации, тем меньше сюрпризов в схеме базы. Ограничения в @Column (nullable, length, unique) — это не украшение, а реальные ограничения целостности, которые попадут в DDL и защитят данные от мусора на уровне СУБД.

Дважды подчеркнём про namespace, потому что это самая частая путаница новичков на Spring Boot 3. Все JPA-аннотации импортируются из jakarta.persistence, а не из javax.persistence. Если вы копируете код из старого туториала и видите javax — это сигнал, что материал устарел и относится к Spring Boot 2. Та же история с валидацией (jakarta.validation) и сервлетами (jakarta.servlet). Привыкайте сразу к jakarta-миру: весь актуальный стек 2024–2025 годов живёт именно в нём, и обратной дороги к javax уже нет.

Проверьте себя
1. Из какого пакета импортируются JPA-аннотации (@Entity, @Id) в Spring Boot 3?
Ajavax.persistence
Bjakarta.persistence
Corg.springframework.data
Djava.sql
2. Что делает @GeneratedValue(strategy = GenerationType.IDENTITY) на поле @Id?
AЗапрещает изменение id
BПоручает базе данных автоматически генерировать значение первичного ключа
CДелает поле уникальным вручную
DШифрует идентификатор