惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
N
Netflix TechBlog - Medium
F
Fortinet All Blogs
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
宝玉的分享
宝玉的分享
Y
Y Combinator Blog
博客园 - 聂微东
WordPress大学
WordPress大学
酷 壳 – CoolShell
酷 壳 – CoolShell
B
Blog RSS Feed
小众软件
小众软件
The GitHub Blog
The GitHub Blog
S
SegmentFault 最新的问题
Hugging Face - Blog
Hugging Face - Blog
Jina AI
Jina AI
Microsoft Azure Blog
Microsoft Azure Blog
V
V2EX
B
Blog
H
Help Net Security
D
Docker
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
罗磊的独立博客
月光博客
月光博客
博客园 - 司徒正美

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания
Новые возможности Hibernate 7.4
Spring АйО · 2026-06-16 · via Все публикации подряд на Хабре

Простой

5 мин

3

В Hibernate 7.4 появилось несколько улучшений упрощающих загрузку страницы данных вместе со связанными дочерними коллекциями, доступ к историческим данным и ведение журнала аудита.

В статье рассматриваются следующие возможности:

  • Limits и Fetch Joins: как Hibernate 7.4 улучшает работу с постраничными запросами, включающими загрузку ассоциаций.

  • History и Audit Tables: как новые возможности поддерживают запросы к состоянию сущности в разные моменты времени и работу с историческими данными.


Пример кода к этой статье доступен в репозитории GitHub.

Limits и Fetch Joins

Одна из типичных задач в приложениях, работающих с данными, — загрузить страницу родительских сущностей вместе со связанной коллекцией дочерних сущностей. Например, предположим, что в приложении есть сущность Order с коллекцией Set<OrderItem> , и нам нужно загрузить несколько первых заказов вместе с их позициями.

List<Order> orders = session
        .createSelectionQuery(
            "select o from Order o join fetch o.items order by o.id",
            Order.class
        )
        .setMaxResults(10)
        .getResultList();

В версиях Hibernate до 7.4 применение ограничения к запросу с fetch join коллекции нельзя было безопасно передать на уровень базы данных. Поскольку каждый Order может содержать несколько строк OrderItem , прямое ограничение SQL-результата могло обрезать коллекцию позиций заказа. Чтобы избежать возврата неполных коллекций, Hibernate загружал все подходящие строки из базы данных и применял пагинацию в памяти на уровне приложения.

Такое поведение было корректным, но могло обходиться дорого. Запрос, рассчитанный на загрузку только 10 заказов, всё равно мог считывать значительно больше строк, если таблица содержала большое количество заказов и позиций.

До Hibernate 7.4 генерируемый SQL выглядел примерно так:

select
    o1_0.id, i1_0.order_id, i1_0.id, i1_0.product_code,
    i1_0.quantity, o1_0.order_number, o1_0.status
from
    orders o1_0
        join
    order_items i1_0
    on o1_0.id=i1_0.order_id

Как видно, ограничение (пагинация) не применяется на уровне SQL-запроса. Поэтому будут загружены все orders и связанные с ними order_items, что может оказаться очень затратной операцией и привести к OutOfMemoryException.

Hibernate при этом записывает в лог предупреждение следующего вида:

[WARN] HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory

Один из способов запретить Hibernate выполнять пагинацию в памяти — задать следующее свойство:

hibernate.query.fail_on_pagination_over_collection_fetch=true

При установке этого свойства Hibernate будет выбрасывать исключение вместо того, чтобы выполнять пагинацию в памяти.

Hibernate 7.4 решает эту проблему с помощью вложенных запросов. Вместо того чтобы применять ограничение непосредственно к результату JOIN, Hibernate сначала определяет ограниченный набор идентификаторов родительских сущностей, а затем загружает связанную коллекцию только для этих строк.

Это позволяет выполнять пагинацию на стороне базы данных, при этом для каждого выбранного Order возвращаются полные коллекции позиций.

В Hibernate 7.4 SQL генерируется следующим образом:

select
        o1_0.id, i1_0.order_id, i1_0.id, i1_0.product_code,
        i1_0.quantity, o1_0.order_number,o1_0.status 
    from
        (select
            o1_0.id, o1_0.order_number, o1_0.status 
        from
            orders o1_0 
        where
            exists(select
                1 from order_items i1_0 
            where
                o1_0.id=i1_0.order_id) 
        offset
            ? rows 
        fetch
            first ? rows only) o1_0(id, order_number, status) 
    join
        order_items i1_0 
            on o1_0.id=i1_0.order_id

