Что изменилось в Hibernate 6
В Hibernate 5 многие команды спокойно мапили колонку TINYINT на Integer/int, а SMALLINT — тоже на Integer. Драйвер «проглатывал» расширение типа, schema validation часто была мягче, и всё работало годами.
Hibernate 6 перешёл на строгую пару Java type ↔ JDBC type через JavaType / JdbcType / SqlTypes. Рекомендуемый JDBC-тип для Java-типа стал каноническим: byte/Byte → TINYINT, short/Short → SMALLINT, int/Integer → INTEGER. Если в БД уже лежит более узкий тип, а в entity — более широкий, при ddl-auto=validate получаете классическую ошибку wrong column type.
Пример 1: TINYINT больше не «почти Integer»
Типичный legacy-код:
// Hibernate 5 — часто проходило
@Column(name = "status_code")
private Integer statusCode; // колонка TINYINT
После апгрейда на Spring Boot 3 / Hibernate 6:
Schema-validation: wrong column type encountered;
found [tinyint (Types#TINYINT)], but expecting [integer (Types#INTEGER)]
Правильное сужение Java-типа под DDL:
@Column(name = "status_code")
private Byte statusCode; // или byte, если null не нужен
Если менять API домена нельзя (публичный DTO ждёт Integer), оставьте поле как есть, но явно скажите Hibernate JDBC-тип:
@JdbcTypeCode(SqlTypes.TINYINT)
@Column(name = "status_code")
private Integer statusCode;
Либо точечно:
@JdbcType(TinyIntJdbcType.class)
private Integer statusCode;
Пример 2: SMALLINT → Short
Та же история для двухбайтовых колонок:
// Было
private Integer countryId; // SMALLINT в PostgreSQL/MySQL
// Стало логичнее
private Short countryId;
// Или явный override
@JdbcTypeCode(SqlTypes.SMALLINT)
private Integer countryId;
Сужение типов — не «придирка фреймворка», а защита от молчаливой потери данных и от рассинхрона DDL ↔ entity при миграциях Flyway/Liquibase.
Пример 3: enum и смена дефолта
В Hibernate 6.1+ ordinal-enum по умолчанию ушёл с TINYINT на SMALLINT (у enum теоретически больше 255 значений). На MySQL/MariaDB/H2 это часто всплывает при validate:
public enum OrderStatus { NEW, PAID, SHIPPED, CANCELLED }
@Enumerated(EnumType.ORDINAL)
private OrderStatus status;
Варианты миграции:
- расширить колонку до
SMALLINTмиграцией; - зафиксировать старый JDBC-тип:
@JdbcTypeCode(SqlTypes.TINYINT); - лучше для эволюции схемы —
@Enumerated(EnumType.STRING)+VARCHAR/ENUMБД.
@Enumerated(EnumType.ORDINAL)
@JdbcTypeCode(SqlTypes.TINYINT)
private OrderStatus status;
// Предпочтительнее для читаемости и миграций
@Enumerated(EnumType.STRING)
@Column(length = 32)
private OrderStatus status;
Пример 4: boolean — BOOLEAN, BIT или TINYINT
На PostgreSQL Boolean спокойно мапится на BOOLEAN. На MySQL historically часто BIT(1) или TINYINT(1). После апгрейда диалект может ожидать другой код типа, чем в вашей старой схеме.
// Явно под MySQL tinyint(1)
@JdbcTypeCode(SqlTypes.TINYINT)
private Boolean active;
// Или под BIT
@JdbcTypeCode(SqlTypes.BIT)
private Boolean active;
Правило то же: либо подгоняете Java/JDBC-аннотацию под существующую колонку, либо меняете DDL миграцией — не оба сразу «наугад».
Пример 5: UUID, время и «старые» Timestamp
Hibernate 6 лучше дружит с java.time и нативными UUID:
@JdbcTypeCode(SqlTypes.UUID)
private UUID id;
private Instant createdAt; // timestamptz / timestamp
private OffsetDateTime updatedAt; // предпочтительнее java.util.Date
Мапинг java.util.Date / Calendar всё ещё жив, но на новых проектах его сужают до Instant/LocalDateTime/OffsetDateTime, чтобы убрать двусмысленность таймзоны. Для PostgreSQL uuid колонки не тащите строковый VARCHAR(36) без необходимости — это лишние преобразования и индексы другого размера.
Пример 6: AttributeConverter вместо «магического» Integer
Когда в БД хранится код TINYINT, а в домене нужен богатый тип:
@Converter(autoApply = true)
public class PriorityConverter implements AttributeConverter<Priority, Byte> {
public Byte convertToDatabaseColumn(Priority p) {
return p == null ? null : p.getCode(); // 1..5
}
public Priority convertToEntityAttribute(Byte code) {
return Priority.fromCode(code);
}
}
private Priority priority; // колонка TINYINT, Java — enum/value object
Так вы сужаете JDBC-тип до реальной колонки и одновременно поднимаете уровень доменной модели.
Как диагностировать быстро
- Включите
spring.jpa.hibernate.ddl-auto=validateна staging — ошибки всплывут до прода. - Сверьте DDL (Flyway) с entity:
TINYINT/SMALLINT/INT/BIGINT. - Смотрите migration guide Hibernate 6.0 → 6.2 → 6.4: enum, UUID, JSON, array types.
- Не лечите всё через
columnDefinition— это ломает переносимость диалектов. Предпочтительнее@JdbcTypeCode/ смена Java-типа / миграция колонки.
Практический чеклист миграции
TINYINT→Byte/byte(или@JdbcTypeCode(SqlTypes.TINYINT)на Integer).SMALLINT→Short/short.- Ordinal enum → проверить
SMALLINTvs старыйTINYINT. Boolean→ сверить сBIT/BOOLEAN/TINYINTконкретной СУБД.- Время →
Instant/OffsetDateTime; UUID →UUID+SqlTypes.UUID.
Вывод
«Сужение типов» в Hibernate 6 — это про честный контракт между колонкой и полем entity. Раньше int часто маскировал TINYINT; сейчас фреймворк требует либо точный Java-тип (byte/short), либо явный @JdbcTypeCode, либо миграцию DDL. Закладывайте это в апгрейд Spring Boot 2→3: один день на аудит колонок экономит неделю странных validate-ошибок на каждом окружении.
