Spring Boot + JPA (Hibernate 7.4): One Annotation for Pagination, History, and Audit Tables

The article demonstrates how Hibernate 7.4, used with Spring Boot 3.5, adds built‑in support for safe pagination of collection fetches, temporal history tables, and native audit tables, all configurable via a single annotation and a few properties, with concrete code examples and runtime screenshots.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Boot + JPA (Hibernate 7.4): One Annotation for Pagination, History, and Audit Tables

In enterprise projects JPA maps objects to databases, but growing business needs such as large‑scale pagination, change tracking, and historical queries often require extra development or third‑party components. Hibernate 7.4 enhances the ORM by integrating these capabilities directly.

1. Introduction

Spring Boot 3.5 is used as the runtime environment. The article shows practical changes introduced in Hibernate 7.4 that simplify pagination, history, and audit features.

2. Practical Cases

2.1 Pagination limit and fetch join

When loading a page of parent entities together with their child collections, a typical need is to fetch a few Order entities and their List<OrderItem> items. The following entity definitions are used:

@Entity
public class Order {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private BigDecimal price;
  @OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
  private List<OrderItem> items = new ArrayList<>();
}

@Entity
public class OrderItem {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String name;
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "order_id")
  private Order order;
}

The query executed via JPA is:

private final EntityManager entityManager;

public void queryOrder() {
  String sql = "select o from Order o join fetch o.items order by o.id";
  List<Order> orders = this.entityManager.createQuery(sql, Order.class)
      .setMaxResults(10)
      .getResultList();
  System.err.println(orders);
}

Running this on a large table triggers a full‑table scan and produces a warning; with massive data it can cause an OutOfMemoryException . To prevent this, the following Spring property is set:

spring:
  jpa:
    properties:
      '[query.fail_on_pagination_over_collection_fetch]': true

With the property enabled, the same query throws an exception immediately, avoiding OOM. Hibernate 7.4 (used with Spring Boot 4.1.x + Data JPA) lifts this restriction, allowing the query to succeed as shown in the subsequent console output (images omitted for brevity).

2.2 History table

Hibernate 7.4+ adds built‑in support for temporal history tables, enabling versioned queries of an entity at a specific point in time. Example entity:

@Entity
@Temporal
@Temporal.HistoryTable(name = "t_products_history")
public class Product {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String code;
  private String name;
  private BigDecimal price;
}

The @Temporal annotation marks the entity for history tracking, and @Temporal.HistoryTable specifies the table name. Hibernate stores previous versions in t_products_history with columns effective (when the version became valid) and superseded (when it was replaced).

Saving a Product produces SQL shown in the article’s screenshots, and the history table is populated accordingly.

To query a version as of a given timestamp, the following code is used:

public Product query(Long id, Instant time) {
  try (var session = this.sessionFactory.withOptions().asOf(time).open()) {
    return session.find(Product.class, id);
  }
}

The console output confirms that the correct historical row is retrieved.

2.3 Audit table

Previously, audit functionality required the separate Hibernate Envers library. Starting with Hibernate 7.4, audit support is integrated into the core ORM. Adding the @Audited annotation enables audit logging, and @Audited.Table can map it to a custom table:

@Entity
@Audited
@Audited.Table(name = "t_products_audit_log")
public class Product {
}

Each change now writes a row to the audit table, recording the operation type and timestamp. The audit table contains a rev column (timestamp) and a revtype column, whose values correspond to the ModificationType enum:

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

Screenshots in the article illustrate the audit rows generated after an update operation.

Overall, Hibernate 7.4 simplifies three common enterprise requirements—paginated collection fetches, temporal history, and audit logging—by providing annotation‑driven configurations that eliminate the need for manual SQL tricks or external libraries.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Spring BootPaginationJPAAudit TableHibernate 7.4History Table
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.