Это улучшение делает fetch join по-настоящему удобным для постраничных экранов — например, страницы списка заказов, где каждый заказ отображается вместе со своими позициями, — без необходимости предварительно загружать весь результирующий набор.

History и Audit Tables

Hibernate 7.4 добавляет встроенную поддержку history tables и audit tables. Обе возможности позволяют отслеживать изменения данных сущностей, но решают немного разные задачи: history tables позволяют запрашивать состояние сущности в определённый момент времени, тогда как audit tables фиксируют последовательность и природу изменений, произошедших с сущностью.

Рассмотрим следующую сущность Product :

@Entity
@Table(name = "products")
class Product {
    //fields id, code, name, price
}

History Tables

Чтобы включить историю (т.е. history table) для Product, аннотируйте сущность с помощью @Temporal и при необходимости укажите имя таблицы истории с помощью @Temporal.HistoryTable.

@Entity
@Table(name = "products")
@Temporal
@Temporal.HistoryTable(name="products_history")
class Product {
    //fields id, code, name, price
}

При таком маппинге Hibernate сохраняет предыдущие версии строк продукта в таблице products_history . Таблица включает колонки сущности и две дополнительные колонки: effective, которая фиксирует момент активации версии, и superseded, которая фиксирует момент её замены.

products_history :

id

code

name

price

effective

superseded

2251

P1000

Product-1000

40.00

2026-05-15 08:21:39.949001 +00:00

null

2301

P1001

Product-1001

90.00

2026-05-15 08:22:24.765883 +00:00

2026-05-15 08:22:24.778067 +00:00

2301

P1001

Product-1001

100.00

2026-05-15 08:22:24.778067 +00:00

null

Данные сущности Product в заданный момент времени можно получить следующим образом:

Instant someTime = ...
try (var session = sessionFactory.withOptions().asOf(someTime).open()) {
    var product = session.find(Product.class, productId);
    
}

Благодаря этому запросы состояния сущности в момент времени ‘t’ выглядят как обычный поиск сущностей, тогда как Hibernate самостоятельно разрешает нужную историческую строку за кулисами.

Hibernate предлагает несколько стратегий маппинга временны́х сущностей: NATIVE, SINGLE_TABLE и HISTORY_TABLE. Подробнее см. в разделе Temporal data.

Таблицы аудита

Раньше приложения на базе Hibernate, как правило, использовали отдельную библиотеку Hibernate Envers для аудита изменений сущностей. Hibernate 7.4 встраивает поддержку таблиц аудита непосредственно в Hibernate ORM, так что приложения могут использовать эту функциональность нативно — без подключения Envers для данного сценария.

Поддержка аудита включается добавлением @Audited и может быть привязана к произвольной таблице с помощью @Audited.Table.

@Entity
@Table(name = "products")
@Audited
@Audited.Table(name="products_aud_log")
class Product {
    //fields id, code, name, price
}

При включённом аудите Hibernate записывает в таблицу аудита по одной строке на каждое изменение. В отличие от таблицы истории, таблица аудита ориентирована на фиксацию того, какая именно операция произошла (DELETE, UPDATE или INSERT) и когда именно.

id

code

name

price

rev

revtype

2001

P1002

Product-1002

90.00

2026-05-13 14:58:17.505775 +00:00

0

2001

P1002

Product-1002

100.00

2026-05-13 14:58:17.518194 +00:00

1

Значения rev — временны́е метки момента изменения. Значения revtype представлены с помощью ModificationType enum следующим образом:

public enum ModificationType {
    /**
    * Creation, encoded as 0
    */
    ADD,
    /**
    * Modification, encoded as 1
    */
    MOD,
    /**
    * Deletion, encoded as 2
    */
    DEL
}

Подробнее см. в разделе Audit logs.

Итоги

В большинстве приложений для отображения списков ресурсов используется пагинация, и раньше приходилось писать собственную логику для постраничной загрузки данных вместе со связанными дочерними коллекциями. Теперь это решается на уровне самого фреймворка. Кроме того, для реализации аудита мы привыкли опираться на внешние библиотеки, такие как Envers — теперь же эта возможность предоставляется самим Hibernate.

Hibernate 7.4 привносит практичные улучшения, решающие реальные задачи в приложениях на базе JPA/Hibernate. Будь то оптимизация поведения запросов при пагинации или отслеживание исторических данных — Hibernate 7.4 сокращает объём инфраструктурного кода, который нужно писать самостоятельно, и обеспечивает более богатую поддержку из коробки без необходимости подключать дополнительные библиотеки.

Изучите эти новые возможности на примере данного репозитория на GitHub.

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.