8 Hidden Hibernate Annotations That Supercharge Spring Boot 3
This guide explores eight lesser‑known Hibernate annotations—such as @EntityGraph, @Formula, @SQLRestriction, @SQLDelete, @JoinFormula, @Cache, @DynamicUpdate/Insert, and @Filter/FilterDef—demonstrating how they improve performance, simplify queries, and enhance data handling in Spring Boot 3 applications for developers.
Introduction
Spring Data JPA greatly simplifies database access by wrapping the JPA specification, automatically generating CRUD methods from repository interfaces and reducing boilerplate code. It supports lazy loading, caching, transaction management, type‑safe queries, pagination, and complex association handling. This article introduces eight lesser‑known but powerful Hibernate annotations that address performance tuning, query optimization, and development efficiency.
Practical Cases
2.1 @EntityGraph
The @EntityGraph annotation defines the loading strategy for entity associations, allowing eager fetching of specified attributes to solve the N+1 query problem.
@Entity
@NamedEntityGraph(
name = "Order.EG",
attributeNodes = {
@NamedAttributeNode("customer"),
@NamedAttributeNode("items")
}
)
public class Order {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id")
private Customer customer;
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<OrderItem> items = new ArrayList<>();
}
public interface OrderRepository extends JpaRepository<Order, Long> {
@EntityGraph(value = "Order.EG", type = EntityGraphType.LOAD)
List<Order> findAll();
}Calling findAll() loads the configured associations in a single SQL statement, eliminating N+1 queries.
2.2 @Formula
@Formula enables dynamic calculation of a property using a SQL expression, without persisting a dedicated column. It is read‑only and can compute values such as discounts or aggregated data.
@Entity
@Table(name = "t_book")
public class Book {
// ... other fields ...
@Formula("price * 0.9")
private BigDecimal discountedPrice;
@Formula("(select avg(r.rating) from review r where r.book_id = id)")
private Double averageRating;
}When querying Book, the generated SQL includes the computed columns.
2.3 @SQLRestriction
@SQLRestriction injects a native SQL condition into the SQL generated for an entity or collection.
@Entity
@Table(name = "o_user")
@SQLRestriction("age > 10")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Integer age;
// ... other fields ...
}Queries on User now include the age > 10 clause.
2.4 @SQLDelete
@SQLDelete replaces the default DELETE statement with a custom DML, often used for soft‑delete implementations.
@Entity
@Table(name = "t_person")
@SQLDelete(sql = "update t_person p set p.deleted = 1 where p.id = ?")
public class Person {
// ... other fields ...
@Column(columnDefinition = "int default 0")
private Integer deleted = 0; // 0: normal, 1: deleted
}
public interface PersonRepository extends JpaRepository<Person, Long> {}Executing a delete on Person runs the custom UPDATE statement instead of a physical DELETE.
2.5 @JoinFormula
@JoinFormula allows a custom join condition written in native SQL when the default association mapping is insufficient.
@Entity
@Table(name = "t_document")
public class Document {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinFormula("(SELECT v.id FROM t_document_version v WHERE v.document_id = id ORDER BY v.version_number DESC LIMIT 1)")
private DocumentVersion latestVersion;
private Integer versionCount;
}
@Entity
@Table(name = "t_document_version")
public class DocumentVersion {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long documentId;
private Integer versionNumber;
private String content;
}Querying Document generates SQL that joins the latest version via the sub‑query.
2.6 @Cache and @CacheableQuery
These annotations provide fine‑grained second‑level cache control. Example below caches the Country entity and its collection of State objects.
@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Country {
@Id
private String code;
private String name;
@OneToMany(mappedBy = "country")
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
private Set<State> states;
}Using these annotations can significantly improve application performance by reducing database hits.
2.7 @DynamicUpdate & @DynamicInsert
@DynamicUpdate generates UPDATE statements that include only the fields that have changed; @DynamicInsert generates INSERT statements that include only non‑null fields, reducing unnecessary column writes.
@Entity
@Table(name = "t_book")
@DynamicInsert
@DynamicUpdate
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String isbn;
private String description;
private Integer page;
private BigDecimal price;
private String rating;
}
@Resource
private BookRepository bookRepository;
@Test
public void testSave() {
Book book = new Book();
book.setTitle("Spring Boot3实战案例200讲");
book.setPrice(BigDecimal.valueOf(70));
this.bookRepository.save(book);
}The generated SQL contains only the title and price columns, demonstrating the performance benefit.
2.8 @Filter & @FilterDef
@FilterDef defines a reusable filter template with parameters; @Filter applies the template to an entity, enabling dynamic SQL conditions such as soft‑delete or multi‑tenant filtering.
@FilterDef(
name = "priceRangeFilter",
parameters = {
@ParamDef(name = "minPrice", type = BigDecimal.class),
@ParamDef(name = "maxPrice", type = BigDecimal.class)
}
)
@Filter(name = "priceRangeFilter", condition = "price >= :minPrice AND price <= :maxPrice")
public class Book {}
private final EntityManager entityManager;
@Transactional(readOnly = true)
public List<Book> findBooksByPriceRange(BigDecimal minPrice, BigDecimal maxPrice) {
Session session = entityManager.unwrap(Session.class);
session.enableFilter("priceRangeFilter")
.setParameter("minPrice", minPrice)
.setParameter("maxPrice", maxPrice);
return bookRepository.findAll();
}The filter must be enabled within a transaction and the same Hibernate Session for it to take effect.
All the annotations demonstrated above are provided by Hibernate.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
