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 заранее. |
AUTO | Hibernate выбирает стратегию сам — обычно 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, проекции.