← назад к разделу

Hibernate — это ORM-библиотека: она берёт объект Java и сама транслирует его в строку таблицы (и обратно). Чтобы это работало, нужно описать, как именно поля класса соответствуют колонкам. Этот процесс и называют маппингом сущностей.

Что такое сущность

Сущность (entity) — это обычный Java-класс, который Hibernate умеет сохранять в базу данных и загружать из неё. Минимальные требования: аннотация @Entity, конструктор без аргументов (может быть protected) и поле с @Id.

import jakarta.persistence.*;

@Entity
@Table(name = "products")
public class Product {

    @Id
    private Long id;

    private String name;
}

@Table(name = "products") задаёт имя таблицы явно. Без неё Hibernate использует имя класса — поведение зависит от настройки hibernate.physical_naming_strategy, поэтому явное @Table надёжнее.

Первичный ключ и генерация идентификатора

Каждая сущность обязана иметь @Id. Чтобы Hibernate генерировал значение автоматически, добавляют @GeneratedValue.

Стратегии генерации:

СтратегияКак работает
IDENTITYПолагается на AUTO_INCREMENT / GENERATED ALWAYS AS IDENTITY в БД. Hibernate вставляет строку и потом читает сгенерированный ключ.
SEQUENCEИспользует объект-последовательность в БД. Hibernate запрашивает следующий ID заранее.
AUTOHibernate выбирает стратегию сам — обычно SEQUENCE для PostgreSQL.

Почему SEQUENCE предпочтительнее IDENTITY? При IDENTITY Hibernate не знает ID до выполнения INSERT — это блокирует батчинг (JDBC batch). Со стратегией SEQUENCE ID запрашивается заранее через nextval, поэтому несколько INSERT можно отправить одним пакетом, что резко снижает число round-trip к базе.

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_seq")
@SequenceGenerator(name = "product_seq", sequenceName = "product_id_seq", allocationSize = 50)
private Long id;

allocationSize = 50 означает: Hibernate резервирует блок из 50 значений за один запрос к последовательности и расходует его инкрементально. При интенсивной вставке это экономит запросы к БД.

Если идентификатор — UUID, см. статью UUID в PostgreSQL — там разобраны типы uuid и стратегии генерации на уровне базы.

Колонки: @Column

По умолчанию Hibernate маппит каждое поле на колонку с тем же именем (с учётом стратегии именования). @Column позволяет явно задать параметры:

@Column(name = "product_name", nullable = false, length = 255)
private String name;

@Column(name = "price", precision = 10, scale = 2)
private BigDecimal price;

@Column(name = "in_stock", columnDefinition = "boolean default true")
private boolean inStock;

Важные атрибуты:

  • nullable = false — добавляет NOT NULL в DDL (если Hibernate генерирует схему) и сигнализирует Bean Validation.
  • length — максимальная длина для VARCHAR (по умолчанию 255).
  • precision / scale — точность для NUMERIC.
  • insertable = false / updatable = false — Hibernate не включает поле в INSERT / UPDATE. Используется, например, для колонок, управляемых триггерами.

Перечисления: ловушка @Enumerated

Перечисление (enum) можно сохранять двумя способами:

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Status status;

@Enumerated(EnumType.ORDINAL) // ОПАСНО
private Priority priority;

EnumType.ORDINAL хранит порядковый номер константы (0, 1, 2…). Стоит добавить новую константу в середину enum или переупорядочить существующие — и все старые данные в базе окажутся неверными. Это молчаливая ошибка, которую очень сложно обнаружить.

Правило: всегда используй EnumType.STRING. Строковое значение читаемо, устойчиво к рефакторингу, и понятно при просмотре данных в консоли БД.

Встраиваемые объекты: @Embedded и @Embeddable

Иногда несколько колонок одной таблицы логически образуют группу — например, адрес. Вместо того чтобы складывать всё в плоский класс-сущность, можно выделить встраиваемый объект (@Embeddable):

@Embeddable
public class Address {
    private String city;
    private String street;

    @Column(name = "postal_code", length = 10)
    private String postalCode;
}
@Entity
@Table(name = "customers")
public class Customer {

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

    @Embedded
    private Address address;
}

Hibernate хранит city, street, postal_code прямо в таблице customers — никакой дополнительной таблицы нет. При этом в Java-коде адрес — самостоятельный объект со своей логикой валидации.

Если один @Embeddable-тип используется в сущности дважды (например, deliveryAddress и billingAddress), имена колонок нужно переопределить через @AttributeOverrides:

@Embedded
@AttributeOverrides({
    @AttributeOverride(name = "city", column = @Column(name = "billing_city")),
    @AttributeOverride(name = "street", column = @Column(name = "billing_street")),
    @AttributeOverride(name = "postalCode", column = @Column(name = "billing_postal_code"))
})
private Address billingAddress;

Поля без маппинга: @Transient

Если поле нужно в Java-классе, но сохранять его в базу не нужно — добавь @Transient:

@Transient
private String displayLabel; // вычисляется на лету, не хранится

Без @Transient Hibernate попытается найти соответствующую колонку и выбросит ошибку при несоответствии схемы (или молча запишет null — хуже).

Базовые типы и преобразования

Hibernate умеет маппить стандартные Java-типы напрямую: String, Integer, Long, BigDecimal, Boolean, LocalDate, LocalDateTime, ZonedDateTime, UUID. Для Hibernate 6 / Spring Boot 3 поддержка Java Time API встроена.

Для нестандартных типов (например, хранить список строк как JSON-колонку) применяют @Convert с реализацией AttributeConverter<X, Y>:

@Converter(autoApply = false)
public class StringListConverter implements AttributeConverter<List<String>, String> {

    @Override
    public String convertToDatabaseColumn(List<String> list) {
        return list == null ? null : String.join(",", list);
    }

    @Override
    public List<String> convertToEntityAttribute(String value) {
        return value == null ? List.of() : List.of(value.split(","));
    }
}
@Convert(converter = StringListConverter.class)
@Column(name = "tags")
private List<String> tags;

Коротко

  • @Entity + конструктор без аргументов + @Id — минимум для сущности.
  • SEQUENCE лучше IDENTITY для высоких нагрузок: позволяет JDBC-батчинг.
  • @Enumerated(EnumType.STRING) — всегда; ORDINAL ломается при изменении порядка констант.
  • @Embedded / @Embeddable — группируют колонки в объект без новой таблицы.
  • @Transient — поле в памяти, не в базе.
  • Для нестандартных типов — AttributeConverter<X, Y>.

Что почитать дальше

  • Связи между сущностями — @OneToMany, @ManyToOne, @ManyToMany, каскады.
  • Persistence Context — как Hibernate отслеживает изменения и когда делает flush.
  • Стратегии наследования — SINGLE_TABLE, JOINED, TABLE_PER_CLASS.
  • Spring Data JPA — репозитории поверх Hibernate: JpaRepository, @Query, проекции.